]> git.donarmstrong.com Git - roundcube.git/commitdiff
Imported Upstream version 0.1~beta2.2~dfsg
authorJérémy Bobbio <lunar@debian.org>
Sat, 18 Jun 2011 14:55:08 +0000 (16:55 +0200)
committerJérémy Bobbio <lunar@debian.org>
Sat, 18 Jun 2011 14:55:08 +0000 (16:55 +0200)
288 files changed:
._CHANGELOG [new file with mode: 0644]
.htaccess [new file with mode: 0644]
CHANGELOG [new file with mode: 0644]
INSTALL [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README [new file with mode: 0644]
SQL/mysql.initial.sql [new file with mode: 0644]
SQL/mysql.update.sql [new file with mode: 0644]
SQL/mysql5.initial.sql [new file with mode: 0644]
SQL/postgres.initial.sql [new file with mode: 0755]
SQL/sqlite.initial.sql [new file with mode: 0644]
UPGRADING [new file with mode: 0644]
config/.htaccess [new file with mode: 0644]
config/db.inc.php.dist [new file with mode: 0644]
config/main.inc.php.dist [new file with mode: 0644]
index.php [new file with mode: 0644]
logs/.htaccess [new file with mode: 0644]
program/blank.gif [new file with mode: 0644]
program/include/bugs.inc [new file with mode: 0644]
program/include/cache.inc [new file with mode: 0644]
program/include/main.inc [new file with mode: 0644]
program/include/rcube_db.inc [new file with mode: 0755]
program/include/rcube_imap.inc [new file with mode: 0644]
program/include/rcube_ldap.inc [new file with mode: 0644]
program/include/rcube_mdb2.inc [new file with mode: 0755]
program/include/rcube_shared.inc [new file with mode: 0644]
program/include/rcube_smtp.inc [new file with mode: 0644]
program/include/rcube_sqlite.inc [new file with mode: 0644]
program/include/session.inc [new file with mode: 0644]
program/js/app.js [new file with mode: 0644]
program/js/common.js [new file with mode: 0644]
program/js/googiespell.js [new file with mode: 0755]
program/lib/Auth/SASL.php [new file with mode: 0755]
program/lib/Auth/SASL/Anonymous.php [new file with mode: 0755]
program/lib/Auth/SASL/Common.php [new file with mode: 0755]
program/lib/Auth/SASL/CramMD5.php [new file with mode: 0755]
program/lib/Auth/SASL/DigestMD5.php [new file with mode: 0755]
program/lib/Auth/SASL/Login.php [new file with mode: 0755]
program/lib/Auth/SASL/Plain.php [new file with mode: 0755]
program/lib/DB.php [new file with mode: 0644]
program/lib/DB/common.php [new file with mode: 0644]
program/lib/DB/dbase.php [new file with mode: 0644]
program/lib/DB/fbsql.php [new file with mode: 0644]
program/lib/DB/ibase.php [new file with mode: 0644]
program/lib/DB/ifx.php [new file with mode: 0644]
program/lib/DB/msql.php [new file with mode: 0644]
program/lib/DB/mssql.php [new file with mode: 0644]
program/lib/DB/mysql.php [new file with mode: 0644]
program/lib/DB/mysqli.php [new file with mode: 0644]
program/lib/DB/oci8.php [new file with mode: 0644]
program/lib/DB/odbc.php [new file with mode: 0644]
program/lib/DB/pgsql.php [new file with mode: 0644]
program/lib/DB/sqlite.php [new file with mode: 0644]
program/lib/DB/storage.php [new file with mode: 0644]
program/lib/DB/sybase.php [new file with mode: 0644]
program/lib/Mail/mime.php [new file with mode: 0644]
program/lib/Mail/mimeDecode.php [new file with mode: 0644]
program/lib/Mail/mimePart.php [new file with mode: 0644]
program/lib/Net/SMTP.php [new file with mode: 0644]
program/lib/Net/Socket.php [new file with mode: 0644]
program/lib/PEAR.php [new file with mode: 0644]
program/lib/encoding/CP1250.map [new file with mode: 0644]
program/lib/encoding/CP1251.map [new file with mode: 0644]
program/lib/encoding/CP1252.map [new file with mode: 0644]
program/lib/encoding/CP1253.map [new file with mode: 0644]
program/lib/encoding/CP1254.map [new file with mode: 0644]
program/lib/encoding/CP1255.map [new file with mode: 0644]
program/lib/encoding/CP1256.map [new file with mode: 0644]
program/lib/encoding/CP1257.map [new file with mode: 0644]
program/lib/encoding/CP1258.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-1.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-2.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-3.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-4.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-5.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-6.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-7.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-8.map [new file with mode: 0644]
program/lib/encoding/ISO-8859-9.map [new file with mode: 0644]
program/lib/enriched.inc [new file with mode: 0644]
program/lib/html2text.inc [new file with mode: 0644]
program/lib/icl_commons.inc [new file with mode: 0644]
program/lib/imap.inc [new file with mode: 0644]
program/lib/mime.inc [new file with mode: 0644]
program/lib/utf7.inc [new file with mode: 0644]
program/lib/utf8.class.php [new file with mode: 0644]
program/localization/de_CH/labels.inc [new file with mode: 0644]
program/localization/de_CH/messages.inc [new file with mode: 0644]
program/localization/de_DE/labels.inc [new file with mode: 0644]
program/localization/de_DE/messages.inc [new file with mode: 0644]
program/localization/en_GB/labels.inc [new file with mode: 0644]
program/localization/en_GB/messages.inc [new file with mode: 0644]
program/localization/en_US/labels.inc [new file with mode: 0644]
program/localization/en_US/messages.inc [new file with mode: 0644]
program/localization/es/labels.inc [new file with mode: 0644]
program/localization/es/messages.inc [new file with mode: 0644]
program/localization/fr/labels.inc [new file with mode: 0644]
program/localization/fr/messages.inc [new file with mode: 0644]
program/localization/index.inc [new file with mode: 0644]
program/localization/pt_BR/labels.inc [new file with mode: 0644]
program/localization/pt_BR/messages.inc [new file with mode: 0644]
program/localization/pt_PT/labels.inc [new file with mode: 0644]
program/localization/pt_PT/messages.inc [new file with mode: 0644]
program/steps/addressbook/delete.inc [new file with mode: 0644]
program/steps/addressbook/edit.inc [new file with mode: 0644]
program/steps/addressbook/func.inc [new file with mode: 0644]
program/steps/addressbook/ldapsearchform.inc [new file with mode: 0644]
program/steps/addressbook/list.inc [new file with mode: 0644]
program/steps/addressbook/save.inc [new file with mode: 0644]
program/steps/addressbook/show.inc [new file with mode: 0644]
program/steps/error.inc [new file with mode: 0644]
program/steps/mail/addcontact.inc [new file with mode: 0644]
program/steps/mail/check_recent.inc [new file with mode: 0644]
program/steps/mail/compose.inc [new file with mode: 0644]
program/steps/mail/folders.inc [new file with mode: 0644]
program/steps/mail/func.inc [new file with mode: 0644]
program/steps/mail/get.inc [new file with mode: 0644]
program/steps/mail/getunread.inc [new file with mode: 0644]
program/steps/mail/list.inc [new file with mode: 0644]
program/steps/mail/mark.inc [new file with mode: 0644]
program/steps/mail/move_del.inc [new file with mode: 0644]
program/steps/mail/rss.inc [new file with mode: 0644]
program/steps/mail/search.inc [new file with mode: 0644]
program/steps/mail/sendmail.inc [new file with mode: 0644]
program/steps/mail/show.inc [new file with mode: 0644]
program/steps/mail/spell.inc [new file with mode: 0644]
program/steps/mail/upload.inc [new file with mode: 0644]
program/steps/mail/viewsource.inc [new file with mode: 0644]
program/steps/settings/delete_identity.inc [new file with mode: 0644]
program/steps/settings/edit_identity.inc [new file with mode: 0644]
program/steps/settings/func.inc [new file with mode: 0644]
program/steps/settings/identities.inc [new file with mode: 0644]
program/steps/settings/manage_folders.inc [new file with mode: 0644]
program/steps/settings/save_identity.inc [new file with mode: 0644]
program/steps/settings/save_prefs.inc [new file with mode: 0644]
skins/default/addresses.css [new file with mode: 0644]
skins/default/common.css [new file with mode: 0755]
skins/default/googiespell.css [new file with mode: 0755]
skins/default/images/blank.gif [new file with mode: 0644]
skins/default/images/buttons/add_act.png [new file with mode: 0644]
skins/default/images/buttons/add_contact_act.png [new file with mode: 0644]
skins/default/images/buttons/add_contact_pas.png [new file with mode: 0644]
skins/default/images/buttons/add_contact_sel.png [new file with mode: 0644]
skins/default/images/buttons/add_pas.png [new file with mode: 0644]
skins/default/images/buttons/add_sel.png [new file with mode: 0644]
skins/default/images/buttons/addressbook.gif [new file with mode: 0644]
skins/default/images/buttons/addressbook.png [new file with mode: 0644]
skins/default/images/buttons/attach_act.png [new file with mode: 0644]
skins/default/images/buttons/attach_pas.png [new file with mode: 0644]
skins/default/images/buttons/attach_sel.png [new file with mode: 0644]
skins/default/images/buttons/back_act.png [new file with mode: 0644]
skins/default/images/buttons/back_pas.png [new file with mode: 0644]
skins/default/images/buttons/back_sel.png [new file with mode: 0644]
skins/default/images/buttons/bg.gif [new file with mode: 0644]
skins/default/images/buttons/compose_act.png [new file with mode: 0644]
skins/default/images/buttons/compose_pas.png [new file with mode: 0644]
skins/default/images/buttons/compose_sel.png [new file with mode: 0644]
skins/default/images/buttons/contacts_act.png [new file with mode: 0644]
skins/default/images/buttons/contacts_pas.png [new file with mode: 0644]
skins/default/images/buttons/contacts_sel.png [new file with mode: 0644]
skins/default/images/buttons/delete_act.png [new file with mode: 0644]
skins/default/images/buttons/delete_pas.png [new file with mode: 0644]
skins/default/images/buttons/delete_sel.png [new file with mode: 0644]
skins/default/images/buttons/down_arrow.png [new file with mode: 0755]
skins/default/images/buttons/download_act.png [new file with mode: 0644]
skins/default/images/buttons/download_pas.png [new file with mode: 0644]
skins/default/images/buttons/download_sel.png [new file with mode: 0644]
skins/default/images/buttons/drafts_act.png [new file with mode: 0644]
skins/default/images/buttons/drafts_pas.png [new file with mode: 0644]
skins/default/images/buttons/drafts_sel.png [new file with mode: 0644]
skins/default/images/buttons/edit_contact_act.png [new file with mode: 0644]
skins/default/images/buttons/edit_contact_pas.png [new file with mode: 0644]
skins/default/images/buttons/edit_contact_sel.png [new file with mode: 0644]
skins/default/images/buttons/forward_act.png [new file with mode: 0644]
skins/default/images/buttons/forward_pas.png [new file with mode: 0644]
skins/default/images/buttons/forward_sel.png [new file with mode: 0644]
skins/default/images/buttons/inbox_act.png [new file with mode: 0644]
skins/default/images/buttons/inbox_pas.png [new file with mode: 0644]
skins/default/images/buttons/inbox_sel.png [new file with mode: 0644]
skins/default/images/buttons/ldap_act.png [new file with mode: 0644]
skins/default/images/buttons/ldap_pas.png [new file with mode: 0644]
skins/default/images/buttons/logout.gif [new file with mode: 0644]
skins/default/images/buttons/logout.png [new file with mode: 0644]
skins/default/images/buttons/mail.gif [new file with mode: 0644]
skins/default/images/buttons/mail.png [new file with mode: 0644]
skins/default/images/buttons/next_act.png [new file with mode: 0644]
skins/default/images/buttons/next_pas.png [new file with mode: 0644]
skins/default/images/buttons/next_sel.png [new file with mode: 0644]
skins/default/images/buttons/previous_act.png [new file with mode: 0644]
skins/default/images/buttons/previous_pas.png [new file with mode: 0644]
skins/default/images/buttons/previous_sel.png [new file with mode: 0644]
skins/default/images/buttons/print_act.png [new file with mode: 0644]
skins/default/images/buttons/print_pas.png [new file with mode: 0644]
skins/default/images/buttons/print_sel.png [new file with mode: 0644]
skins/default/images/buttons/reply_act.png [new file with mode: 0644]
skins/default/images/buttons/reply_pas.png [new file with mode: 0644]
skins/default/images/buttons/reply_sel.png [new file with mode: 0644]
skins/default/images/buttons/replyall_act.png [new file with mode: 0644]
skins/default/images/buttons/replyall_pas.png [new file with mode: 0644]
skins/default/images/buttons/replyall_sel.png [new file with mode: 0644]
skins/default/images/buttons/send_act.png [new file with mode: 0644]
skins/default/images/buttons/send_pas.png [new file with mode: 0644]
skins/default/images/buttons/send_sel.png [new file with mode: 0644]
skins/default/images/buttons/settings.gif [new file with mode: 0644]
skins/default/images/buttons/settings.png [new file with mode: 0644]
skins/default/images/buttons/source_act.png [new file with mode: 0644]
skins/default/images/buttons/source_pas.png [new file with mode: 0644]
skins/default/images/buttons/source_sel.png [new file with mode: 0644]
skins/default/images/buttons/spacer.gif [new file with mode: 0644]
skins/default/images/buttons/spellcheck_act.png [new file with mode: 0644]
skins/default/images/buttons/spellcheck_pas.png [new file with mode: 0644]
skins/default/images/buttons/spellcheck_sel.png [new file with mode: 0644]
skins/default/images/buttons/up_arrow.png [new file with mode: 0755]
skins/default/images/cleardot.png [new file with mode: 0644]
skins/default/images/display/confirm.png [new file with mode: 0644]
skins/default/images/display/info.png [new file with mode: 0644]
skins/default/images/display/loading.gif [new file with mode: 0755]
skins/default/images/display/warning.png [new file with mode: 0644]
skins/default/images/favicon.ico [new file with mode: 0644]
skins/default/images/googiespell/change_lang.gif [new file with mode: 0644]
skins/default/images/googiespell/indicator.gif [new file with mode: 0644]
skins/default/images/googiespell/ok.gif [new file with mode: 0644]
skins/default/images/googiespell/spellc.gif [new file with mode: 0644]
skins/default/images/icons/abcard.png [new file with mode: 0644]
skins/default/images/icons/attachment.png [new file with mode: 0644]
skins/default/images/icons/deleted.png [new file with mode: 0644]
skins/default/images/icons/dot.png [new file with mode: 0644]
skins/default/images/icons/edit.png [new file with mode: 0644]
skins/default/images/icons/flagged.png [new file with mode: 0644]
skins/default/images/icons/folder-closed.png [new file with mode: 0644]
skins/default/images/icons/folder-drafts.png [new file with mode: 0644]
skins/default/images/icons/folder-inbox.png [new file with mode: 0644]
skins/default/images/icons/folder-junk.png [new file with mode: 0644]
skins/default/images/icons/folder-open.png [new file with mode: 0644]
skins/default/images/icons/folder-sent.png [new file with mode: 0644]
skins/default/images/icons/folder-trash.png [new file with mode: 0644]
skins/default/images/icons/forwarded.png [new file with mode: 0644]
skins/default/images/icons/plus.gif [new file with mode: 0755]
skins/default/images/icons/remove-attachment.png [new file with mode: 0644]
skins/default/images/icons/replied.png [new file with mode: 0644]
skins/default/images/icons/reset.gif [new file with mode: 0644]
skins/default/images/icons/silhouette.png [new file with mode: 0644]
skins/default/images/icons/unread.png [new file with mode: 0644]
skins/default/images/listheader_aqua.gif [new file with mode: 0644]
skins/default/images/listheader_dark.gif [new file with mode: 0644]
skins/default/images/listheader_light.gif [new file with mode: 0644]
skins/default/images/mailbox_list.gif [new file with mode: 0644]
skins/default/images/mailbox_selected.gif [new file with mode: 0644]
skins/default/images/rcube_watermark.png [new file with mode: 0644]
skins/default/images/roundcube_logo.gif [new file with mode: 0644]
skins/default/images/roundcube_logo.png [new file with mode: 0644]
skins/default/images/roundcube_logo_print.gif [new file with mode: 0644]
skins/default/images/searchfield.gif [new file with mode: 0644]
skins/default/images/sort_asc.gif [new file with mode: 0644]
skins/default/images/sort_desc.gif [new file with mode: 0644]
skins/default/images/tab_act.gif [new file with mode: 0644]
skins/default/images/tab_pas.gif [new file with mode: 0644]
skins/default/images/taskbar.gif [new file with mode: 0644]
skins/default/includes/header.html [new file with mode: 0644]
skins/default/includes/ldapscripts.html [new file with mode: 0644]
skins/default/includes/links.html [new file with mode: 0644]
skins/default/includes/settingscripts.html [new file with mode: 0644]
skins/default/includes/settingstabs.html [new file with mode: 0644]
skins/default/includes/taskbar.html [new file with mode: 0644]
skins/default/ldapsearchform.css [new file with mode: 0644]
skins/default/mail.css [new file with mode: 0644]
skins/default/pngbehavior.htc [new file with mode: 0644]
skins/default/print.css [new file with mode: 0644]
skins/default/settings.css [new file with mode: 0644]
skins/default/templates/addcontact.html [new file with mode: 0644]
skins/default/templates/addidentity.html [new file with mode: 0644]
skins/default/templates/addressbook.html [new file with mode: 0644]
skins/default/templates/compose.html [new file with mode: 0644]
skins/default/templates/editcontact.html [new file with mode: 0644]
skins/default/templates/editidentity.html [new file with mode: 0644]
skins/default/templates/error.html [new file with mode: 0644]
skins/default/templates/identities.html [new file with mode: 0644]
skins/default/templates/ldappublicsearch.html [new file with mode: 0644]
skins/default/templates/login.html [new file with mode: 0644]
skins/default/templates/mail.html [new file with mode: 0644]
skins/default/templates/managefolders.html [new file with mode: 0644]
skins/default/templates/message.html [new file with mode: 0644]
skins/default/templates/messagepart.html [new file with mode: 0644]
skins/default/templates/printmessage.html [new file with mode: 0644]
skins/default/templates/settings.html [new file with mode: 0644]
skins/default/templates/showcontact.html [new file with mode: 0644]
skins/default/watermark.html [new file with mode: 0644]
temp/.htaccess [new file with mode: 0644]

diff --git a/._CHANGELOG b/._CHANGELOG
new file mode 100644 (file)
index 0000000..429e7b2
Binary files /dev/null and b/._CHANGELOG differ
diff --git a/.htaccess b/.htaccess
new file mode 100644 (file)
index 0000000..9474105
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,13 @@
+# AddDefaultCharset    UTF-8
+php_flag       display_errors  Off
+php_flag       log_errors      On
+php_value      error_log       logs/errors
+php_value      upload_max_filesize     2M
+
+<FilesMatch "(\.inc|\~)$|^_">
+  Order allow,deny
+  Deny from all
+</FilesMatch>
+
+Order deny,allow
+Allow from all
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644 (file)
index 0000000..89fa444
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,364 @@
+CHANGELOG RoundCube Webmail
+---------------------------
+
+2007/01/15 (thomasb)
+----------
+- Applied path for wrong HTML message parsing
+
+
+2006/12/22 (thomasb)
+----------
+- Applied security fixes for XSS vulnerabilites
+
+
+2006/08/04 (thomasb)
+----------
+- Fixed Bug in saving identities (Ticket #1483915)
+- Set folder name in window title (Bug #1483919)
+- Don't add imap_root to INBOX path (Bug #1483816)
+- Attempt to create default folders only after login
+- Avoid usage of $CONFIG in rcube_imap class
+
+
+2006/07/30 (thomasb)
+----------
+- Alter links in HTML messages (Bug #1326402)
+- Added fallback if host not found in 'mail_domain' array
+- Applied patch of Charles to highlight droptargets (Ticket #1473034)
+- Fixed folder renaming (Bug #1483914)
+- Added confirmation message after deleting a folder
+
+
+2006/07/25 (thomasb)
+----------
+- Made folder renaming a bit more ajax-style
+- Changed rename-labels and German translation
+- Fixed addressbox countbar width (Bug #1483845)
+- Fixed refresh interval problems in Safari (Bug #1483902) 
+- Fixed clear_message_list_header() errors (Bug #1483898)
+- Sanity check of $message_set in imap.inc (Bug #1443200)
+- Added correct changing of message list headers for Sent folder
+- Updated Spanish localization (Ticket #1483887)
+- Applied patch #1483846
+
+
+2006/07/24 (richs)
+----------
+- Draft window no longer reloads. It saves to an iframe in the background instead (fixes bug #1483869)
+- Draft timer now part of program/js/app.js instead of skins/default/templates/compose.inc
+- Draft saving now properly returns an error when saving fails
+- Draft timer stops and resets properly when attachments are uploaded, or when saving manually
+- Old compose session/attachments are now cleaned up when a new/forward/reply/draft is made/opened
+
+
+2006/07/19 (thomasb)
+----------
+- Correct entity encoding of link urls (HTML validity)
+- Improved usability in compose step (Ticket #1483807)
+- Added absolute URLs to several buttons (for "open in new window")
+- Applied patch #1328032
+- Fixed Bug/Patch #1443200
+
+
+2006/07/18 (thomasb)
+----------
+- Fixed password with spaces issue (Bug #1364122)
+- Replaced _auth hash with second cookie (Ticket #1483811)
+- Don't use get_input_value() for passwords (Bug #1468895)
+- Made password encryption key configurable
+- Minor bugfixes with charset encoding
+- Added <label> tags to forms (Ticket #1483810)
+
+
+2006/07/07 (thomasb)
+----------
+- Fixed INSTALL_PATH bug #1425663
+
+
+2006/07/03 (richs)
+----------
+- Fixed compatibility with in-body email addresses containing "+" (Bug #1483836)
+- Updated French localizations (Ticket #1483862)
+- Incoming messages can now be moved to Drafts, edited, saved, then moved back (Feature #1436191)
+- Added Firefox workaround when clicking whitespace to drag messages (Bug #1483857)
+- Corrected Dutch and Italian localizations (Ticket #1483851 and #1483848)
+- Enabled 'Empty' (purge) command for Junk mailbox (defined in main.inc.php) 
+
+
+2006/06/30 (richs)
+----------
+- Fixed empty INBOX compatibility bug (Patch #1443200)
+- Temporarily fixed French "compact" localization (Patch #1483862)
+- Fixed "Select All" not working with Delete interface button (Bug #1332434)
+- Fixed messsage list column compatibility with Konqueror (Bug #1395711)
+- Fixed "unread count" in window title when count changed (Bug #1483812)
+- Fixed DB error when deleting from message table (Patch #1483835)
+
+
+2006/06/29 (richs)
+----------
+- Added ability to remove attachments (Feature #1436721)
+- Default folders are now auto-created on first login (Feature #1471594)
+- Fixed compatibility with folder apostrophes (e.g.: Joe's Folder) (Bug #1429458)
+- Corrected Italian localizations
+- Tweaked rename-folder form to clear after a rename
+
+
+2006/06/26 (richs)
+----------
+- Added button to immediately check for new messages 
+- New message checking now displays status "Checking for new messages..."
+- New message checking now looks for unread messages in all mailboxes (Feature #1326401)
+- Task buttons now respond to clicks by darkening (as in other applications)
+- Fixed "Sender" column changing to "Recipient" for "Sent" and "Drafts" message lists
+- Added ability to sort messages by "Size"
+- Added ability to rename folders (Feature #1326396)
+- Added 'protect_default_folders' option to main.inc.php to prevent renames/deletes/unsubscribes of default folders
+- Corrected 5 typos of "INSTLL" to "INSTALL" in program/include/main.inc
+
+
+2006/06/25
+----------
+- Changed behavior to include host-specific configuration (Bug #1483849)
+- Assume ISO-8859-1 encoding of mail messages by default (Patch #1483839)
+- Fixed spell checker to work with the new URL at google.com
+- Some memory and security optimizations sendmail.inc
+- Updated UGRADING description
+
+
+2006/06/19
+----------
+- Added Drafts support (Feature #1326839) (richs)
+
+
+2006/06/02
+----------
+- Updated Estonian localization and moved from ee to et
+- Added Bulgarian localization
+
+
+2006/05/25
+----------
+- Finalized GoogieSpell integration
+
+
+2006/05/18
+----------
+- Added Arabic and Armenian localizations
+- Updated Russian localization
+- Removed MDB2 classes from repository. Install them seperately if used.
+- Updated MDB2 wrapper class contributed by Lukas Kahwe Smith
+- Allow & in e-mail addresses
+
+
+2006/05/05
+----------
+- Fixed typos in function rcube_button() (Bugs #1473198 and #1473201)
+- Check for zlib.output_compression before using ob_gzhandler (Bug #1471069)
+- Casting date parts in iil_StrToTime() to avoid warnings (Bug #1482140)
+- Corrected INSTALL description (Bug #1476106)
+- Added charset to javascript HTTP headers
+- Fixed Opera bug with CC and BCC fields (Bug #1474576)
+- Changed login page title regarding product name (Bug #1476413)
+- Pimped search function
+- Applied attachment viewing/forwarding patches by Andrew Fladmark
+- Applied prev/next patch by Leonard Bouchet
+- Applied patches by Mark Bucciarelli
+- Applied patch for requesting receipts by Salvatore Ansani
+- Integrated GoogieSpell as suggested by phil (styling is not perfect yet, localization is missing)
+
+
+2006/04/13
+----------
+- Added Slovenian localization
+- Updated Portuguese localization
+- Fixed parent.location problem for compose-links
+- Added sort order saving patch by Jacob Brunson
+- Added gzip compression support
+
+
+2006/04/02
+----------
+- Added Lithuanian localization
+- Improved search function
+- Added version string as template object
+- Load host-specific configuration file (see config/main.inc.php)
+- New config parameter adding domain to user names for login
+- Strip tags on _auth, _action, _task parameters
+- Corrected labels for next/previous page buttons in address book 
+
+
+2006/03/23
+----------
+- Auto-detect mail header delimiters
+- Regard daylight savings
+- Localized quota display
+- Started implementing search function
+
+
+2006/03/20
+----------
+- Avoid error message when saving an unchanged identity (Bug #1429510)
+- Fixed hard-coded cols selection for sent folder (Bug #1354586)
+- Enable some HTML links for use with "open in new window" or "save target"
+- Check meta-key instead of ctrl on Macs
+- Ignore double clicks when holding down a modifier key
+- Fixed reloading of the login page
+- Fixed typo in compose template (Bug #1446852)
+- Added compose button to message read step (Request #1433288)
+- New config parameter for persistent database connections (Bug #1431817)
+
+
+2006/03/14
+----------
+- Don't remove internal HTML tags in plaintext messages
+- Improved error handling in DB connection failure
+
+
+2006/02/22
+----------
+- Updated localizations
+- Fixed bug #1435989
+
+
+2006/02/19
+----------
+- Updated localizations
+- Applied patch of Anders Karlsson
+- Applied patch of Jacob Brunson
+- Applied patch for virtuser_query by Robin Elfrink
+- Added support for References header (patch by Auke)
+- Added support for mbstring module by Tadashi Jokagi
+- Added function for automatic remove of slashes on GET and POST vars
+  if magic_quotes is enabled
+
+
+2006/02/05
+----------
+- Added Slovak, Hungarian, Bosnian and Croation translation
+- Fixed bug when inserting signatures with !?&
+- Chopping message headers before inserting into the message cache table
+  (to avoid bugs in Postgres)
+- Allow one-char domains in e-mail addresses
+- Make product name in page title configurable
+- Make username available as skin object
+- Added session_write_close() in rcube_db class destructor to avoid problems
+  in PHP 5.0.5
+- Use move_uploaded_file() instead of copy() for a more secure handling of
+  uploaded attachments
+- Additional config parameter to show/hide deleted messages
+- Added periodic request for checking new mails (Request #1307821)
+- Added EXPUNGE command
+- Optimized loading time for mail interface
+- Changed to full UTF-8 support
+- Additional timezones (Patch #1389912)
+- Added LDAP public search (experimental)
+- Applied patch for correct ctrl/shift behavior for message selection (Bug #1326364)
+- Casting to strings when adding empty headers to message cache (Bug #1406026)
+- Skip sender-address as recipient when Reply-to-all
+- Fixes in utf8-class
+- Added patch for Quota display by Aury Fink Filho <nuny@aury.com.br>
+- Added garbage collector for message cache
+- Added patches for BCC headers
+
+
+2005/12/16
+----------
+- Added Turkish and Simplified Chinese translation
+- Use virtusertable to resolve e-mail addresses at login
+- Configurable mail_domain used to compose correct e-mail addresses
+  on first login
+
+
+2005/12/03
+----------
+- Added Finnish, Romanian, Polish, Czech, British, Norwegian, Greek, Russian,
+  Estonian and Chinese translation
+- Get IMAP server capabilities in array
+- Check for NAMESPACE capability before sending command
+- Set default user language from config 'locale_string'
+- Added sorting patch for message list
+- Make default sort col/order configurable
+- Fixed XSS in address book and identities
+- Added more XSS protection (Bug #1308236)
+- Added tab indexes for compose form
+- Added 'changed' col to contacts table
+- Support for 160-bit session hashes
+- Added input check for contacts and identities (Patch #1346523)
+- Added messages/warning to compose step (Patch #1323895)
+- Added favicon to the default skin
+- Fixed Bug #1334337 as far as possible
+- Added Reply-To-All functionality (Request #1326395, Patch #1349777)
+- Redesign of client side AJAX code (enable multi threading)
+- Added keep-alive signal every minute
+- Make logs dir configurable
+- Added support for SMTPS
+- Decode attachment file names
+- Make delimiter for message headers configurable
+- Add generic footer to sent messages
+- Choose the rigt identity when replying
+- Remove signature when replying (Request #1333167)
+- Signatures for each identity
+- Select charset when composing message
+- Complete re-design of the caching mechanism
+
+
+2005/08/11
+----------
+- Write list header to client even if list is empty
+- Add functions "select all", "select none" to message list
+- Improved filter for HTML messages to remove potentially malicious tags
+  (script, iframe, object) and event handlers.
+- Buttons for next/previous message in view mode
+- Add new created contact to list and show confirmation status
+- Added folder management (subscribe/create/delete)
+- Log message sending (SMTP log)
+- Grant access for Camino browser
+- Added German translation
+
+
+2005/10/20
+----------
+- Added Swedish, Latvian, Portuguese and Catalan translation
+- Make SMTP auth method configurable
+- Make mailboxlist scrollable (Bug #1326372)
+- Fixed SSL support
+- Improved support for Courier IMAP (root folder and delimiter issues)
+- Moved taskbar from bottom to top
+- Added 'session_lifetime' parameter
+- Fixed wrong unread count when deleting message (Bug #1332434)
+- Srip tags when creating a new folder (Bug #1332084)
+- Translate HTML tags in message headers (Bug #1330134)
+- Correction in German translation (Bug #1329434)
+- Display folder names with special chars correctly (Bug #1330157)
+
+
+2005/10/07
+----------
+- Added French, Italian, Spanish, Danish, Dutch translation
+- Clarified license (Bug #1305966)
+- Fixed PHP warnings (Bug #1299403)
+- Fixed english translation (Bug #1295406)
+- Fixed bug #1290833: Last character of email not seen
+- Fixed bug #1292199 when creating new user
+- Allow more browsers (Bug #1285101)
+- Added setting for showing pretty dates
+- Added support for SQLite database
+- Make use of message caching configurable
+- Also add attachments when forwarding a message
+- Long folder names will not flow over message list (Bug #1267232)
+- Show nested mailboxes hieracically
+- Enable IMAPS by host
+
+
+2005/08/20
+----------
+- Improved cacheing of mailbox messagecount
+- Fixed javascript bug when creating a new message folder
+- Fixed javascript bugs #1260990 and #1260992: folder selection
+- Make Trash folder configurable
+- Auto create folders Inbox, Sent and Trash (if configured)
+- Support for IMAP root folder
+- Added support fot text/enriched messages
+- Make list of special mailboxes configurable
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..0c7e7cb
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,108 @@
+
+REQUIREMENTS
+============
+
+* The Apache Webserver
+* .htaccess support allowing overrides for DirectoryIndex
+* PHP Version 4.3.1 or greater
+* PCRE (perl compatible regular expression) installed with PHP
+* php.ini options:
+   - error_reporting E_ALL & ~E_NOTICE (or lower)
+   - file_uploads on (for attachment upload features)
+   - memory_limit (increase as suitable to support large attachments)
+* A MySQL or PostgreSQL database engine or the SQLite extension for PHP
+* A database with permission to create tables
+
+
+INSTALLATION
+============
+
+1. Decompress and put this folder somewhere inside your document root
+2. Make sure that the following directories (and the files within)
+   are writable by the webserver
+   - /temp
+   - /logs
+3. Create a new database and a database user for RoundCube (see DATABASE SETUP)
+4. Create database tables using the queries in file 'SQL/*.initial.sql'
+   (* stands for your database type)
+5. Rename the files config/*.inc.php.dist to config/*.inc.php
+6. Modify the files in config/* to suit your local environment
+7. Done!
+
+
+DATABASE SETUP
+==============
+
+* MySQL 4.0.x
+-------------
+Setting up the mysql database can be done by creating an empty database,
+importing the table layout and granting the proper permissions to the
+roundcube user. Here is an example of that procedure:
+
+# mysql
+> CREATE DATABASE 'roundcubemail';
+> GRANT ALL PRIVILEGES ON roundcubemail.* TO roundcube@localhost
+        IDENTIFIED BY 'password';
+> quit
+# mysql roundcubemail < SQL/mysql.initial.sql
+
+
+* MySQL 4.1.x/5.x
+-----------------
+For MySQL version 4.1 and up, it's recommended to create the database for
+RoundCube with utf-8 charset. Here's an example of the init procedure:
+
+# mysql
+> CREATE DATABASE 'roundcubemail' DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+> GRANT ALL PRIVILEGES ON roundcubemail.* TO roundcube@localhost
+        IDENTIFIED BY 'password';
+> quit
+
+# mysql roundcubemail < SQL/mysql5.initial.sql
+
+
+* SQLite
+--------
+Sqlite requires specifically php5 (sqlite in php4 currently doesn't
+work with roundcube), and you need sqlite 2 (preferably 2.8) to setup
+the sqlite db (sqlite 3.x also doesn't work at the moment). Here is
+an example how you can setup the sqlite.db for roundcube:
+
+# sqlite -init SQL/sqlite.initial.sql sqlite.db
+
+Make sure your configuration points to the sqlite.db file and that the
+webserver can write to the file.
+
+
+* PostgreSQL
+------------
+To use RoundCube with PostgreSQL support you have to follow the next
+simple steps, which have to be done with the postgres system user (or
+which ever is the database superuser):
+
+$ createuser roundcubemail
+$ createdb -O roundcubemail roundcubemail
+$ psql roundcubemail
+
+roundcubemail =# ALTER USER roundcube WITH PASSWORD 'the_new_password';
+roundcubemail =# \c - roundcubemail
+roundcubemail => \i SQL/postgres.initial.sql
+
+All this has been tested with PostgreSQL 8.0.x and 7.4.x. Older
+versions don't have a -O option for the createdb, so if you are
+using that version you'll have to change ownership of the DB later.
+
+
+CONFIGURATION
+=============
+
+Change the files in config/* according your to environment and your needs.
+Details about the config paramaters can be found in the config files.
+
+
+UPGRADING
+=========
+If you already have a previous version of RoundCube installed,
+please refer to the instructions in UPGRADING guide.
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..7ade7a6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,281 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e8e065e
--- /dev/null
+++ b/README
@@ -0,0 +1,82 @@
+RoundCube Webmail (http://roundcube.net)
+
+
+ATTENTION
+---------
+This is just a snapshot of the current CVS repository and is NOT A STABLE
+version of RoundCube. There have been major changes since the latest release
+so please read the update instructions carefully. It's not recommended to
+replace an existing installation of RoundCube with this version. Also using
+a separate database or this installation is highly recommended.
+
+
+Introduction:
+-------------
+RoundCube Webmail is a browser-based multilingual IMAP client with an
+application-like user interface. It provides full functionality you expect from
+an e-mail client, including MIME support, address book, folder manipulation and
+message filters. RoundCube Webmail is written in PHP and requires the MySQL
+database. The user interface is fully skinnable using XHTML and CSS 2.
+
+This project is meant to be a modern webmail solution which is easy to
+install/configure and that runs on a standard PHP plus MySQL or Postgres
+configuration. It includes open-source classes/libraries like PEAR
+(http://pear.php.net) and the IMAP wrapper from IlohaMail
+(http://www.ilohamail.org).
+
+The current development skin uses icons designed by Stephen Horlander and Kevin 
+Gerich for Mozilla.org.
+
+
+How it works:
+-------------
+The main authority for the RoundCube access is the IMAP server. If
+'auto_create_user' is set to TRUE in config/main.inc.php a new record in the
+user table will be created once the IMAP login succeeded. This user record does
+not store a password, it's just used to assign identities, contacts and cache
+records. If you have 'auto_create_user' set to FALSE only IMAP logins which
+already have a corresponding entry in the user's table (username and hostname)
+will be allowed.
+
+
+Code Layout:
+------------
+
+Basic sequence (index.php):
+  - index.php -> load_gui -> parse_template
+  - authentication details in this sequence
+
+Tasks
+  - index.php limits tasks to set list
+  - can see task in roundcube link when you mouse over it
+  - task templates stored in skins/default/templates
+  - templates "roundcube:" tokens that get replaced in parse_template
+
+program/include/rcube_shared.inc
+  - defines rcube_html_page, class that lays out a roundcube web page
+  - defines form control classes
+
+
+Installation:
+-------------
+For detailed instructions on how to install RoundCube webmail on your server,
+please refer to the INSTALL document in the same directory as this document.
+
+
+Licensing: 
+----------
+This product is distributed under the GPL. Please read through the file
+LICENSE for more information about our license.
+
+
+Contact:
+--------
+For any bug reports or feature requests please refer to the tracking system
+at sourceforge.net (http://sourceforge.net/tracker/?group_id=139281) or 
+subscribe to our mailing list. See http://www.roundcube.net/?p=mailinglists
+for details.
+
+You're always welcome to send a message to the project admin:
+roundcube@gmail.com
+
+
diff --git a/SQL/mysql.initial.sql b/SQL/mysql.initial.sql
new file mode 100644 (file)
index 0000000..7546e52
--- /dev/null
@@ -0,0 +1,127 @@
+-- RoundCube Webmail initial database structure
+-- Version 0.1beta2
+-- 
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `cache`
+-- 
+
+CREATE TABLE `cache` (
+  `cache_id` int(10) unsigned NOT NULL auto_increment,
+  `user_id` int(10) unsigned NOT NULL default '0',
+  `session_id` varchar(40) default NULL,
+  `cache_key` varchar(128) NOT NULL default '',
+  `created` datetime NOT NULL default '0000-00-00 00:00:00',
+  `data` longtext NOT NULL,
+  PRIMARY KEY  (`cache_id`),
+  KEY `user_id` (`user_id`),
+  KEY `cache_key` (`cache_key`),
+  KEY `session_id` (`session_id`)
+);
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `contacts`
+-- 
+
+CREATE TABLE `contacts` (
+  `contact_id` int(10) unsigned NOT NULL auto_increment,
+  `user_id` int(10) unsigned NOT NULL default '0',
+  `changed` datetime NOT NULL default '0000-00-00 00:00:00',
+  `del` tinyint(1) NOT NULL default '0',
+  `name` varchar(128) NOT NULL default '',
+  `email` varchar(128) NOT NULL default '',
+  `firstname` varchar(128) NOT NULL default '',
+  `surname` varchar(128) NOT NULL default '',
+  `vcard` text NOT NULL,
+  PRIMARY KEY  (`contact_id`),
+  KEY `user_id` (`user_id`)
+);
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `identities`
+-- 
+
+CREATE TABLE `identities` (
+  `identity_id` int(10) unsigned NOT NULL auto_increment,
+  `user_id` int(10) unsigned NOT NULL default '0',
+  `del` tinyint(1) NOT NULL default '0',
+  `standard` tinyint(1) NOT NULL default '0',
+  `name` varchar(128) NOT NULL default '',
+  `organization` varchar(128) NOT NULL default '',
+  `email` varchar(128) NOT NULL default '',
+  `reply-to` varchar(128) NOT NULL default '',
+  `bcc` varchar(128) NOT NULL default '',
+  `signature` text NOT NULL,
+  PRIMARY KEY  (`identity_id`),
+  KEY `user_id` (`user_id`)
+);
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `session`
+-- 
+
+CREATE TABLE `session` (
+  `sess_id` varchar(40) NOT NULL default '',
+  `created` datetime NOT NULL default '0000-00-00 00:00:00',
+  `changed` datetime NOT NULL default '0000-00-00 00:00:00',
+  `ip` VARCHAR(15) NOT NULL default '',
+  `vars` text NOT NULL,
+  PRIMARY KEY  (`sess_id`)
+);
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `users`
+-- 
+
+CREATE TABLE `users` (
+  `user_id` int(10) unsigned NOT NULL auto_increment,
+  `username` varchar(128) NOT NULL default '',
+  `mail_host` varchar(128) NOT NULL default '',
+  `alias` varchar(128) NOT NULL default '',
+  `created` datetime NOT NULL default '0000-00-00 00:00:00',
+  `last_login` datetime NOT NULL default '0000-00-00 00:00:00',
+  `language` varchar(5) NOT NULL default 'en',
+  `preferences` text NOT NULL default '',
+  PRIMARY KEY  (`user_id`)
+);
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `messages`
+-- 
+
+CREATE TABLE `messages` (
+  `message_id` int(11) unsigned NOT NULL auto_increment,
+  `user_id` int(11) unsigned NOT NULL default '0',
+  `del` tinyint(1) NOT NULL default '0',
+  `cache_key` varchar(128) NOT NULL default '',
+  `created` datetime NOT NULL default '0000-00-00 00:00:00',
+  `idx` int(11) unsigned NOT NULL default '0',
+  `uid` int(11) unsigned NOT NULL default '0',
+  `subject` varchar(255) NOT NULL default '',
+  `from` varchar(255) NOT NULL default '',
+  `to` varchar(255) NOT NULL default '',
+  `cc` varchar(255) NOT NULL default '',
+  `date` datetime NOT NULL default '0000-00-00 00:00:00',
+  `size` int(11) unsigned NOT NULL default '0',
+  `headers` text NOT NULL,
+  `body` longtext,
+  PRIMARY KEY  (`message_id`),
+  KEY `user_id` (`user_id`),
+  KEY `cache_key` (`cache_key`),
+  KEY `idx` (`idx`),
+  KEY `uid` (`uid`)
+);
+
+
diff --git a/SQL/mysql.update.sql b/SQL/mysql.update.sql
new file mode 100644 (file)
index 0000000..7fa296b
--- /dev/null
@@ -0,0 +1,57 @@
+-- RoundCube Webmail update script for MySQL databases
+-- Updates from version 0.1-20051007
+
+
+ALTER TABLE `session` ADD `ip` VARCHAR(15) NOT NULL AFTER changed;
+ALTER TABLE `users` ADD `alias` VARCHAR(128) NOT NULL AFTER mail_host;
+
+
+
+-- RoundCube Webmail update script for MySQL databases
+-- Updates from version 0.1-20051021
+
+ALTER TABLE `session` CHANGE `sess_id` `sess_id` VARCHAR(40) NOT NULL;
+
+ALTER TABLE `contacts` CHANGE `del` `del` TINYINT(1) NOT NULL;
+ALTER TABLE `contacts` ADD `changed` DATETIME NOT NULL AFTER `user_id`;
+
+UPDATE `contacts`  SET `del`=0 WHERE `del`=1;
+UPDATE `contacts`  SET `del`=1 WHERE `del`=2;
+
+ALTER TABLE `identities` CHANGE `default` `standard` TINYINT(1) NOT NULL;
+ALTER TABLE `identities` CHANGE `del` `del` TINYINT(1) NOT NULL;
+
+UPDATE `identities`  SET `del`=0 WHERE `del`=1;
+UPDATE `identities`  SET `del`=1 WHERE `del`=2;
+UPDATE `identities`  SET `standard`=0 WHERE `standard`=1;
+UPDATE `identities`  SET `standard`=1 WHERE `standard`=2;
+
+CREATE TABLE `messages` (
+  `message_id` int(11) unsigned NOT NULL auto_increment,
+  `user_id` int(11) unsigned NOT NULL default '0',
+  `del` tinyint(1) NOT NULL default '0',
+  `cache_key` varchar(128) NOT NULL default '',
+  `created` datetime NOT NULL default '0000-00-00 00:00:00',
+  `idx` int(11) unsigned NOT NULL default '0',
+  `uid` int(11) unsigned NOT NULL default '0',
+  `subject` varchar(255) NOT NULL default '',
+  `from` varchar(255) NOT NULL default '',
+  `to` varchar(255) NOT NULL default '',
+  `cc` varchar(255) NOT NULL default '',
+  `date` datetime NOT NULL default '0000-00-00 00:00:00',
+  `size` int(11) unsigned NOT NULL default '0',
+  `headers` text NOT NULL,
+  `body` longtext,
+  PRIMARY KEY  (`message_id`),
+  KEY `user_id` (`user_id`),
+  KEY `cache_key` (`cache_key`),
+  KEY `idx` (`idx`),
+  KEY `uid` (`uid`)
+) TYPE=MyISAM;
+
+
+
+-- RoundCube Webmail update script for MySQL databases
+-- Updates from version 0.1-20051216
+
+ALTER TABLE `messages` ADD `created` DATETIME NOT NULL AFTER `cache_key` ;
diff --git a/SQL/mysql5.initial.sql b/SQL/mysql5.initial.sql
new file mode 100644 (file)
index 0000000..0116468
--- /dev/null
@@ -0,0 +1,126 @@
+-- RoundCube Webmail initial database structure
+-- Version 0.1beta2
+-- 
+
+-- --------------------------------------------------------
+
+SET FOREIGN_KEY_CHECKS=0;
+
+
+-- Table structure for table `session`
+
+CREATE TABLE `session` (
+ `sess_id` varchar(40) NOT NULL,
+ `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `changed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `ip` varchar(15) NOT NULL,
+ `vars` text NOT NULL,
+ PRIMARY KEY(`sess_id`)
+) TYPE=MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+
+-- Table structure for table `users`
+
+CREATE TABLE `users` (
+ `user_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `username` varchar(128) NOT NULL,
+ `mail_host` varchar(128) NOT NULL,
+ `alias` varchar(128) NOT NULL,
+ `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `last_login` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `language` varchar(5) NOT NULL DEFAULT 'en',
+ `preferences` text NOT NULL,
+ PRIMARY KEY(`user_id`)
+) TYPE=MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+
+-- Table structure for table `messages`
+
+CREATE TABLE `messages` (
+ `message_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `del` tinyint(1) NOT NULL DEFAULT '0',
+ `cache_key` varchar(128) NOT NULL,
+ `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `idx` int(11) UNSIGNED NOT NULL DEFAULT '0',
+ `uid` int(11) UNSIGNED NOT NULL DEFAULT '0',
+ `subject` varchar(255) NOT NULL,
+ `from` varchar(255) NOT NULL,
+ `to` varchar(255) NOT NULL,
+ `cc` varchar(255) NOT NULL,
+ `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `size` int(11) UNSIGNED NOT NULL DEFAULT '0',
+ `headers` text NOT NULL,
+ `body` longtext,
+ `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY(`message_id`),
+ INDEX `cache_key`(`cache_key`),
+ INDEX `idx`(`idx`),
+ INDEX `uid`(`uid`),
+ CONSTRAINT `User_ID_FK_messages` FOREIGN KEY (`user_id`)
+   REFERENCES `users`(`user_id`)
+     ON DELETE CASCADE
+     ON UPDATE CASCADE
+) TYPE=MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+
+-- Table structure for table `cache`
+
+CREATE TABLE `cache` (
+ `cache_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `session_id` varchar(40),
+ `cache_key` varchar(128) NOT NULL,
+ `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `data` longtext NOT NULL,
+ `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY(`cache_id`),
+ INDEX `cache_key`(`cache_key`),
+ INDEX `session_id`(`session_id`),
+ CONSTRAINT `User_ID_FK_cache` FOREIGN KEY (`user_id`)
+   REFERENCES `users`(`user_id`)
+     ON DELETE CASCADE
+     ON UPDATE CASCADE
+) TYPE=MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+
+-- Table structure for table `contacts`
+
+CREATE TABLE `contacts` (
+ `contact_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `changed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+ `del` tinyint(1) NOT NULL DEFAULT '0',
+ `name` varchar(128) NOT NULL,
+ `email` varchar(128) NOT NULL,
+ `firstname` varchar(128) NOT NULL,
+ `surname` varchar(128) NOT NULL,
+ `vcard` text NOT NULL,
+ `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY(`contact_id`),
+ CONSTRAINT `User_ID_FK_contacts` FOREIGN KEY (`user_id`)
+   REFERENCES `users`(`user_id`)
+     ON DELETE CASCADE
+     ON UPDATE CASCADE
+) TYPE=MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+
+-- Table structure for table `identities`
+
+CREATE TABLE `identities` (
+ `identity_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `del` tinyint(1) NOT NULL DEFAULT '0',
+ `standard` tinyint(1) NOT NULL DEFAULT '0',
+ `name` varchar(128) NOT NULL,
+ `organization` varchar(128) NOT NULL,
+ `email` varchar(128) NOT NULL,
+ `reply-to` varchar(128) NOT NULL,
+ `bcc` varchar(128) NOT NULL,
+ `signature` text NOT NULL,
+ `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY(`identity_id`),
+ CONSTRAINT `User_ID_FK_identities` FOREIGN KEY (`user_id`)
+   REFERENCES `users`(`user_id`)
+     ON DELETE CASCADE
+     ON UPDATE CASCADE
+) TYPE=MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+
+SET FOREIGN_KEY_CHECKS=1;
\ No newline at end of file
diff --git a/SQL/postgres.initial.sql b/SQL/postgres.initial.sql
new file mode 100755 (executable)
index 0000000..55e1396
--- /dev/null
@@ -0,0 +1,168 @@
+--
+-- Sequence "user_ids"
+-- Name: user_ids; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+CREATE SEQUENCE user_ids
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+--
+-- Table "users"
+-- Name: users; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE users (
+    user_id integer DEFAULT nextval('user_ids'::text) PRIMARY KEY,
+    username character varying(128) DEFAULT ''::character varying NOT NULL,
+    mail_host character varying(128) DEFAULT ''::character varying NOT NULL,
+    alias character varying(128) DEFAULT ''::character varying NOT NULL,
+    created timestamp with time zone DEFAULT now() NOT NULL,
+    last_login timestamp with time zone DEFAULT now() NOT NULL,
+    "language" character varying(5) DEFAULT 'en'::character varying NOT NULL,
+    preferences text DEFAULT ''::text NOT NULL
+);
+
+
+  
+--
+-- Table "session"
+-- Name: session; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE "session" (
+    sess_id character varying(40) DEFAULT ''::character varying PRIMARY KEY,
+    created timestamp with time zone DEFAULT now() NOT NULL,
+    changed timestamp with time zone DEFAULT now() NOT NULL,
+    ip character varying(16) NOT NULL,
+    vars text NOT NULL
+);
+
+
+
+--
+-- Sequence "identity_ids"
+-- Name: identity_ids; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+CREATE SEQUENCE identity_ids
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+--
+-- Table "identities"
+-- Name: identities; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE identities (
+    identity_id integer DEFAULT nextval('identity_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
+    del integer DEFAULT 0 NOT NULL,
+    standard integer DEFAULT 0 NOT NULL,
+    name character varying(128) NOT NULL,
+    organization character varying(128),
+    email character varying(128) NOT NULL,
+    "reply-to" character varying(128),
+    bcc character varying(128),
+    signature text
+);
+
+
+
+--
+-- Sequence "contact_ids"
+-- Name: contact_ids; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+CREATE SEQUENCE contact_ids
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+--
+-- Table "contacts"
+-- Name: contacts; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE contacts (
+    contact_id integer DEFAULT nextval('contact_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
+    changed timestamp with time zone DEFAULT now() NOT NULL,
+    del integer DEFAULT 0 NOT NULL,
+    name character varying(128) DEFAULT ''::character varying NOT NULL,
+    email character varying(128) DEFAULT ''::character varying NOT NULL,
+    firstname character varying(128) DEFAULT ''::character varying NOT NULL,
+    surname character varying(128) DEFAULT ''::character varying NOT NULL,
+    vcard text
+);
+
+
+
+--
+-- Sequence "cache_ids"
+-- Name: cache_ids; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+CREATE SEQUENCE cache_ids
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+--
+-- Table "cache"
+-- Name: cache; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE "cache" (
+    cache_id integer DEFAULT nextval('cache_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
+    session_id character varying(40) REFERENCES "session" (sess_id),
+    cache_key character varying(128) DEFAULT ''::character varying NOT NULL,
+    created timestamp with time zone DEFAULT now() NOT NULL,
+    data text NOT NULL
+);
+
+
+
+--
+-- Sequence "message_ids"
+-- Name: message_ids; Type: SEQUENCE; Schema: public; Owner: postgres
+--
+
+CREATE SEQUENCE message_ids
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+--
+-- Table "messages"
+-- Name: messages; Type: TABLE; Schema: public; Owner: postgres
+--
+
+CREATE TABLE "messages" (
+    message_id integer DEFAULT nextval('message_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
+    del integer DEFAULT 0 NOT NULL,
+    cache_key character varying(128) DEFAULT ''::character varying NOT NULL,
+    created timestamp with time zone DEFAULT now() NOT NULL,
+    idx integer DEFAULT 0 NOT NULL,
+    uid integer DEFAULT 0 NOT NULL,
+    subject character varying(128) DEFAULT ''::character varying NOT NULL,
+    "from" character varying(128) DEFAULT ''::character varying NOT NULL,
+    "to" character varying(128) DEFAULT ''::character varying NOT NULL,
+    cc character varying(128) DEFAULT ''::character varying NOT NULL,
+    date timestamp with time zone NOT NULL,
+    size integer DEFAULT 0 NOT NULL,
+    headers text NOT NULL,
+    body text
+);
+
diff --git a/SQL/sqlite.initial.sql b/SQL/sqlite.initial.sql
new file mode 100644 (file)
index 0000000..ae16a31
--- /dev/null
@@ -0,0 +1,127 @@
+-- RoundCube Webmail initial database structure
+-- Version 0.1a
+-- 
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table `cache`
+-- 
+
+CREATE TABLE cache (
+  cache_id integer NOT NULL PRIMARY KEY,
+  user_id integer NOT NULL default 0,
+  session_id varchar(40) default NULL,
+  cache_key varchar(128) NOT NULL default '',
+  created datetime NOT NULL default '0000-00-00 00:00:00',
+  data longtext NOT NULL
+);
+
+CREATE INDEX ix_cache_user_id ON cache(user_id);
+CREATE INDEX ix_cache_cache_key ON cache(cache_key);
+CREATE INDEX ix_cache_session_id ON cache(session_id);
+
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table contacts
+-- 
+
+CREATE TABLE contacts (
+  contact_id integer NOT NULL PRIMARY KEY,
+  user_id integer NOT NULL default '0',
+  changed datetime NOT NULL default '0000-00-00 00:00:00',
+  del tinyint NOT NULL default '0',
+  name varchar(128) NOT NULL default '',
+  email varchar(128) NOT NULL default '',
+  firstname varchar(128) NOT NULL default '',
+  surname varchar(128) NOT NULL default '',
+  vcard text NOT NULL default ''
+);
+
+CREATE INDEX ix_contacts_user_id ON contacts(user_id);
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table identities
+-- 
+
+CREATE TABLE identities (
+  identity_id integer NOT NULL PRIMARY KEY,
+  user_id integer NOT NULL default '0',
+  del tinyint NOT NULL default '0',
+  standard tinyint NOT NULL default '0',
+  name varchar(128) NOT NULL default '',
+  organization varchar(128) default '',
+  email varchar(128) NOT NULL default '',
+  "reply-to" varchar(128) NOT NULL default '',
+  bcc varchar(128) NOT NULL default '',
+  signature text NOT NULL default ''
+);
+
+CREATE INDEX ix_identities_user_id ON identities(user_id);
+
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table users
+-- 
+
+CREATE TABLE users (
+  user_id integer NOT NULL PRIMARY KEY,
+  username varchar(128) NOT NULL default '',
+  mail_host varchar(128) NOT NULL default '',
+  alias varchar(128) NOT NULL default '',
+  created datetime NOT NULL default '0000-00-00 00:00:00',
+  last_login datetime NOT NULL default '0000-00-00 00:00:00',
+  language varchar(5) NOT NULL default 'en',
+  preferences text NOT NULL default ''
+);
+
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table session
+-- 
+
+CREATE TABLE session (
+  sess_id varchar(40) NOT NULL PRIMARY KEY,
+  created datetime NOT NULL default '0000-00-00 00:00:00',
+  changed datetime NOT NULL default '0000-00-00 00:00:00',
+  ip varchar(15) NOT NULL default '',
+  vars text NOT NULL
+);
+
+
+-- --------------------------------------------------------
+
+-- 
+-- Table structure for table messages
+-- 
+
+CREATE TABLE messages (
+  message_id integer NOT NULL PRIMARY KEY,
+  user_id integer NOT NULL default '0',
+  del tinyint NOT NULL default '0',
+  cache_key varchar(128) NOT NULL default '',
+  created datetime NOT NULL default '0000-00-00 00:00:00',
+  idx integer NOT NULL default '0',
+  uid integer NOT NULL default '0',
+  subject varchar(255) NOT NULL default '',
+  "from" varchar(255) NOT NULL default '',
+  "to" varchar(255) NOT NULL default '',
+  cc varchar(255) NOT NULL default '',
+  date datetime NOT NULL default '0000-00-00 00:00:00',
+  size integer NOT NULL default '0',
+  headers text NOT NULL,
+  body text
+);
+
+CREATE INDEX ix_messages_user_id ON messages(user_id);
+CREATE INDEX ix_messages_cache_key ON messages(cache_key);
+CREATE INDEX ix_messages_idx ON messages(idx);
+CREATE INDEX ix_messages_uid ON messages(uid);
diff --git a/UPGRADING b/UPGRADING
new file mode 100644 (file)
index 0000000..bb8b36b
--- /dev/null
+++ b/UPGRADING
@@ -0,0 +1,133 @@
+UPDATE instructions
+===================
+
+Follow these instructions if upgrading from a previous version
+of RoundCube Webmail.
+
+
+from versions 0.1-alpha and 0.1-20050811
+----------------------------------------
+- replace index.php
+- replace all files in folder /program/
+- replace all files in folder /skins/default/
+- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
+- add these line to /config/main.inc.php
+  $rcmail_config['trash_mbox'] = 'Trash';
+  $rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
+  $rcmail_config['prefer_html'] = TRUE;
+  $rcmail_config['prettydate'] = TRUE;
+  $rcmail_config['smtp_port'] = 25;
+  $rcmail_config['default_port'] = 143;
+  $rcmail_config['session_lifetime'] = 20;
+  $rcmail_config['skip_deleted'] = FALSE;
+  $rcmail_config['message_sort_col'] = 'date';
+  $rcmail_config['message_sort_order'] = 'DESC';
+  $rcmail_config['log_dir'] = 'logs/';
+  $rcmail_config['temp_dir'] = 'temp/';
+  $rcmail_config['message_cache_lifetime'] = '10d';
+- replace database properties (db_type, db_host, db_user, db_pass, $d_name)
+  in /config/db.inc.php with the following line:
+  $rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
+- add these lines to /config/db.inc.php
+  $rcmail_config['db_max_length'] = 512000;
+
+
+from version 0.1-20050820
+----------------------------------------
+- replace index.php
+- replace all files in folder /program/
+- replace all files in folder /skins/default/
+- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
+- add these line to /config/main.inc.php
+  $rcmail_config['prettydate'] = TRUE;
+  $rcmail_config['smtp_port'] = 25;
+  $rcmail_config['default_port'] = 143;
+  $rcmail_config['session_lifetime'] = 20;
+  $rcmail_config['skip_deleted'] = FALSE;
+  $rcmail_config['message_sort_col'] = 'date';
+  $rcmail_config['message_sort_order'] = 'DESC';
+  $rcmail_config['log_dir'] = 'logs/';
+  $rcmail_config['temp_dir'] = 'temp/';
+  $rcmail_config['message_cache_lifetime'] = '10d';
+- replace database properties (db_type, db_host, db_user, db_pass, $d_name)
+  in /config/db.inc.php with the following line:
+  $rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
+- add these lines to /config/db.inc.php
+  $rcmail_config['db_max_length'] = 512000;
+
+
+from version 0.1-20051007
+----------------------------------------
+- replace index.php
+- replace all files in folder /program/
+- replace all files in folder /skins/default/
+- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
+- add these lines to /config/main.inc.php
+  $rcmail_config['smtp_auth_type'] = '';  // if you need to specify an auth method for SMTP
+  $rcmail_config['session_lifetime'] = 20;  // to specify the session lifetime in minutes
+  $rcmail_config['skip_deleted'] = FALSE;
+  $rcmail_config['message_sort_col'] = 'date';
+  $rcmail_config['message_sort_order'] = 'DESC';
+  $rcmail_config['log_dir'] = 'logs/';
+  $rcmail_config['temp_dir'] = 'temp/';
+  $rcmail_config['message_cache_lifetime'] = '10d';
+  $rcmail_config['drafts_mbox'] = 'Drafts';
+  $rcmail_config['product_name'] = 'RoundCube Webmail';
+  $rcmail_config['read_when_deleted'] = TRUE;
+  $rcmail_config['enable_spellcheck'] = TRUE;
+- add these lines to /config/db.inc.php
+  $rcmail_config['db_max_length'] = 512000;  
+  $rcmail_config['db_sequence_user_ids'] = 'user_ids';
+  $rcmail_config['db_sequence_identity_ids'] = 'identity_ids';
+  $rcmail_config['db_sequence_contact_ids'] = 'contact_ids';
+  $rcmail_config['db_sequence_cache_ids'] = 'cache_ids';
+  $rcmail_config['db_sequence_message_ids'] = 'message_ids';  
+  $rcmail_config['db_persistent'] = TRUE;
+
+from version 0.1-20051021
+----------------------------------------
+- replace index.php
+- replace all files in folder /program/
+- replace all files in folder /skins/default/
+- run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
+- add these lines to /config/main.inc.php
+  $rcmail_config['skip_deleted'] = FALSE;
+  $rcmail_config['message_sort_col'] = 'date';
+  $rcmail_config['message_sort_order'] = 'DESC';
+  $rcmail_config['log_dir'] = 'logs/';
+  $rcmail_config['temp_dir'] = 'temp/';
+  $rcmail_config['message_cache_lifetime'] = '10d';
+  $rcmail_config['drafts_mbox'] = 'Drafts';
+  $rcmail_config['product_name'] = 'RoundCube Webmail';
+  $rcmail_config['read_when_deleted'] = TRUE;
+  $rcmail_config['enable_spellcheck'] = TRUE;
+- add these lines to /config/db.inc.php
+  $rcmail_config['db_max_length'] = 512000;
+  $rcmail_config['db_sequence_user_ids'] = 'user_ids';
+  $rcmail_config['db_sequence_identity_ids'] = 'identity_ids';
+  $rcmail_config['db_sequence_contact_ids'] = 'contact_ids';
+  $rcmail_config['db_sequence_cache_ids'] = 'cache_ids';
+  $rcmail_config['db_sequence_message_ids'] = 'message_ids';
+  $rcmail_config['db_persistent'] = TRUE;
+  
+  
+form version 0.1-beta
+----------------------------------------
+- replace index.php
+- replace all files in folder /program/
+- replace all files in folder /skins/default/
+- add these line to /config/db.inc.php
+  $rcmail_config['db_persistent'] = TRUE;
+- add these lines to /config/main.inc.php
+  $rcmail_config['drafts_mbox'] = 'Drafts';
+  $rcmail_config['junk_mbox'] = 'Junk';
+  $rcmail_config['product_name'] = 'RoundCube Webmail';
+  $rcmail_config['read_when_deleted'] = TRUE;
+  $rcmail_config['enable_spellcheck'] = TRUE;
+  $rcmail_config['protect_default_folders'] = TRUE;
+- replace the following line from /config/main.inc.php
+   @include($_SERVER['HTTP_HOST'].'.inc.php');
+  with 
+   $rcmail_config['include_host_config'] = TRUE;
+  
+  
diff --git a/config/.htaccess b/config/.htaccess
new file mode 100644 (file)
index 0000000..8e6a345
--- /dev/null
@@ -0,0 +1,2 @@
+Order allow,deny
+Deny from all 
\ No newline at end of file
diff --git a/config/db.inc.php.dist b/config/db.inc.php.dist
new file mode 100644 (file)
index 0000000..f9d3b2d
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | Configuration file for database access                                |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+
+*/
+
+$rcmail_config = array();
+
+// PEAR database DSN for read/write operations
+// format is db_provider://user:password@host/databse
+// currentyl suported db_providers: mysql, sqlite
+
+$rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
+// postgres example: 'pgsql://roundcube:pass@localhost/roundcubemail';
+// sqlite example: 'sqlite://./sqlite.db?mode=0646';
+
+// PEAR database DSN for read only operations (if empty write database will be used)
+// useful for database replication
+$rcmail_config['db_dsnr'] = '';
+
+// database backend to use (only db or mdb2 are supported)
+$rcmail_config['db_backend'] = 'db';
+
+// maximum length of a query in bytes
+$rcmail_config['db_max_length'] = 512000;  // 500K
+
+// use persistent db-connections
+$rcmail_config['db_persistent'] = TRUE;
+
+
+// you can define specific table names used to store webmail data
+$rcmail_config['db_table_users'] = 'users';
+
+$rcmail_config['db_table_identities'] = 'identities';
+
+$rcmail_config['db_table_contacts'] = 'contacts';
+
+$rcmail_config['db_table_session'] = 'session';
+
+$rcmail_config['db_table_cache'] = 'cache';
+
+$rcmail_config['db_table_messages'] = 'messages';
+
+
+// you can define specific sequence names used in PostgreSQL
+$rcmail_config['db_sequence_users'] = 'user_ids';
+
+$rcmail_config['db_sequence_identities'] = 'identity_ids';
+
+$rcmail_config['db_sequence_contacts'] = 'contact_ids';
+
+$rcmail_config['db_sequence_cache'] = 'cache_ids';
+
+$rcmail_config['db_sequence_messages'] = 'message_ids';
+
+
+// end db config file
+?>
\ No newline at end of file
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
new file mode 100644 (file)
index 0000000..2f79bd8
--- /dev/null
@@ -0,0 +1,220 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | Main configuration file                                               |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+
+*/
+
+$rcmail_config = array();
+
+
+// system error reporting: 1 = log; 2 = report (not implemented yet), 4 = show, 8 = trace
+$rcmail_config['debug_level'] = 1;
+
+// enable caching of messages and mailbox data in the local database.
+// this is recommended if the IMAP server does not run on the same machine
+$rcmail_config['enable_caching'] = TRUE;
+
+// lifetime of message cache
+// possible units: s, m, h, d, w
+$rcmail_config['message_cache_lifetime'] = '10d';
+
+// automatically create a new RoundCube user when log-in the first time.
+// a new user will be created once the IMAP login succeeds.
+// set to false if only registered users can use this service
+$rcmail_config['auto_create_user'] = TRUE;
+
+// the mail host chosen to perform the log-in
+// leave blank to show a textbox at login, give a list of hosts
+// to display a pulldown menu or set one host as string.
+// To use SSL connection, enter ssl://hostname:993
+$rcmail_config['default_host'] = '';
+
+// TCP port used for IMAP connections
+$rcmail_config['default_port'] = 143;
+
+// Automatically add this domain to user names for login
+// Only for IMAP servers that require full e-mail addresses for login
+// Specify an array with 'host' => 'domain' values to support multiple hosts
+$rcmail_config['username_domain'] = '';
+
+// This domain will be used to form e-mail addresses of new users
+// Specify an array with 'host' => 'domain' values to support multiple hosts
+$rcmail_config['mail_domain'] = '';
+
+// Path to a virtuser table file to resolve user names and e-mail addresses
+$rcmail_config['virtuser_file'] = '';
+
+// Query to resolve user names and e-mail addresses from the database
+// %u will be replaced with the current username for login.
+// The query should select the user's e-mail address as first col
+$rcmail_config['virtuser_query'] = '';
+
+// use this host for sending mails.
+// to use SSL connection, set ssl://smtp.host.com
+// if left blank, the PHP mail() function is used
+$rcmail_config['smtp_server'] = '';
+
+// SMTP port (default is 25; 465 for SSL)
+$rcmail_config['smtp_port'] = 25;
+
+// SMTP username (if required) if you use %u as the username RoundCube
+// will use the current username for login
+$rcmail_config['smtp_user'] = '';
+
+// SMTP password (if required) if you use %p as the password RoundCube
+// will use the current user's password for login
+$rcmail_config['smtp_pass'] = '';
+
+// SMTP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use
+// best server supported one)
+$rcmail_config['smtp_auth_type'] = '';
+
+// Log sent messages
+$rcmail_config['smtp_log'] = TRUE;
+
+// these cols are shown in the message list
+// available cols are: subject, from, to, cc, replyto, date, size, encoding
+$rcmail_config['list_cols'] = array('subject', 'from', 'date', 'size');
+
+// relative path to the skin folder
+$rcmail_config['skin_path'] = 'skins/default/';
+
+// use this folder to store temp files (must be writebale for apache user)
+$rcmail_config['temp_dir'] = 'temp/';
+
+// use this folder to store log files (must be writebale for apache user)
+$rcmail_config['log_dir'] = 'logs/';
+
+// session lifetime in minutes
+$rcmail_config['session_lifetime'] = 10;
+
+// check client IP in session athorization
+$rcmail_config['ip_check'] = TRUE;
+
+// this key is used to encrypt the users imap password which is stored
+// in the session record (and the client cookie if remember password is enabled).
+// please provide a string of exactly 24 chars.
+$rcmail_config['des_key'] = 'rcmail-!24ByteDESkey*Str';
+
+// the default locale setting
+$rcmail_config['locale_string'] = 'en';
+
+// use this format for short date display
+$rcmail_config['date_short'] = 'D H:i';
+
+// use this format for detailed date/time formatting
+$rcmail_config['date_long'] = 'd.m.Y H:i';
+
+// add this user-agent to message headers when sending
+$rcmail_config['useragent'] = 'RoundCube Webmail/0.1b';
+
+// use this name to compose page titles
+$rcmail_config['product_name'] = 'RoundCube Webmail';
+
+// only list folders within this path
+$rcmail_config['imap_root'] = '';
+
+// store draft message is this mailbox
+// leave blank if draft messages should not be stored
+$rcmail_config['drafts_mbox'] = 'Drafts';
+
+// store spam messages in this mailbox
+$rcmail_config['junk_mbox'] = 'Junk';
+
+// store sent message is this mailbox
+// leave blank if sent messages should not be stored
+$rcmail_config['sent_mbox'] = 'Sent';
+
+// move messages to this folder when deleting them
+// leave blank if they should be deleted directly
+$rcmail_config['trash_mbox'] = 'Trash';
+
+// display these folders separately in the mailbox list.
+// these folders will automatically be created if they do not exist
+$rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
+
+// protect the default folders from renames, deletes, and subscription changes
+$rcmail_config['protect_default_folders'] = TRUE;
+
+// Set TRUE if deleted messages should not be displayed
+// This will make the application run slower
+$rcmail_config['skip_deleted'] = FALSE;
+
+// Set true to Mark deleted messages as read as well as deleted
+// False means that a message's read status is not affected by marking it as deleted
+$rcmail_config['read_when_deleted'] = TRUE;
+
+// When a Trash folder is not present and a message is deleted, flag 
+// the message for deletion rather than deleting it immediately.  Setting this to 
+// false causes deleted messages to be permanantly removed if there is no Trash folder
+$rcmail_config['flag_for_deletion'] = TRUE;
+
+// Make use of the built-in spell checker. It is based on GoogieSpell
+// which means that the message content will be sent to Google in order to check spelling
+$rcmail_config['enable_spellcheck'] = TRUE;
+
+// path to a text file which will be added to each sent message
+// paths are relative to the RoundCube root folder
+$rcmail_config['generic_message_footer'] = '';
+
+// this string is used as a delimiter for message headers when sending
+// leave empty for auto-detection
+$rcmail_config['mail_header_delimiter'] = NULL;
+
+// in order to enable public ldap search, create a config array
+// like the Verisign example below. if you would like to test, 
+// simply uncomment the Verisign example.
+/** 
+ *  example config for Verisign directory
+ *
+ *  $rcmail_config['ldap_public']['Verisign'] = array('hosts'         => array('directory.verisign.com'),
+ *                                                    'port'          => 389,
+ *                                                    'base_dn'       => '',
+ *                                                    'search_fields' => array('Email' => 'mail', 'Name' => 'cn'),
+ *                                                    'name_field'    => 'cn',
+ *                                                    'mail_field'    => 'mail',
+ *                                                    'scope'         => 'sub',
+ *                                                    'fuzzy_search'  => 0);
+ */
+
+// try to load host-specific configuration
+$rcmail_config['include_host_config'] = FALSE;
+
+
+/***** these settings can be overwritten by user's preferences *****/
+
+// show up to X items in list view
+$rcmail_config['pagesize'] = 40;
+
+// use this timezone to display date/time
+$rcmail_config['timezone'] = 1;
+
+// daylight savings are On
+$rcmail_config['dst_active'] = TRUE;
+
+// prefer displaying HTML messages
+$rcmail_config['prefer_html'] = TRUE;
+
+// show pretty dates as standard
+$rcmail_config['prettydate'] = TRUE;
+
+// default sort col
+$rcmail_config['message_sort_col'] = 'date';
+
+// default sort order
+$rcmail_config['message_sort_order'] = 'DESC';
+
+// list of configuration option names that need to be available in Javascript.
+$rcmail_config['javascript_config'] = array('read_when_deleted', 'flag_for_deletion');
+
+
+// end of config file
+?>
diff --git a/index.php b/index.php
new file mode 100644 (file)
index 0000000..35184ff
--- /dev/null
+++ b/index.php
@@ -0,0 +1,362 @@
+<?php
+/*
+ +-----------------------------------------------------------------------+
+ | RoundCube Webmail IMAP Client                                         |
+ | Version 0.1-beta2                                                     |
+ |                                                                       |
+ | Copyright (C) 2005-2006, RoundCube Dev. - Switzerland                 |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | Redistribution and use in source and binary forms, with or without    |
+ | modification, are permitted provided that the following conditions    |
+ | are met:                                                              |
+ |                                                                       |
+ | o Redistributions of source code must retain the above copyright      |
+ |   notice, this list of conditions and the following disclaimer.       |
+ | o Redistributions in binary form must reproduce the above copyright   |
+ |   notice, this list of conditions and the following disclaimer in the |
+ |   documentation and/or other materials provided with the distribution.|
+ | o The names of the authors may not be used to endorse or promote      |
+ |   products derived from this software without specific prior written  |
+ |   permission.                                                         |
+ |                                                                       |
+ | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
+ | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
+ | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+ | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
+ | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+ | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
+ | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+ | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+ | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
+ | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+ | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: index.php 429 2006-12-22 22:26:24Z thomasb $
+
+*/
+
+define('RCMAIL_VERSION', '0.1-beta2');
+
+// define global vars
+$CHARSET = 'UTF-8';
+$OUTPUT_TYPE = 'html';
+$JS_OBJECT_NAME = 'rcmail';
+$INSTALL_PATH = dirname(__FILE__);
+$MAIN_TASKS = array('mail','settings','addressbook','logout');
+
+if (empty($INSTALL_PATH))
+  $INSTALL_PATH = './';
+else
+  $INSTALL_PATH .= '/';
+
+
+// make sure path_separator is defined
+if (!defined('PATH_SEPARATOR'))
+  define('PATH_SEPARATOR', (eregi('win', PHP_OS) ? ';' : ':'));
+
+
+// RC include folders MUST be included FIRST to avoid other
+// possible not compatible libraries (i.e PEAR) to be included
+// instead the ones provided by RC
+ini_set('include_path', $INSTALL_PATH.PATH_SEPARATOR.$INSTALL_PATH.'program'.PATH_SEPARATOR.$INSTALL_PATH.'program/lib'.PATH_SEPARATOR.ini_get('include_path'));
+
+ini_set('session.name', 'sessid');
+ini_set('session.use_cookies', 1);
+ini_set('session.gc_maxlifetime', 21600);
+ini_set('session.gc_divisor', 500);
+ini_set('error_reporting', E_ALL&~E_NOTICE); 
+
+// increase maximum execution time for php scripts
+// (does not work in safe mode)
+@set_time_limit(120);
+
+// include base files
+require_once('include/rcube_shared.inc');
+require_once('include/rcube_imap.inc');
+require_once('include/bugs.inc');
+require_once('include/main.inc');
+require_once('include/cache.inc');
+require_once('PEAR.php');
+
+
+// set PEAR error handling
+// PEAR::setErrorHandling(PEAR_ERROR_TRIGGER, E_USER_NOTICE);
+
+// use gzip compression if supported
+if (function_exists('ob_gzhandler') && !ini_get('zlib.output_compression'))
+  ob_start('ob_gzhandler');
+else
+  ob_start();
+
+
+// catch some url/post parameters
+$_task = strip_quotes(get_input_value('_task', RCUBE_INPUT_GPC));
+$_action = strip_quotes(get_input_value('_action', RCUBE_INPUT_GPC));
+$_framed = (!empty($_GET['_framed']) || !empty($_POST['_framed']));
+
+// use main task if empty or invalid value
+if (empty($_task) || !in_array($_task, $MAIN_TASKS))
+  $_task = 'mail';
+
+if (!empty($_GET['_remote']))
+  $REMOTE_REQUEST = TRUE;
+
+// start session with requested task
+rcmail_startup($_task);
+
+// set session related variables
+$COMM_PATH = sprintf('./?_task=%s', $_task);
+$SESS_HIDDEN_FIELD = '';
+
+
+// add framed parameter
+if ($_framed)
+  {
+  $COMM_PATH .= '&_framed=1';
+  $SESS_HIDDEN_FIELD .= "\n".'<input type="hidden" name="_framed" value="1" />';
+  }
+
+
+// init necessary objects for GUI
+load_gui();
+
+
+// check DB connections and exit on failure
+if ($err_str = $DB->is_error())
+  {
+  raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                    'message' => $err_str), FALSE, TRUE);
+  }
+
+
+// error steps
+if ($_action=='error' && !empty($_GET['_code']))
+  {
+  raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
+  }
+
+
+// try to log in
+if ($_action=='login' && $_task=='mail')
+  {
+  $host = $_POST['_host'] ? $_POST['_host'] : $CONFIG['default_host'];
+  
+  // check if client supports cookies
+  if (empty($_COOKIE))
+    {
+    show_message("cookiesdisabled", 'warning');
+    }
+  else if (isset($_POST['_user']) && isset($_POST['_pass']) &&
+           rcmail_login(get_input_value('_user', RCUBE_INPUT_POST), $_POST['_pass'], $host))
+    {
+    // send redirect
+    header("Location: $COMM_PATH");
+    exit;
+    }
+  else
+    {
+    show_message("loginfailed", 'warning');
+    $_SESSION['user_id'] = '';
+    }
+  }
+
+// end session
+else if ($_action=='logout' && isset($_SESSION['user_id']))
+  {
+  show_message('loggedout');
+  rcmail_kill_session();
+  }
+
+// check session and auth cookie
+else if ($_action!='login' && $_SESSION['user_id'])
+  {
+  if (!rcmail_authenticate_session() ||
+      ($CONFIG['session_lifetime'] && isset($SESS_CHANGED) && $SESS_CHANGED + $CONFIG['session_lifetime']*60 < mktime()))
+    {
+    $message = show_message('sessionerror', 'error');
+    rcmail_kill_session();
+    }
+  }
+
+
+// log in to imap server
+if (!empty($_SESSION['user_id']) && $_task=='mail')
+  {
+  $conn = $IMAP->connect($_SESSION['imap_host'], $_SESSION['username'], decrypt_passwd($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl']);
+  if (!$conn)
+    {
+    show_message('imaperror', 'error');
+    $_SESSION['user_id'] = '';
+    }
+  else
+    rcmail_set_imap_prop();
+  }
+
+
+// not logged in -> set task to 'login
+if (empty($_SESSION['user_id']))
+  {
+  if ($REMOTE_REQUEST)
+    {
+    $message .= "setTimeout(\"location.href='\"+this.env.comm_path+\"'\", 2000);";
+    rcube_remote_response($message);
+    }
+  
+  $_task = 'login';
+  }
+
+
+
+// set task and action to client
+$script = sprintf("%s.set_env('task', '%s');", $JS_OBJECT_NAME, $_task);
+if (!empty($_action))
+  $script .= sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $_action);
+
+$OUTPUT->add_script($script);
+
+
+
+// not logged in -> show login page
+if (!$_SESSION['user_id'])
+  {
+  parse_template('login');
+  exit;
+  }
+
+
+// handle keep-alive signal
+if ($_action=='keep-alive')
+  {
+  rcube_remote_response('');
+  exit;
+  }
+
+
+// include task specific files
+if ($_task=='mail')
+  {
+  include_once('program/steps/mail/func.inc');
+  
+  if ($_action=='show' || $_action=='print')
+    include('program/steps/mail/show.inc');
+
+  if ($_action=='get')
+    include('program/steps/mail/get.inc');
+
+  if ($_action=='moveto' || $_action=='delete')
+    include('program/steps/mail/move_del.inc');
+
+  if ($_action=='mark')
+    include('program/steps/mail/mark.inc');
+
+  if ($_action=='viewsource')
+    include('program/steps/mail/viewsource.inc');
+
+  if ($_action=='send')
+    include('program/steps/mail/sendmail.inc');
+
+  if ($_action=='upload')
+    include('program/steps/mail/upload.inc');
+
+  if ($_action=='compose' || $_action=='remove-attachment')
+    include('program/steps/mail/compose.inc');
+
+  if ($_action=='addcontact')
+    include('program/steps/mail/addcontact.inc');
+
+  if ($_action=='expunge' || $_action=='purge')
+    include('program/steps/mail/folders.inc');
+
+  if ($_action=='check-recent')
+    include('program/steps/mail/check_recent.inc');
+
+  if ($_action=='getunread')
+    include('program/steps/mail/getunread.inc');
+    
+  if ($_action=='list' && isset($_GET['_remote']))
+    include('program/steps/mail/list.inc');
+
+   if ($_action=='search')
+     include('program/steps/mail/search.inc');
+     
+  if ($_action=='spell')
+    include('program/steps/mail/spell.inc');
+
+  if ($_action=='rss')
+    include('program/steps/mail/rss.inc');
+
+
+  // make sure the message count is refreshed
+  $IMAP->messagecount($_SESSION['mbox'], 'ALL', TRUE);
+  }
+
+
+// include task specific files
+if ($_task=='addressbook')
+  {
+  include_once('program/steps/addressbook/func.inc');
+
+  if ($_action=='save')
+    include('program/steps/addressbook/save.inc');
+  
+  if ($_action=='edit' || $_action=='add')
+    include('program/steps/addressbook/edit.inc');
+  
+  if ($_action=='delete')
+    include('program/steps/addressbook/delete.inc');
+
+  if ($_action=='show')
+    include('program/steps/addressbook/show.inc');  
+
+  if ($_action=='list' && $_GET['_remote'])
+    include('program/steps/addressbook/list.inc');
+
+  if ($_action=='ldappublicsearch')
+    include('program/steps/addressbook/ldapsearchform.inc');
+  }
+
+
+// include task specific files
+if ($_task=='settings')
+  {
+  include_once('program/steps/settings/func.inc');
+
+  if ($_action=='save-identity')
+    include('program/steps/settings/save_identity.inc');
+
+  if ($_action=='add-identity' || $_action=='edit-identity')
+    include('program/steps/settings/edit_identity.inc');
+
+  if ($_action=='delete-identity')
+    include('program/steps/settings/delete_identity.inc');
+  
+  if ($_action=='identities')
+    include('program/steps/settings/identities.inc');  
+
+  if ($_action=='save-prefs')
+    include('program/steps/settings/save_prefs.inc');  
+
+  if ($_action=='folders' || $_action=='subscribe' || $_action=='unsubscribe' ||
+      $_action=='create-folder' || $_action=='rename-folder' || $_action=='delete-folder')
+    include('program/steps/settings/manage_folders.inc');
+
+  }
+
+
+// parse main template
+parse_template($_task);
+
+
+// if we arrive here, something went wrong
+raise_error(array('code' => 404,
+                  'type' => 'php',
+                  'line' => __LINE__,
+                  'file' => __FILE__,
+                  'message' => "Invalid request"), TRUE, TRUE);
+                      
+?>
diff --git a/logs/.htaccess b/logs/.htaccess
new file mode 100644 (file)
index 0000000..8e6a345
--- /dev/null
@@ -0,0 +1,2 @@
+Order allow,deny
+Deny from all 
\ No newline at end of file
diff --git a/program/blank.gif b/program/blank.gif
new file mode 100644 (file)
index 0000000..ea83374
Binary files /dev/null and b/program/blank.gif differ
diff --git a/program/include/bugs.inc b/program/include/bugs.inc
new file mode 100644 (file)
index 0000000..f1fa72b
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/bugs.inc                                              |
+ |                                                                       |
+ | This file is part of the BQube Webmail client                         |
+ | Copyright (C) 2005, BQube Dev - Switzerland                           |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide error handling and logging functions                        |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: bugs.inc 90 2005-12-11 23:19:48Z roundcube $
+
+*/
+
+
+// throw system error and show error page
+function raise_error($arg=array(), $log=FALSE, $terminate=FALSE)
+  {
+  global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
+  
+  /* $arg keys:
+       int     code
+       string  type (php, xpath, db, imap, javascript)
+       string  message
+       sring   file
+       int     line
+  */
+
+  // report bug (if not incompatible browser)
+  if ($log && $arg['type'] && $arg['message'])
+    log_bug($arg);
+
+  // display error page and terminate script
+  if ($terminate)
+    {
+    $ERROR_CODE = $arg['code'];
+    $ERROR_MESSAGE = $arg['message'];
+    include("program/steps/error.inc");
+    exit;
+    }
+  }
+
+
+// report error
+function log_bug($arg_arr)
+  {
+  global $CONFIG, $INSTALL_PATH;
+  $program = $arg_arr['type']=='xpath' ? 'XPath' : strtoupper($arg_arr['type']);
+
+  // write error to local log file
+  if ($CONFIG['debug_level'] & 1)
+    {
+    $log_entry = sprintf("[%s] %s Error: %s in %s on line %d\n",
+                 date("d-M-Y H:i:s O", mktime()),
+                 $program,
+                 $arg_arr['message'],
+                 $arg_arr['file'],
+                 $arg_arr['line']);
+                 
+    if (empty($CONFIG['log_dir']))
+      $CONFIG['log_dir'] = $INSTALL_PATH.'logs';
+      
+    // try to open specific log file for writing
+    if ($fp = @fopen($CONFIG['log_dir'].'/errors', 'a'))
+    
+      {
+      fwrite($fp, $log_entry);
+      fclose($fp);
+      }
+    else
+      {
+      // send error to PHPs error handler
+      trigger_error($arg_arr['message']);
+      }
+    }
+
+/*
+  // resport the bug to the global bug reporting system
+  if ($CONFIG['debug_level'] & 2)
+    {
+    $delm = '%AC';
+    http_request(sprintf('http://roundcube.net/log/bug.php?_type=%s&_domain=%s&_server_ip=%s&_client_ip=%s&_useragent=%s&_url=%s%%3A//%s&_errors=%s%s%s%s%s',
+                 $arg_arr['type'],
+                $GLOBALS['HTTP_HOST'],
+                 $GLOBALS['SERVER_ADDR'],
+                 $GLOBALS['REMOTE_ADDR'],
+                 rawurlencode($GLOBALS['HTTP_USER_AGENT']),
+                        $GLOBALS['SERVER_PORT']==43 ? 'https' : 'http',
+                        $GLOBALS['HTTP_HOST'].$GLOBALS['REQUEST_URI'],
+                        $arg_arr['file'], $delm,
+                 $arg_arr['line'], $delm,
+                 rawurlencode($arg_arr['message'])));
+    }
+*/
+
+  // show error if debug_mode is on
+  if ($CONFIG['debug_level'] & 4)
+    {
+    print "<b>$program Error";
+
+    if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
+      print " in $arg_arr[file] ($arg_arr[line])";
+
+    print ":</b>&nbsp;";
+    print nl2br($arg_arr['message']);
+    print '<br />';
+    flush();
+    }
+  }
+
+
+?>
\ No newline at end of file
diff --git a/program/include/cache.inc b/program/include/cache.inc
new file mode 100644 (file)
index 0000000..0cfa67b
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/cache.inc                                             |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide access to the application cache                             |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: cache.inc 88 2005-12-03 16:54:12Z roundcube $
+
+*/
+
+
+function rcube_read_cache($key)
+  {
+  global $DB, $CACHE_KEYS;
+  
+  // query db
+  $sql_result = $DB->query("SELECT cache_id, data
+                            FROM ".get_table_name('cache')."
+                            WHERE  user_id=?
+                            AND    cache_key=?",
+                            $_SESSION['user_id'],
+                            $key);
+
+  // get cached data
+  if ($sql_arr = $DB->fetch_assoc($sql_result))
+    {
+    $data = $sql_arr['data'];
+    $CACHE_KEYS[$key] = $sql_arr['cache_id'];
+    }
+  else
+    $data = FALSE;
+
+  return $data;
+  }
+
+
+function rcube_write_cache($key, $data, $session_cache=FALSE)
+  {
+  global $DB, $CACHE_KEYS, $sess_id;
+  
+  // check if we already have a cache entry for this key
+  if (!isset($CACHE_KEYS[$key]))
+    {
+    $sql_result = $DB->query("SELECT cache_id
+                              FROM ".get_table_name('cache')."
+                              WHERE  user_id=?
+                              AND    cache_key=?",
+                              $_SESSION['user_id'],
+                              $key);
+                                     
+    if ($sql_arr = $DB->fetch_assoc($sql_result))
+      $CACHE_KEYS[$key] = $sql_arr['cache_id'];
+    else
+      $CACHE_KEYS[$key] = FALSE;
+    }
+
+  // update existing cache record
+  if ($CACHE_KEYS[$key])
+    {
+    $DB->query("UPDATE ".get_table_name('cache')."
+                SET    created=now(),
+                       data=?
+                WHERE  user_id=?
+                AND    cache_key=?",
+                $data,
+                $_SESSION['user_id'],
+                $key);
+    }
+  // add new cache record
+  else
+    {
+    $DB->query("INSERT INTO ".get_table_name('cache')."
+                (created, user_id, session_id, cache_key, data)
+                VALUES (now(), ?, ?, ?, ?)",
+                $_SESSION['user_id'],
+                $session_cache ? $sess_id : 'NULL',
+                $key,
+                $data);
+    }
+  }
+
+
+function rcube_clear_cache($key)
+  {
+  global $DB;
+
+  $DB->query("DELETE FROM ".get_table_name('cache')."
+              WHERE  user_id=?
+              AND    cache_key=?",
+              $_SESSION['user_id'],
+              $key);
+  }
+
+
+?>
\ No newline at end of file
diff --git a/program/include/main.inc b/program/include/main.inc
new file mode 100644 (file)
index 0000000..c594767
--- /dev/null
@@ -0,0 +1,1844 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/main.inc                                              |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide basic functions for the webmail package                     |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: main.inc 429 2006-12-22 22:26:24Z thomasb $
+
+*/
+
+require_once('lib/des.inc');
+require_once('lib/utf7.inc');
+require_once('lib/utf8.class.php');
+
+
+// define constannts for input reading
+define('RCUBE_INPUT_GET', 0x0101);
+define('RCUBE_INPUT_POST', 0x0102);
+define('RCUBE_INPUT_GPC', 0x0103);
+
+
+// register session and connect to server
+function rcmail_startup($task='mail')
+  {
+  global $sess_id, $sess_auth, $sess_user_lang;
+  global $CONFIG, $INSTALL_PATH, $BROWSER, $OUTPUT, $_SESSION, $IMAP, $DB, $JS_OBJECT_NAME;
+
+  // check client
+  $BROWSER = rcube_browser();
+
+  // load config file
+  include_once('config/main.inc.php');
+  $CONFIG = is_array($rcmail_config) ? $rcmail_config : array();
+  
+  // load host-specific configuration
+  rcmail_load_host_config($CONFIG);
+  
+  $CONFIG['skin_path'] = $CONFIG['skin_path'] ? unslashify($CONFIG['skin_path']) : 'skins/default';
+
+  // load db conf
+  include_once('config/db.inc.php');
+  $CONFIG = array_merge($CONFIG, $rcmail_config);
+
+  if (empty($CONFIG['log_dir']))
+    $CONFIG['log_dir'] = $INSTALL_PATH.'logs';
+  else
+    $CONFIG['log_dir'] = unslashify($CONFIG['log_dir']);
+
+  // set PHP error logging according to config
+  if ($CONFIG['debug_level'] & 1)
+    {
+    ini_set('log_errors', 1);
+    ini_set('error_log', $CONFIG['log_dir'].'/errors');
+    }
+  if ($CONFIG['debug_level'] & 4)
+    ini_set('display_errors', 1);
+  else
+    ini_set('display_errors', 0);
+
+
+  // set session garbage collecting time according to session_lifetime
+  if (!empty($CONFIG['session_lifetime']))
+    ini_set('session.gc_maxlifetime', ($CONFIG['session_lifetime']+2)*60);
+
+
+  // prepare DB connection
+  require_once('include/rcube_'.(empty($CONFIG['db_backend']) ? 'db' : $CONFIG['db_backend']).'.inc');
+  
+  $DB = new rcube_db($CONFIG['db_dsnw'], $CONFIG['db_dsnr'], $CONFIG['db_persistent']);
+  $DB->sqlite_initials = $INSTALL_PATH.'SQL/sqlite.initial.sql';
+  $DB->db_connect('w');
+    
+  // we can use the database for storing session data
+  if (!$DB->is_error())
+    include_once('include/session.inc');
+
+  // init session
+  session_start();
+  $sess_id = session_id();
+
+  // create session and set session vars
+  if (!isset($_SESSION['auth_time']))
+    {
+    $_SESSION['user_lang'] = rcube_language_prop($CONFIG['locale_string']);
+    $_SESSION['auth_time'] = mktime();
+    setcookie('sessauth', rcmail_auth_hash($sess_id, $_SESSION['auth_time']));
+    }
+
+  // set session vars global
+  $sess_user_lang = rcube_language_prop($_SESSION['user_lang']);
+
+
+  // overwrite config with user preferences
+  if (is_array($_SESSION['user_prefs']))
+    $CONFIG = array_merge($CONFIG, $_SESSION['user_prefs']);
+
+
+  // reset some session parameters when changing task
+  if ($_SESSION['task'] != $task)
+    unset($_SESSION['page']);
+
+  // set current task to session
+  $_SESSION['task'] = $task;
+
+  // create IMAP object
+  if ($task=='mail')
+    rcmail_imap_init();
+
+
+  // set localization
+  if ($CONFIG['locale_string'])
+    setlocale(LC_ALL, $CONFIG['locale_string']);
+  else if ($sess_user_lang)
+    setlocale(LC_ALL, $sess_user_lang);
+
+
+  register_shutdown_function('rcmail_shutdown');
+  }
+
+
+// load a host-specific config file if configured
+function rcmail_load_host_config(&$config)
+  {
+  $fname = NULL;
+  
+  if (is_array($config['include_host_config']))
+    $fname = $config['include_host_config'][$_SERVER['HTTP_HOST']];
+  else if (!empty($config['include_host_config']))
+    $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $_SERVER['HTTP_HOST']) . '.inc.php';
+
+   if ($fname && is_file('config/'.$fname))
+     {
+     include('config/'.$fname);
+     $config = array_merge($config, $rcmail_config);
+     }
+  }
+
+
+// create authorization hash
+function rcmail_auth_hash($sess_id, $ts)
+  {
+  global $CONFIG;
+  
+  $auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
+                         $sess_id,
+                         $ts,
+                         $CONFIG['ip_check'] ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
+                         $_SERVER['HTTP_USER_AGENT']);
+  
+  if (function_exists('sha1'))
+    return sha1($auth_string);
+  else
+    return md5($auth_string);
+  }
+
+
+// compare the auth hash sent by the client with the local session credentials
+function rcmail_authenticate_session()
+  {
+  $now = mktime();
+  $valid = ($_COOKIE['sessauth'] == rcmail_auth_hash(session_id(), $_SESSION['auth_time']));
+
+  // renew auth cookie every 5 minutes (only for GET requests)
+  if (!$valid || ($_SERVER['REQUEST_METHOD']!='POST' && $now-$_SESSION['auth_time'] > 300))
+    {
+    $_SESSION['auth_time'] = $now;
+    setcookie('sessauth', rcmail_auth_hash(session_id(), $now));
+    }
+    
+  return $valid;
+  }
+
+
+// create IMAP object and connect to server
+function rcmail_imap_init($connect=FALSE)
+  {
+  global $CONFIG, $DB, $IMAP;
+
+  $IMAP = new rcube_imap($DB);
+  $IMAP->debug_level = $CONFIG['debug_level'];
+  $IMAP->skip_deleted = $CONFIG['skip_deleted'];
+
+
+  // connect with stored session data
+  if ($connect)
+    {
+    if (!($conn = $IMAP->connect($_SESSION['imap_host'], $_SESSION['username'], decrypt_passwd($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])))
+      show_message('imaperror', 'error');
+      
+    rcmail_set_imap_prop();
+    }
+
+  // enable caching of imap data
+  if ($CONFIG['enable_caching']===TRUE)
+    $IMAP->set_caching(TRUE);
+
+  // set pagesize from config
+  if (isset($CONFIG['pagesize']))
+    $IMAP->set_pagesize($CONFIG['pagesize']);
+  }
+
+
+// set root dir and last stored mailbox
+// this must be done AFTER connecting to the server
+function rcmail_set_imap_prop()
+  {
+  global $CONFIG, $IMAP;
+
+  // set root dir from config
+  if (!empty($CONFIG['imap_root']))
+    $IMAP->set_rootdir($CONFIG['imap_root']);
+
+  if (is_array($CONFIG['default_imap_folders']))
+    $IMAP->set_default_mailboxes($CONFIG['default_imap_folders']);
+
+  if (!empty($_SESSION['mbox']))
+    $IMAP->set_mailbox($_SESSION['mbox']);
+  if (isset($_SESSION['page']))
+    $IMAP->set_page($_SESSION['page']);
+  }
+
+
+// do these things on script shutdown
+function rcmail_shutdown()
+  {
+  global $IMAP;
+  
+  if (is_object($IMAP))
+    {
+    $IMAP->close();
+    $IMAP->write_cache();
+    }
+    
+  // before closing the database connection, write session data
+  session_write_close();
+  }
+
+
+// destroy session data and remove cookie
+function rcmail_kill_session()
+  {
+  // save user preferences
+  $a_user_prefs = $_SESSION['user_prefs'];
+  if (!is_array($a_user_prefs))
+    $a_user_prefs = array();
+    
+  if ((isset($_SESSION['sort_col']) && $_SESSION['sort_col']!=$a_user_prefs['message_sort_col']) ||
+      (isset($_SESSION['sort_order']) && $_SESSION['sort_order']!=$a_user_prefs['message_sort_order']))
+    {
+    $a_user_prefs['message_sort_col'] = $_SESSION['sort_col'];
+    $a_user_prefs['message_sort_order'] = $_SESSION['sort_order'];
+    rcmail_save_user_prefs($a_user_prefs);
+    }
+
+  $_SESSION = array();
+  session_destroy();
+  }
+
+
+// return correct name for a specific database table
+function get_table_name($table)
+  {
+  global $CONFIG;
+  
+  // return table name if configured
+  $config_key = 'db_table_'.$table;
+
+  if (strlen($CONFIG[$config_key]))
+    return $CONFIG[$config_key];
+  
+  return $table;
+  }
+
+
+// return correct name for a specific database sequence
+// (used for Postres only)
+function get_sequence_name($sequence)
+  {
+  global $CONFIG;
+  
+  // return table name if configured
+  $config_key = 'db_sequence_'.$sequence;
+
+  if (strlen($CONFIG[$config_key]))
+    return $CONFIG[$config_key];
+  
+  return $table;
+  }
+
+
+// check the given string and returns language properties
+function rcube_language_prop($lang, $prop='lang')
+  {
+  global $INSTALL_PATH;
+  static $rcube_languages, $rcube_language_aliases, $rcube_charsets;
+
+  if (empty($rcube_languages))
+    @include($INSTALL_PATH.'program/localization/index.inc');
+    
+  // check if we have an alias for that language
+  if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang]))
+    $lang = $rcube_language_aliases[$lang];
+    
+  // try the first two chars
+  if (!isset($rcube_languages[$lang]) && strlen($lang)>2)
+    {
+    $lang = substr($lang, 0, 2);
+    $lang = rcube_language_prop($lang);
+    }
+
+  if (!isset($rcube_languages[$lang]))
+    $lang = 'en_US';
+
+  // language has special charset configured
+  if (isset($rcube_charsets[$lang]))
+    $charset = $rcube_charsets[$lang];
+  else
+    $charset = 'UTF-8';    
+
+
+  if ($prop=='charset')
+    return $charset;
+  else
+    return $lang;
+  }
+  
+
+// init output object for GUI and add common scripts
+function load_gui()
+  {
+  global $CONFIG, $OUTPUT, $COMM_PATH, $JS_OBJECT_NAME, $sess_user_lang;
+
+  // init output page
+  $OUTPUT = new rcube_html_page();
+  
+  // add common javascripts
+  $javascript = "var $JS_OBJECT_NAME = new rcube_webmail();\n";
+  $javascript .= "$JS_OBJECT_NAME.set_env('comm_path', '$COMM_PATH');\n";
+
+  if (isset($CONFIG['javascript_config'] )){
+    foreach ($CONFIG['javascript_config'] as $js_config_var){
+      $javascript .= "$JS_OBJECT_NAME.set_env('$js_config_var', '" . $CONFIG[$js_config_var] . "');\n";
+    }
+  }
+  
+  if (!empty($GLOBALS['_framed']))
+    $javascript .= "$JS_OBJECT_NAME.set_env('framed', true);\n";
+    
+  $OUTPUT->add_script($javascript);
+  $OUTPUT->include_script('common.js');
+  $OUTPUT->include_script('app.js');
+  $OUTPUT->scripts_path = 'program/js/';
+
+  // set locale setting
+  rcmail_set_locale($sess_user_lang);
+
+  // set user-selected charset
+  if (!empty($CONFIG['charset']))
+    $OUTPUT->set_charset($CONFIG['charset']);
+
+  // add some basic label to client
+  rcube_add_label('loading','checkingmail');
+  }
+
+
+// set localization charset based on the given language
+function rcmail_set_locale($lang)
+  {
+  global $OUTPUT, $MBSTRING, $MBSTRING_ENCODING;
+  static $s_mbstring_loaded = NULL;
+  
+  // settings for mbstring module (by Tadashi Jokagi)
+  if ($s_mbstring_loaded===NULL)
+    {
+    if ($s_mbstring_loaded = extension_loaded("mbstring"))
+      {
+      $MBSTRING = TRUE;
+      if (function_exists("mb_mbstring_encodings"))
+        $MBSTRING_ENCODING = mb_mbstring_encodings();
+      else
+        $MBSTRING_ENCODING = array("ISO-8859-1", "UTF-7", "UTF7-IMAP", "UTF-8",
+                                   "ISO-2022-JP", "EUC-JP", "EUCJP-WIN",
+                                   "SJIS", "SJIS-WIN");
+
+       $MBSTRING_ENCODING = array_map("strtoupper", $MBSTRING_ENCODING);
+       if (in_array("SJIS", $MBSTRING_ENCODING))
+         $MBSTRING_ENCODING[] = "SHIFT_JIS";
+       }
+     else
+      {
+      $MBSTRING = FALSE;
+      $MBSTRING_ENCODING = array();
+      }
+    }
+
+  if ($MBSTRING && function_exists("mb_language"))
+    {
+    if (!@mb_language(strtok($lang, "_")))
+      $MBSTRING = FALSE;   //  unsupport language
+    }
+
+  $OUTPUT->set_charset(rcube_language_prop($lang, 'charset'));
+  }
+
+
+// perfom login to the IMAP server and to the webmail service
+function rcmail_login($user, $pass, $host=NULL)
+  {
+  global $CONFIG, $IMAP, $DB, $sess_user_lang;
+  $user_id = NULL;
+  
+  if (!$host)
+    $host = $CONFIG['default_host'];
+
+   // Validate that selected host is in the list of configured hosts
+   if (is_array($CONFIG['default_host']))
+     {
+     $allowed = FALSE;
+     foreach ($CONFIG['default_host'] as $key => $host_allowed)
+       {
+       if (!is_numeric($key))
+         $host_allowed = $key;
+       if ($host == $host_allowed)
+         {
+         $allowed = TRUE;
+         break;
+         }
+       }
+     if (!$allowed)
+       return FALSE;
+     }
+   else if (!empty($CONFIG['default_host']) && $host != $CONFIG['default_host'])
+     return FALSE;
+       
+  // parse $host URL
+  $a_host = parse_url($host);
+  if ($a_host['host'])
+    {
+    $host = $a_host['host'];
+    $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? TRUE : FALSE;
+    $imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : $CONFIG['default_port']);
+    }
+  else
+    $imap_port = $CONFIG['default_port'];
+
+
+  /* Modify username with domain if required  
+     Inspired by Marco <P0L0_notspam_binware.org>
+  */
+  // Check if we need to add domain
+  if ($CONFIG['username_domain'] && !strstr($user, '@'))
+    {
+    if (is_array($CONFIG['username_domain']) && isset($CONFIG['username_domain'][$host]))
+      $user .= '@'.$CONFIG['username_domain'][$host];
+    else if (!empty($CONFIG['username_domain']))
+      $user .= '@'.$CONFIG['username_domain'];    
+    }
+
+
+  // query if user already registered
+  $sql_result = $DB->query("SELECT user_id, username, language, preferences
+                            FROM ".get_table_name('users')."
+                            WHERE  mail_host=? AND (username=? OR alias=?)",
+                            $host,
+                            $user,
+                            $user);
+
+  // user already registered -> overwrite username
+  if ($sql_arr = $DB->fetch_assoc($sql_result))
+    {
+    $user_id = $sql_arr['user_id'];
+    $user = $sql_arr['username'];
+    }
+
+  // try to resolve email address from virtuser table    
+  if (!empty($CONFIG['virtuser_file']) && strstr($user, '@'))
+    $user = rcmail_email2user($user);
+
+
+  // exit if IMAP login failed
+  if (!($imap_login  = $IMAP->connect($host, $user, $pass, $imap_port, $imap_ssl)))
+    return FALSE;
+
+  // user already registered
+  if ($user_id && !empty($sql_arr))
+    {
+    // get user prefs
+    if (strlen($sql_arr['preferences']))
+      {
+      $user_prefs = unserialize($sql_arr['preferences']);
+      $_SESSION['user_prefs'] = $user_prefs;
+      array_merge($CONFIG, $user_prefs);
+      }
+
+
+    // set user specific language
+    if (strlen($sql_arr['language']))
+      $sess_user_lang = $_SESSION['user_lang'] = $sql_arr['language'];
+      
+    // update user's record
+    $DB->query("UPDATE ".get_table_name('users')."
+                SET    last_login=now()
+                WHERE  user_id=?",
+                $user_id);
+    }
+  // create new system user
+  else if ($CONFIG['auto_create_user'])
+    {
+    $user_id = rcmail_create_user($user, $host);
+    }
+
+  if ($user_id)
+    {
+    $_SESSION['user_id']   = $user_id;
+    $_SESSION['imap_host'] = $host;
+    $_SESSION['imap_port'] = $imap_port;
+    $_SESSION['imap_ssl']  = $imap_ssl;
+    $_SESSION['username']  = $user;
+    $_SESSION['user_lang'] = $sess_user_lang;
+    $_SESSION['password']  = encrypt_passwd($pass);
+
+    // force reloading complete list of subscribed mailboxes
+    rcmail_set_imap_prop();
+    $IMAP->clear_cache('mailboxes');
+    $IMAP->create_default_folders();
+
+    return TRUE;
+    }
+
+  return FALSE;
+  }
+
+
+// create new entry in users and identities table
+function rcmail_create_user($user, $host)
+  {
+  global $DB, $CONFIG, $IMAP;
+
+  $user_email = '';
+
+  // try to resolve user in virtusertable
+  if (!empty($CONFIG['virtuser_file']) && strstr($user, '@')==FALSE)
+    $user_email = rcmail_user2email($user);
+
+  $DB->query("INSERT INTO ".get_table_name('users')."
+              (created, last_login, username, mail_host, alias, language)
+              VALUES (now(), now(), ?, ?, ?, ?)",
+              $user,
+              $host,
+              $user_email,
+                     $_SESSION['user_lang']);
+
+  if ($user_id = $DB->insert_id(get_sequence_name('users')))
+    {
+    $mail_domain = $host;
+    if (is_array($CONFIG['mail_domain']))
+      {
+      if (isset($CONFIG['mail_domain'][$host]))
+        $mail_domain = $CONFIG['mail_domain'][$host];
+      }
+    else if (!empty($CONFIG['mail_domain']))
+      $mail_domain = $CONFIG['mail_domain'];
+   
+    if ($user_email=='')
+      $user_email = strstr($user, '@') ? $user : sprintf('%s@%s', $user, $mail_domain);
+
+    $user_name = $user!=$user_email ? $user : '';
+
+    // try to resolve the e-mail address from the virtuser table
+       if (!empty($CONFIG['virtuser_query']))
+         {
+      $sql_result = $DB->query(preg_replace('/%u/', $user, $CONFIG['virtuser_query']));
+      if ($sql_arr = $DB->fetch_array($sql_result))
+        $user_email = $sql_arr[0];
+      }
+
+    // also create new identity records
+    $DB->query("INSERT INTO ".get_table_name('identities')."
+                (user_id, del, standard, name, email)
+                VALUES (?, 0, 1, ?, ?)",
+                $user_id,
+                $user_name,
+                $user_email);
+
+                       
+    // get existing mailboxes
+    $a_mailboxes = $IMAP->list_mailboxes();
+    }
+  else
+    {
+    raise_error(array('code' => 500,
+                      'type' => 'php',
+                      'line' => __LINE__,
+                      'file' => __FILE__,
+                      'message' => "Failed to create new user"), TRUE, FALSE);
+    }
+    
+  return $user_id;
+  }
+
+
+// load virtuser table in array
+function rcmail_getvirtualfile()
+  {
+  global $CONFIG;
+  if (empty($CONFIG['virtuser_file']) || !is_file($CONFIG['virtuser_file']))
+    return FALSE;
+  
+  // read file 
+  $a_lines = file($CONFIG['virtuser_file']);
+  return $a_lines;
+  }
+
+
+// find matches of the given pattern in virtuser table
+function rcmail_findinvirtual($pattern)
+  {
+  $result = array();
+  $virtual = rcmail_getvirtualfile();
+  if ($virtual==FALSE)
+    return $result;
+
+  // check each line for matches
+  foreach ($virtual as $line)
+    {
+    $line = trim($line);
+    if (empty($line) || $line{0}=='#')
+      continue;
+      
+    if (eregi($pattern, $line))
+      $result[] = $line;
+    }
+
+  return $result;
+  }
+
+
+// resolve username with virtuser table
+function rcmail_email2user($email)
+  {
+  $user = $email;
+  $r = rcmail_findinvirtual("^$email");
+
+  for ($i=0; $i<count($r); $i++)
+    {
+    $data = $r[$i];
+    $arr = preg_split('/\s+/', $data);
+    if(count($arr)>0)
+      {
+      $user = trim($arr[count($arr)-1]);
+      break;
+      }
+    }
+
+  return $user;
+  }
+
+
+// resolve e-mail address with virtuser table
+function rcmail_user2email($user)
+  {
+  $email = "";
+  $r = rcmail_findinvirtual("$user$");
+
+  for ($i=0; $i<count($r); $i++)
+    {
+    $data=$r[$i];
+    $arr = preg_split('/\s+/', $data);
+    if (count($arr)>0)
+      {
+      $email = trim($arr[0]);
+      break;
+      }
+    }
+
+  return $email;
+  } 
+
+
+function rcmail_save_user_prefs($a_user_prefs)
+  {
+  global $DB, $CONFIG, $sess_user_lang;
+  
+  $DB->query("UPDATE ".get_table_name('users')."
+              SET    preferences=?,
+                     language=?
+              WHERE  user_id=?",
+              serialize($a_user_prefs),
+              $sess_user_lang,
+              $_SESSION['user_id']);
+
+  if ($DB->affected_rows())
+    {
+    $_SESSION['user_prefs'] = $a_user_prefs;  
+    $CONFIG = array_merge($CONFIG, $a_user_prefs);
+    return TRUE;
+    }
+    
+  return FALSE;
+  }
+
+
+// overwrite action variable  
+function rcmail_overwrite_action($action)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+  $GLOBALS['_action'] = $action;
+
+  $OUTPUT->add_script(sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $action));  
+  }
+
+
+function show_message($message, $type='notice', $vars=NULL)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME, $REMOTE_REQUEST;
+  
+  $framed = $GLOBALS['_framed'];
+  $command = sprintf("display_message('%s', '%s');",
+                     addslashes(rep_specialchars_output(rcube_label(array('name' => $message, 'vars' => $vars)))),
+                     $type);
+                     
+  if ($REMOTE_REQUEST)
+    return 'this.'.$command;
+  
+  else
+    $OUTPUT->add_script(sprintf("%s%s.%s\n",
+                                $framed ? sprintf('if(parent.%s)parent.', $JS_OBJECT_NAME) : '',
+                                $JS_OBJECT_NAME,
+                                $command));
+  
+  // console(rcube_label($message));
+  }
+
+
+function console($msg, $type=1)
+  {
+  if ($GLOBALS['REMOTE_REQUEST'])
+    print "// $msg\n";
+  else
+    {
+    print $msg;
+    print "\n<hr>\n";
+    }
+  }
+
+
+// encrypt IMAP password using DES encryption
+function encrypt_passwd($pass)
+  {
+  $cypher = des(get_des_key(), $pass, 1, 0, NULL);
+  return base64_encode($cypher);
+  }
+
+
+// decrypt IMAP password using DES encryption
+function decrypt_passwd($cypher)
+  {
+  $pass = des(get_des_key(), base64_decode($cypher), 0, 0, NULL);
+  return preg_replace('/\x00/', '', $pass);
+  }
+
+
+// return a 24 byte key for the DES encryption
+function get_des_key()
+  {
+  $key = !empty($GLOBALS['CONFIG']['des_key']) ? $GLOBALS['CONFIG']['des_key'] : 'rcmail?24BitPwDkeyF**ECB';
+  $len = strlen($key);
+  
+  // make sure the key is exactly 24 chars long
+  if ($len<24)
+    $key .= str_repeat('_', 24-$len);
+  else if ($len>24)
+    substr($key, 0, 24);
+  
+  return $key;
+  }
+
+
+// send correct response on a remote request
+function rcube_remote_response($js_code, $flush=FALSE)
+  {
+  global $OUTPUT, $CHARSET;
+  static $s_header_sent = FALSE;
+  
+  if (!$s_header_sent)
+    {
+    $s_header_sent = TRUE;
+    send_nocacheing_headers();
+    header('Content-Type: application/x-javascript; charset='.$CHARSET);
+    print '/** remote response ['.date('d/M/Y h:i:s O')."] **/\n";
+    }
+
+  // send response code
+  print rcube_charset_convert($js_code, $CHARSET, $OUTPUT->get_charset());
+
+  if ($flush)  // flush the output buffer
+    flush();
+  else         // terminate script
+    exit;
+  }
+
+
+// send correctly formatted response for a request posted to an iframe
+function rcube_iframe_response($js_code='')
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+
+  if (!empty($js_code))
+    $OUTPUT->add_script("if(parent.$JS_OBJECT_NAME){\n" . $js_code . "\n}");
+
+  $OUTPUT->write();
+  exit;
+  }
+
+
+// read directory program/localization/ and return a list of available languages
+function rcube_list_languages()
+  {
+  global $CONFIG, $INSTALL_PATH;
+  static $sa_languages = array();
+
+  if (!sizeof($sa_languages))
+    {
+    @include($INSTALL_PATH.'program/localization/index.inc');
+
+    if ($dh = @opendir($INSTALL_PATH.'program/localization'))
+      {
+      while (($name = readdir($dh)) !== false)
+        {
+        if ($name{0}=='.' || !is_dir($INSTALL_PATH.'program/localization/'.$name))
+          continue;
+
+        if ($label = $rcube_languages[$name])
+          $sa_languages[$name] = $label ? $label : $name;
+        }
+      closedir($dh);
+      }
+    }
+  return $sa_languages;
+  }
+
+
+// add a localized label to the client environment
+function rcube_add_label()
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+  
+  $arg_list = func_get_args();
+  foreach ($arg_list as $i => $name)
+    $OUTPUT->add_script(sprintf("%s.add_label('%s', '%s');",
+                                $JS_OBJECT_NAME,
+                                $name,
+                                rep_specialchars_output(rcube_label($name), 'js')));  
+  }
+
+
+// remove temp files of a session
+function rcmail_clear_session_temp($sess_id)
+  {
+  global $CONFIG;
+
+  $temp_dir = slashify($CONFIG['temp_dir']);
+  $cache_dir = $temp_dir.$sess_id;
+
+  if (is_dir($cache_dir))
+    {
+    clear_directory($cache_dir);
+    rmdir($cache_dir);
+    }  
+  }
+
+
+// remove all expired message cache records
+function rcmail_message_cache_gc()
+  {
+  global $DB, $CONFIG;
+  
+  // no cache lifetime configured
+  if (empty($CONFIG['message_cache_lifetime']))
+    return;
+  
+  // get target timestamp
+  $ts = get_offset_time($CONFIG['message_cache_lifetime'], -1);
+  
+  $DB->query("DELETE FROM ".get_table_name('messages')."
+             WHERE  created < ".$DB->fromunixtime($ts));
+  }
+
+
+// convert a string from one charset to another
+// this function is not complete and not tested well
+function rcube_charset_convert($str, $from, $to=NULL)
+  {
+  global $MBSTRING, $MBSTRING_ENCODING;
+
+  $from = strtoupper($from);
+  $to = $to==NULL ? strtoupper($GLOBALS['CHARSET']) : strtoupper($to);
+
+  if ($from==$to)
+    return $str;
+    
+  // convert charset using mbstring module  
+  if ($MBSTRING)
+    {
+    $to = $to=="UTF-7" ? "UTF7-IMAP" : $to;
+    $from = $from=="UTF-7" ? "UTF7-IMAP": $from;
+    
+    if (in_array($to, $MBSTRING_ENCODING) && in_array($from, $MBSTRING_ENCODING))
+      return mb_convert_encoding($str, $to, $from);
+    }
+
+  // convert charset using iconv module  
+  if (function_exists('iconv') && $from!='UTF-7' && $to!='UTF-7')
+    return iconv($from, $to, $str);
+
+  $conv = new utf8();
+
+  // convert string to UTF-8
+  if ($from=='UTF-7')
+    $str = rcube_charset_convert(UTF7DecodeString($str), 'ISO-8859-1');
+  else if ($from=='ISO-8859-1' && function_exists('utf8_encode'))
+    $str = utf8_encode($str);
+  else if ($from!='UTF-8')
+    {
+    $conv->loadCharset($from);
+    $str = $conv->strToUtf8($str);
+    }
+
+  // encode string for output
+  if ($to=='UTF-7')
+    return UTF7EncodeString($str);
+  else if ($to=='ISO-8859-1' && function_exists('utf8_decode'))
+    return utf8_decode($str);
+  else if ($to!='UTF-8')
+    {
+    $conv->loadCharset($to);
+    return $conv->utf8ToStr($str);
+    }
+
+  // return UTF-8 string
+  return $str;
+  }
+
+
+
+// replace specials characters to a specific encoding type
+function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
+  {
+  global $OUTPUT_TYPE, $OUTPUT;
+  static $html_encode_arr, $js_rep_table, $rtf_rep_table, $xml_rep_table;
+
+  if (!$enctype)
+    $enctype = $GLOBALS['OUTPUT_TYPE'];
+
+  // convert nbsps back to normal spaces if not html
+  if ($enctype!='html')
+    $str = str_replace(chr(160), ' ', $str);
+
+  // encode for plaintext
+  if ($enctype=='text')
+    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
+
+  // encode for HTML output
+  if ($enctype=='html')
+    {
+    if (!$html_encode_arr)
+      {
+      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);        
+      unset($html_encode_arr['?']);
+      unset($html_encode_arr['&']);
+      }
+
+    $ltpos = strpos($str, '<');
+    $encode_arr = $html_encode_arr;
+
+    // don't replace quotes and html tags
+    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
+      {
+      unset($encode_arr['"']);
+      unset($encode_arr['<']);
+      unset($encode_arr['>']);
+      }
+    else if ($mode=='remove')
+      $str = strip_tags($str);
+      
+    $out = strtr($str, $encode_arr);
+      
+    return $newlines ? nl2br($out) : $out;
+    }
+
+
+  if ($enctype=='url')
+    return rawurlencode($str);
+
+
+  // if the replace tables for RTF, XML and JS are not yet defined
+  if (!$js_rep_table)
+    {
+    $js_rep_table = $rtf_rep_table = $xml_rep_table = array();
+    $xml_rep_table['&'] = '&amp;';
+
+    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
+      {
+      $hex = dechex($c);
+      $rtf_rep_table[Chr($c)] = "\\'$hex";
+      $xml_rep_table[Chr($c)] = "&#$c;";
+      
+      if ($OUTPUT->get_charset()=='ISO-8859-1')
+        $js_rep_table[Chr($c)] = sprintf("\u%s%s", str_repeat('0', 4-strlen($hex)), $hex);
+      }
+
+    $js_rep_table['"'] = sprintf("\u%s%s", str_repeat('0', 4-strlen(dechex(34))), dechex(34));
+    $xml_rep_table['"'] = '&quot;';
+    }
+
+  // encode for RTF
+  if ($enctype=='xml')
+    return strtr($str, $xml_rep_table);
+
+  // encode for javascript use
+  if ($enctype=='js')
+    {
+    if ($OUTPUT->get_charset()!='UTF-8')
+      $str = rcube_charset_convert($str, $GLOBALS['CHARSET'], $OUTPUT->get_charset());
+      
+    return preg_replace(array("/\r\n/", '/"/', "/([^\\\])'/"), array('\n', '\"', "$1\'"), strtr($str, $js_rep_table));
+    }
+
+  // encode for RTF
+  if ($enctype=='rtf')
+    return preg_replace("/\r\n/", "\par ", strtr($str, $rtf_rep_table));
+
+  // no encoding given -> return original string
+  return $str;
+  }
+
+
+/**
+ * Read input value and convert it for internal use
+ * Performs stripslashes() and charset conversion if necessary
+ * 
+ * @param  string   Field name to read
+ * @param  int      Source to get value from (GPC)
+ * @param  boolean  Allow HTML tags in field value
+ * @param  string   Charset to convert into
+ * @return string   Field value or NULL if not available
+ */
+function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
+  {
+  global $OUTPUT;
+  $value = NULL;
+  
+  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
+    $value = $_GET[$fname];
+  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
+    $value = $_POST[$fname];
+  else if ($source==RCUBE_INPUT_GPC)
+    {
+    if (isset($_POST[$fname]))
+      $value = $_POST[$fname];
+    else if (isset($_GET[$fname]))
+      $value = $_GET[$fname];
+    else if (isset($_COOKIE[$fname]))
+      $value = $_COOKIE[$fname];
+    }
+  
+  // strip slashes if magic_quotes enabled
+  if ((bool)get_magic_quotes_gpc())
+    $value = stripslashes($value);
+
+  // remove HTML tags if not allowed    
+  if (!$allow_html)
+    $value = strip_tags($value);
+  
+  // convert to internal charset
+  if (is_object($OUTPUT))
+    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
+  else
+    return $value;
+  }
+
+/**
+ * Remove single and double quotes from given string
+ */
+function strip_quotes($str)
+{
+  return preg_replace('/[\'"]/', '', $str);
+}
+
+
+// ************** template parsing and gui functions **************
+
+
+// return boolean if a specific template exists
+function template_exists($name)
+  {
+  global $CONFIG, $OUTPUT;
+  $skin_path = $CONFIG['skin_path'];
+
+  // check template file
+  return is_file("$skin_path/templates/$name.html");
+  }
+
+
+// get page template an replace variable
+// similar function as used in nexImage
+function parse_template($name='main', $exit=TRUE)
+  {
+  global $CONFIG, $OUTPUT;
+  $skin_path = $CONFIG['skin_path'];
+
+  // read template file
+  $templ = '';
+  $path = "$skin_path/templates/$name.html";
+
+  if($fp = @fopen($path, 'r'))
+    {
+    $templ = fread($fp, filesize($path));
+    fclose($fp);
+    }
+  else
+    {
+    raise_error(array('code' => 500,
+                      'type' => 'php',
+                      'line' => __LINE__,
+                      'file' => __FILE__,
+                      'message' => "Error loading template for '$name'"), TRUE, TRUE);
+    return FALSE;
+    }
+
+
+  // parse for specialtags
+  $output = parse_rcube_xml($templ);
+  
+  $OUTPUT->write(trim(parse_with_globals($output)), $skin_path);
+
+  if ($exit)
+    exit;
+  }
+
+
+
+// replace all strings ($varname) with the content of the according global variable
+function parse_with_globals($input)
+  {
+  $GLOBALS['__comm_path'] = $GLOBALS['COMM_PATH'];
+  $output = preg_replace('/\$(__[a-z0-9_\-]+)/e', '$GLOBALS["\\1"]', $input);
+  return $output;
+  }
+
+
+
+function parse_rcube_xml($input)
+  {
+  $output = preg_replace('/<roundcube:([-_a-z]+)\s+([^>]+)>/Uie', "rcube_xml_command('\\1', '\\2')", $input);
+  return $output;
+  }
+
+
+function rcube_xml_command($command, $str_attrib, $add_attrib=array())
+  {
+  global $IMAP, $CONFIG, $OUTPUT;
+  
+  $command = strtolower($command);
+  $attrib = parse_attrib_string($str_attrib) + $add_attrib;
+
+  // execute command
+  switch ($command)
+    {
+    // return a button
+    case 'button':
+      if ($attrib['command'])
+        return rcube_button($attrib);
+      break;
+
+    // show a label
+    case 'label':
+      if ($attrib['name'] || $attrib['command'])
+        return rep_specialchars_output(rcube_label($attrib));
+      break;
+
+    // create a menu item
+    case 'menu':
+      if ($attrib['command'] && $attrib['group'])
+        rcube_menu($attrib);
+      break;
+
+    // include a file 
+    case 'include':
+      $path = realpath($CONFIG['skin_path'].$attrib['file']);
+      
+      if($fp = @fopen($path, 'r'))
+        {
+        $incl = fread($fp, filesize($path));
+        fclose($fp);        
+        return parse_rcube_xml($incl);
+        }
+      break;
+
+    // return code for a specific application object
+    case 'object':
+      $object = strtolower($attrib['name']);
+
+      $object_handlers = array(
+        // GENERAL
+        'loginform' => 'rcmail_login_form',
+        'username'  => 'rcmail_current_username',
+        
+        // MAIL
+        'mailboxlist' => 'rcmail_mailbox_list',
+        'message' => 'rcmail_message_container',
+        'messages' => 'rcmail_message_list',
+        'messagecountdisplay' => 'rcmail_messagecount_display',
+        'quotadisplay' => 'rcmail_quota_display',
+        'messageheaders' => 'rcmail_message_headers',
+        'messagebody' => 'rcmail_message_body',
+        'messageattachments' => 'rcmail_message_attachments',
+        'blockedobjects' => 'rcmail_remote_objects_msg',
+        'messagecontentframe' => 'rcmail_messagecontent_frame',
+        'messagepartframe' => 'rcmail_message_part_frame',
+        'messagepartcontrols' => 'rcmail_message_part_controls',
+        'composeheaders' => 'rcmail_compose_headers',
+        'composesubject' => 'rcmail_compose_subject',
+        'composebody' => 'rcmail_compose_body',
+        'composeattachmentlist' => 'rcmail_compose_attachment_list',
+        'composeattachmentform' => 'rcmail_compose_attachment_form',
+        'composeattachment' => 'rcmail_compose_attachment_field',
+        'priorityselector' => 'rcmail_priority_selector',
+        'charsetselector' => 'rcmail_charset_selector',
+        'searchform' => 'rcmail_search_form',
+        'receiptcheckbox' => 'rcmail_receipt_checkbox',
+        
+        // ADDRESS BOOK
+        'addresslist' => 'rcmail_contacts_list',
+        'addressframe' => 'rcmail_contact_frame',
+        'recordscountdisplay' => 'rcmail_rowcount_display',
+        'contactdetails' => 'rcmail_contact_details',
+        'contacteditform' => 'rcmail_contact_editform',
+        'ldappublicsearch' => 'rcmail_ldap_public_search_form',
+        'ldappublicaddresslist' => 'rcmail_ldap_public_list',
+
+        // USER SETTINGS
+        'userprefs' => 'rcmail_user_prefs_form',
+        'itentitieslist' => 'rcmail_identities_list',
+        'identityframe' => 'rcmail_identity_frame',
+        'identityform' => 'rcube_identity_form',
+        'foldersubscription' => 'rcube_subscription_form',
+        'createfolder' => 'rcube_create_folder_form',
+        'renamefolder' => 'rcube_rename_folder_form',
+        'composebody' => 'rcmail_compose_body'
+      );
+
+      
+      // execute object handler function
+      if ($object_handlers[$object] && function_exists($object_handlers[$object]))
+        return call_user_func($object_handlers[$object], $attrib);
+        
+      else if ($object=='productname')
+        {
+        $name = !empty($CONFIG['product_name']) ? $CONFIG['product_name'] : 'RoundCube Webmail';
+        return rep_specialchars_output($name, 'html', 'all');
+        }
+      else if ($object=='version')
+        {
+        return (string)RCMAIL_VERSION;
+        }
+      else if ($object=='pagetitle')
+        {
+        $task = $GLOBALS['_task'];
+        $title = !empty($CONFIG['product_name']) ? $CONFIG['product_name'].' :: ' : '';
+        
+        if ($task=='login')
+          $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $CONFIG['product_name'])));
+        else if ($task=='mail' && isset($GLOBALS['MESSAGE']['subject']))
+          $title .= $GLOBALS['MESSAGE']['subject'];
+        else if (isset($GLOBALS['PAGE_TITLE']))
+          $title .= $GLOBALS['PAGE_TITLE'];
+        else if ($task=='mail' && ($mbox_name = $IMAP->get_mailbox_name()))
+          $title .= rcube_charset_convert($mbox_name, 'UTF-7', 'UTF-8');
+        else
+          $title .= ucfirst($task);
+          
+        return rep_specialchars_output($title, 'html', 'all');
+        }
+
+      break;
+    }
+
+  return '';
+  }
+
+
+// create and register a button
+function rcube_button($attrib)
+  {
+  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $BROWSER, $COMM_PATH, $MAIN_TASKS;
+  static $sa_buttons = array();
+  static $s_button_count = 100;
+  
+  // these commands can be called directly via url
+  $a_static_commands = array('compose', 'list');
+  
+  $skin_path = $CONFIG['skin_path'];
+  
+  if (!($attrib['command'] || $attrib['name']))
+    return '';
+
+  // try to find out the button type
+  if ($attrib['type'])
+    $attrib['type'] = strtolower($attrib['type']);
+  else
+    $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $arg['imageact']) ? 'image' : 'link';
+  
+  
+  $command = $attrib['command'];
+  
+  // take the button from the stack
+  if($attrib['name'] && $sa_buttons[$attrib['name']])
+    $attrib = $sa_buttons[$attrib['name']];
+
+  // add button to button stack
+  else if($attrib['image'] || $arg['imageact'] || $attrib['imagepas'] || $attrib['class'])
+    {
+    if(!$attrib['name'])
+      $attrib['name'] = $command;
+
+    if (!$attrib['image'])
+      $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
+
+    $sa_buttons[$attrib['name']] = $attrib;
+    }
+
+  // get saved button for this command/name
+  else if ($command && $sa_buttons[$command])
+    $attrib = $sa_buttons[$command];
+
+  //else
+  //  return '';
+
+
+  // set border to 0 because of the link arround the button
+  if ($attrib['type']=='image' && !isset($attrib['border']))
+    $attrib['border'] = 0;
+    
+  if (!$attrib['id'])
+    $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
+
+  // get localized text for labels and titles
+  if ($attrib['title'])
+    $attrib['title'] = rep_specialchars_output(rcube_label($attrib['title']));
+  if ($attrib['label'])
+    $attrib['label'] = rep_specialchars_output(rcube_label($attrib['label']));
+
+  if ($attrib['alt'])
+    $attrib['alt'] = rep_specialchars_output(rcube_label($attrib['alt']));
+
+  // set title to alt attribute for IE browsers
+  if ($BROWSER['ie'] && $attrib['title'] && !$attrib['alt'])
+    {
+    $attrib['alt'] = $attrib['title'];
+    unset($attrib['title']);
+    }
+
+  // add empty alt attribute for XHTML compatibility
+  if (!isset($attrib['alt']))
+    $attrib['alt'] = '';
+
+
+  // register button in the system
+  if ($attrib['command'])
+    {
+    $OUTPUT->add_script(sprintf("%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
+                                $JS_OBJECT_NAME,
+                                $command,
+                                $attrib['id'],
+                                $attrib['type'],
+                                $attrib['imageact'] ? $skin_path.$attrib['imageact'] : $attrib['classact'],
+                                $attrib['imagesel'] ? $skin_path.$attrib['imagesel'] : $attrib['classsel'],
+                                $attrib['imageover'] ? $skin_path.$attrib['imageover'] : ''));
+
+    // make valid href to specific buttons
+    if (in_array($attrib['command'], $MAIN_TASKS))
+      $attrib['href'] = htmlentities(ereg_replace('_task=[a-z]+', '_task='.$attrib['command'], $COMM_PATH));
+    else if (in_array($attrib['command'], $a_static_commands))
+      $attrib['href'] = htmlentities($COMM_PATH.'&_action='.$attrib['command']);
+    }
+
+  // overwrite attributes
+  if (!$attrib['href'])
+    $attrib['href'] = '#';
+
+  if ($command)
+    $attrib['onclick'] = sprintf("return %s.command('%s','%s',this)", $JS_OBJECT_NAME, $command, $attrib['prop']);
+    
+  if ($command && $attrib['imageover'])
+    {
+    $attrib['onmouseover'] = sprintf("return %s.button_over('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
+    $attrib['onmouseout'] = sprintf("return %s.button_out('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
+    }
+
+  if ($command && $attrib['imagesel'])
+    {
+    $attrib['onmousedown'] = sprintf("return %s.button_sel('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
+    $attrib['onmouseup'] = sprintf("return %s.button_out('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
+    }
+
+  $out = '';
+
+  // generate image tag
+  if ($attrib['type']=='image')
+    {
+    $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'width', 'height', 'border', 'hspace', 'vspace', 'align', 'alt'));
+    $img_tag = sprintf('<img src="%%s"%s />', $attrib_str);
+    $btn_content = sprintf($img_tag, $skin_path.$attrib['image']);
+    if ($attrib['label'])
+      $btn_content .= ' '.$attrib['label'];
+    
+    $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'title');
+    }
+  else if ($attrib['type']=='link')
+    {
+    $btn_content = $attrib['label'] ? $attrib['label'] : $attrib['command'];
+    $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style');
+    }
+  else if ($attrib['type']=='input')
+    {
+    $attrib['type'] = 'button';
+    
+    if ($attrib['label'])
+      $attrib['value'] = $attrib['label'];
+      
+    $attrib_str = create_attrib_string($attrib, array('type', 'value', 'onclick', 'id', 'class', 'style'));
+    $out = sprintf('<input%s disabled />', $attrib_str);
+    }
+
+  // generate html code for button
+  if ($btn_content)
+    {
+    $attrib_str = create_attrib_string($attrib, $link_attrib);
+    $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
+    }
+
+  return $out;
+  }
+
+
+function rcube_menu($attrib)
+  {
+  
+  return '';
+  }
+
+
+
+function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
+  {
+  global $DB;
+  
+  // allow the following attributes to be added to the <table> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+  
+  $table = '<table' . $attrib_str . ">\n";
+    
+  // add table title
+  $table .= "<thead><tr>\n";
+
+  foreach ($a_show_cols as $col)
+    $table .= '<td class="'.$col.'">' . rep_specialchars_output(rcube_label($col)) . "</td>\n";
+
+  $table .= "</tr></thead>\n<tbody>\n";
+  
+  $c = 0;
+
+  if (!is_array($table_data)) 
+    {
+    while ($table_data && ($sql_arr = $DB->fetch_assoc($table_data)))
+      {
+      $zebra_class = $c%2 ? 'even' : 'odd';
+
+      $table .= sprintf('<tr id="rcmrow%d" class="contact '.$zebra_class.'">'."\n", $sql_arr[$id_col]);
+
+      // format each col
+      foreach ($a_show_cols as $col)
+        {
+        $cont = rep_specialchars_output($sql_arr[$col]);
+           $table .= '<td class="'.$col.'">' . $cont . "</td>\n";
+        }
+
+      $table .= "</tr>\n";
+      $c++;
+      }
+    }
+  else 
+    {
+    foreach ($table_data as $row_data)
+      {
+      $zebra_class = $c%2 ? 'even' : 'odd';
+
+      $table .= sprintf('<tr id="rcmrow%d" class="contact '.$zebra_class.'">'."\n", $row_data[$id_col]);
+
+      // format each col
+      foreach ($a_show_cols as $col)
+        {
+        $cont = rep_specialchars_output($row_data[$col]);
+           $table .= '<td class="'.$col.'">' . $cont . "</td>\n";
+        }
+
+      $table .= "</tr>\n";
+      $c++;
+      }
+    }
+
+  // complete message table
+  $table .= "</tbody></table>\n";
+  
+  return $table;
+  }
+
+
+
+function rcmail_get_edit_field($col, $value, $attrib, $type='text')
+  {
+  $fname = '_'.$col;
+  $attrib['name'] = $fname;
+  
+  if ($type=='checkbox')
+    {
+    $attrib['value'] = '1';
+    $input = new checkbox($attrib);
+    }
+  else if ($type=='textarea')
+    {
+    $attrib['cols'] = $attrib['size'];
+    $input = new textarea($attrib);
+    }
+  else
+    $input = new textfield($attrib);
+
+  // use value from post
+  if (!empty($_POST[$fname]))
+    $value = $_POST[$fname];
+
+  $out = $input->show($value);
+         
+  return $out;
+  }
+
+
+// compose a valid attribute string for HTML tags
+function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
+  {
+  // allow the following attributes to be added to the <iframe> tag
+  $attrib_str = '';
+  foreach ($allowed_attribs as $a)
+    if (isset($attrib[$a]))
+      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
+
+  return $attrib_str;
+  }
+
+
+// convert a HTML attribute string attributes to an associative array (name => value)
+function parse_attrib_string($str)
+  {
+  $attrib = array();
+  preg_match_all('/\s*([-_a-z]+)=["]([^"]+)["]?/i', stripslashes($str), $regs, PREG_SET_ORDER);
+
+  // convert attributes to an associative array (name => value)
+  if ($regs)
+    foreach ($regs as $attr)
+      $attrib[strtolower($attr[1])] = $attr[2];
+
+  return $attrib;
+  }
+
+
+function format_date($date, $format=NULL)
+  {
+  global $CONFIG, $sess_user_lang;
+  
+  $ts = NULL;
+  
+  if (is_numeric($date))
+    $ts = $date;
+  else if (!empty($date))
+    $ts = @strtotime($date);
+    
+  if (empty($ts))
+    return '';
+   
+  // get user's timezone
+  $tz = $CONFIG['timezone'];
+  if ($CONFIG['dst_active'])
+    $tz++;
+
+  // convert time to user's timezone
+  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
+  
+  // get current timestamp in user's timezone
+  $now = time();  // local time
+  $now -= (int)date('Z'); // make GMT time
+  $now += ($tz * 3600); // user's time
+  $now_date = getdate();
+
+  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
+  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
+
+  // define date format depending on current time  
+  if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit)
+    return sprintf('%s %s', rcube_label('today'), date('H:i', $timestamp));
+  else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit)
+    $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
+  else if (!$format)
+    $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
+
+
+  // parse format string manually in order to provide localized weekday and month names
+  // an alternative would be to convert the date() format string to fit with strftime()
+  $out = '';
+  for($i=0; $i<strlen($format); $i++)
+    {
+    if ($format{$i}=='\\')  // skip escape chars
+      continue;
+    
+    // write char "as-is"
+    if ($format{$i}==' ' || $format{$i-1}=='\\')
+      $out .= $format{$i};
+    // weekday (short)
+    else if ($format{$i}=='D')
+      $out .= rcube_label(strtolower(date('D', $timestamp)));
+    // weekday long
+    else if ($format{$i}=='l')
+      $out .= rcube_label(strtolower(date('l', $timestamp)));
+    // month name (short)
+    else if ($format{$i}=='M')
+      $out .= rcube_label(strtolower(date('M', $timestamp)));
+    // month name (long)
+    else if ($format{$i}=='F')
+      $out .= rcube_label(strtolower(date('F', $timestamp)));
+    else
+      $out .= date($format{$i}, $timestamp);
+    }
+  
+  return $out;
+  }
+
+
+// ************** functions delivering gui objects **************
+
+
+
+function rcmail_message_container($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmMessageContainer';
+
+  // allow the following attributes to be added to the <table> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
+  $out = '<div' . $attrib_str . "></div>";
+  
+  $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('message', '$attrib[id]');");
+  
+  return $out;
+  }
+
+
+// return the IMAP username of the current session
+function rcmail_current_username($attrib)
+  {
+  global $DB;
+  static $s_username;
+
+  // alread fetched  
+  if (!empty($s_username))
+    return $s_username;
+
+  // get e-mail address form default identity
+  $sql_result = $DB->query("SELECT email AS mailto
+                            FROM ".get_table_name('identities')."
+                            WHERE  user_id=?
+                            AND    standard=1
+                            AND    del<>1",
+                            $_SESSION['user_id']);
+                                   
+  if ($DB->num_rows($sql_result))
+    {
+    $sql_arr = $DB->fetch_assoc($sql_result);
+    $s_username = $sql_arr['mailto'];
+    }
+  else if (strstr($_SESSION['username'], '@'))
+    $s_username = $_SESSION['username'];
+  else
+    $s_username = $_SESSION['username'].'@'.$_SESSION['imap_host'];
+
+  return $s_username;
+  }
+
+
+// return code for the webmail login form
+function rcmail_login_form($attrib)
+  {
+  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $SESS_HIDDEN_FIELD;
+  
+  $labels = array();
+  $labels['user'] = rcube_label('username');
+  $labels['pass'] = rcube_label('password');
+  $labels['host'] = rcube_label('server');
+  
+  $input_user = new textfield(array('name' => '_user', 'id' => 'rcmloginuser', 'size' => 30));
+  $input_pass = new passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd', 'size' => 30));
+  $input_action = new hiddenfield(array('name' => '_action', 'value' => 'login'));
+    
+  $fields = array();
+  $fields['user'] = $input_user->show(get_input_value('_user', RCUBE_INPUT_POST));
+  $fields['pass'] = $input_pass->show();
+  $fields['action'] = $input_action->show();
+  
+  if (is_array($CONFIG['default_host']))
+    {
+    $select_host = new select(array('name' => '_host', 'id' => 'rcmloginhost'));
+    
+    foreach ($CONFIG['default_host'] as $key => $value)
+      $select_host->add($value, (is_numeric($key) ? $value : $key));
+      
+    $fields['host'] = $select_host->show($_POST['_host']);
+    }
+  else if (!strlen($CONFIG['default_host']))
+    {
+       $input_host = new textfield(array('name' => '_host', 'id' => 'rcmloginhost', 'size' => 30));
+       $fields['host'] = $input_host->show($_POST['_host']);
+    }
+
+  $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
+  $form_start = !strlen($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
+  $form_end = !strlen($attrib['form']) ? '</form>' : '';
+  
+  if ($fields['host'])
+    $form_host = <<<EOF
+    
+</tr><tr>
+
+<td class="title"><label for="rcmloginhost">$labels[host]</label></td>
+<td>$fields[host]</td>
+
+EOF;
+
+  $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('loginform', '$form_name');");
+  
+  $out = <<<EOF
+$form_start
+$SESS_HIDDEN_FIELD
+$fields[action]
+<table><tr>
+
+<td class="title"><label for="rcmloginuser">$labels[user]</label></td>
+<td>$fields[user]</td>
+
+</tr><tr>
+
+<td class="title"><label for="rcmloginpwd">$labels[pass]</label></td>
+<td>$fields[pass]</td>
+$form_host
+</tr></table>
+$form_end
+EOF;
+
+  return $out;
+  }
+
+
+function rcmail_charset_selector($attrib)
+  {
+  global $OUTPUT;
+  
+  // pass the following attributes to the form class
+  $field_attrib = array('name' => '_charset');
+  foreach ($attrib as $attr => $value)
+    if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex')))
+      $field_attrib[$attr] = $value;
+      
+  $charsets = array(
+    'US-ASCII'     => 'ASCII (English)',
+    'EUC-JP'       => 'EUC-JP (Japanese)',
+    'EUC-KR'       => 'EUC-KR (Korean)',
+    'BIG5'         => 'BIG5 (Chinese)',
+    'GB2312'       => 'GB2312 (Chinese)',
+    'ISO-2022-JP'  => 'ISO-2022-JP (Japanese)',
+    'ISO-8859-1'   => 'ISO-8859-1 (Latin-1)',
+    'ISO-8859-2'   => 'ISO-8895-2 (Central European)',
+    'ISO-8859-7'   => 'ISO-8859-7 (Greek)',
+    'ISO-8859-9'   => 'ISO-8859-9 (Turkish)',
+    'Windows-1251' => 'Windows-1251 (Cyrillic)',
+    'Windows-1252' => 'Windows-1252 (Western)',
+    'Windows-1255' => 'Windows-1255 (Hebrew)',
+    'Windows-1256' => 'Windows-1256 (Arabic)',
+    'Windows-1257' => 'Windows-1257 (Baltic)',
+    'UTF-8'        => 'UTF-8'
+    );
+
+  $select = new select($field_attrib);
+  $select->add(array_values($charsets), array_keys($charsets));
+  
+  $set = $_POST['_charset'] ? $_POST['_charset'] : $OUTPUT->get_charset();
+  return $select->show($set);
+  }
+
+
+/****** debugging function ********/
+
+function rcube_timer()
+  {
+  list($usec, $sec) = explode(" ", microtime());
+  return ((float)$usec + (float)$sec);
+  }
+  
+
+function rcube_print_time($timer, $label='Timer')
+  {
+  static $print_count = 0;
+  
+  $print_count++;
+  $now = rcube_timer();
+  $diff = $now-$timer;
+  
+  if (empty($label))
+    $label = 'Timer '.$print_count;
+  
+  console(sprintf("%s: %0.4f sec", $label, $diff));
+  }
+
+?>
diff --git a/program/include/rcube_db.inc b/program/include/rcube_db.inc
new file mode 100755 (executable)
index 0000000..8c2abe2
--- /dev/null
@@ -0,0 +1,557 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_db.inc                                          |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   PEAR:DB wrapper class that implements PEAR DB functions             |
+ |   See http://pear.php.net/package/DB                                  |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: David Saez Padros <david@ols.es>                              |
+ |         Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_db.inc 262 2006-06-25 09:17:07Z thomasb $
+
+*/
+
+
+/**
+ * Obtain the PEAR::DB class that is used for abstraction
+ */
+require_once('DB.php');
+
+
+/**
+ * Database independent query interface
+ *
+ * This is a wrapper for the PEAR::DB class
+ *
+ * @package    RoundCube Webmail
+ * @author     David Saez Padros <david@ols.es>
+ * @author     Thomas Bruederli <roundcube@gmail.com>
+ * @version    1.17
+ * @link       http://pear.php.net/package/DB
+ */
+class rcube_db
+  {
+  var $db_dsnw;               // DSN for write operations
+  var $db_dsnr;               // DSN for read operations
+  var $db_connected = false;  // Already connected ?
+  var $db_mode = '';          // Connection mode
+  var $db_handle = 0;         // Connection handle
+  var $db_pconn = false;      // Use persistent connections
+  var $db_error = false;
+  var $db_error_msg = '';
+
+  var $a_query_results = array('dummy');
+  var $last_res_id = 0;
+
+
+  /**
+   * Object constructor
+   *
+   * @param  string  DSN for read/write operations
+   * @param  string  Optional DSN for read only operations
+   */
+  function __construct($db_dsnw, $db_dsnr='', $pconn=false)
+    {
+    if ($db_dsnr=='')
+      $db_dsnr=$db_dsnw;
+        
+    $this->db_dsnw = $db_dsnw;
+    $this->db_dsnr = $db_dsnr;
+    $this->db_pconn = $pconn;
+        
+    $dsn_array = DB::parseDSN($db_dsnw);
+    $this->db_provider = $dsn_array['phptype'];        
+    }
+
+
+  /**
+   * PHP 4 object constructor
+   *
+   * @see  rcube_db::__construct
+   */
+  function rcube_db($db_dsnw, $db_dsnr='', $pconn=false)
+    {
+    $this->__construct($db_dsnw, $db_dsnr, $pconn);
+    }
+
+
+  /**
+   * Connect to specific database
+   *
+   * @param  string  DSN for DB connections
+   * @return object  PEAR database handle
+   * @access private
+   */
+  function dsn_connect($dsn)
+    {
+    // Use persistent connections if available
+    $dbh = DB::connect($dsn, array('persistent' => $this->db_pconn));
+        
+    if (DB::isError($dbh))
+      {
+      $this->db_error = TRUE;
+      $this->db_error_msg = $dbh->getMessage();
+
+      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                        'message' => $this->db_error_msg), TRUE, FALSE);
+                        
+      return FALSE;
+      }
+
+    else if ($this->db_provider=='sqlite')
+      {
+      $dsn_array = DB::parseDSN($dsn);
+      if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials))
+        $this->_sqlite_create_database($dbh, $this->sqlite_initials);
+      }
+        
+    return $dbh;
+    }
+
+
+  /**
+   * Connect to appropiate databse
+   * depending on the operation
+   *
+   * @param  string  Connection mode (r|w)
+   * @access public
+   */
+  function db_connect($mode)
+    {
+    $this->db_mode = $mode;
+
+    // Already connected
+    if ($this->db_connected)
+      {
+      // no replication, current connection is ok
+      if ($this->db_dsnw==$this->db_dsnr)
+        return;
+            
+      // connected to master, current connection is ok
+      if ($this->db_mode=='w')
+        return;
+
+      // Same mode, current connection is ok
+      if ($this->db_mode==$mode)
+        return;
+      }
+     
+    if ($mode=='r')
+      $dsn = $this->db_dsnr;
+    else
+      $dsn = $this->db_dsnw;
+
+    $this->db_handle = $this->dsn_connect($dsn);
+    $this->db_connected = $this->db_handle ? TRUE : FALSE;
+    }
+    
+    
+  /**
+   * Getter for error state
+   *
+   * @param  boolean  True on error
+   */
+  function is_error()
+    {
+    return $this->db_error ? $this->db_error_msg : FALSE;
+    }
+
+
+  /**
+   * Execute a SQL query
+   *
+   * @param  string  SQL query to execute
+   * @param  mixed   Values to be inserted in query
+   * @return number  Query handle identifier
+   * @access public
+   */
+  function query()
+    {
+    $params = func_get_args();
+    $query = array_shift($params);
+
+    return $this->_query($query, 0, 0, $params);
+    }
+
+
+  /**
+   * Execute a SQL query with limits
+   *
+   * @param  string  SQL query to execute
+   * @param  number  Offset for LIMIT statement
+   * @param  number  Number of rows for LIMIT statement
+   * @param  mixed   Values to be inserted in query
+   * @return number  Query handle identifier
+   * @access public
+   */
+  function limitquery()
+    {
+    $params = func_get_args();
+    $query = array_shift($params);
+    $offset = array_shift($params);
+    $numrows = array_shift($params);
+               
+    return $this->_query($query, $offset, $numrows, $params);
+    }
+
+
+  /**
+   * Execute a SQL query with limits
+   *
+   * @param  string  SQL query to execute
+   * @param  number  Offset for LIMIT statement
+   * @param  number  Number of rows for LIMIT statement
+   * @param  array   Values to be inserted in query
+   * @return number  Query handle identifier
+   * @access private
+   */
+  function _query($query, $offset, $numrows, $params)
+    {
+    // Read or write ?
+    if (strtolower(trim(substr($query,0,6)))=='select')
+      $mode='r';
+    else
+      $mode='w';
+        
+    $this->db_connect($mode);
+    
+    if (!$this->db_connected)
+      return FALSE;
+
+    if ($this->db_provider == 'sqlite')
+      $this->_sqlite_prepare();
+
+    if ($numrows || $offset)
+      $result = $this->db_handle->limitQuery($query,$offset,$numrows,$params);
+    else    
+      $result = $this->db_handle->query($query, $params);
+       
+    // add result, even if it's an error
+    return $this->_add_result($result);
+    }
+
+
+  /**
+   * Get number of rows for a SQL query
+   * If no query handle is specified, the last query will be taken as reference
+   *
+   * @param  number  Optional query handle identifier
+   * @return mixed   Number of rows or FALSE on failure
+   * @access public
+   */
+  function num_rows($res_id=NULL)
+    {
+    if (!$this->db_handle)
+      return FALSE;
+
+    if ($result = $this->_get_result($res_id))    
+      return $result->numRows();
+    else
+      return FALSE;
+    }
+
+
+  /**
+   * Get number of affected rows fort he last query
+   *
+   * @return mixed   Number of rows or FALSE on failure
+   * @access public
+   */
+  function affected_rows()
+    {
+    if (!$this->db_handle)
+      return FALSE;
+
+    return $this->db_handle->affectedRows();
+    }
+
+
+  /**
+   * Get last inserted record ID
+   * For Postgres databases, a sequence name is required
+   *
+   * @param  string  Sequence name for increment
+   * @return mixed   ID or FALSE on failure
+   * @access public
+   */
+  function insert_id($sequence = '')
+    {
+    if (!$this->db_handle || $this->db_mode=='r')
+      return FALSE;
+
+    switch($this->db_provider)
+      {
+      case 'pgsql':
+        // PostgreSQL uses sequences
+        $result = &$this->db_handle->getOne("SELECT CURRVAL('$sequence')");
+        if (DB::isError($result))
+          {
+          raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 
+                            'message' => $result->getMessage()), TRUE, FALSE);
+          }
+
+        return $result;
+                
+      case 'mysql': // This is unfortuneate
+        return mysql_insert_id($this->db_handle->connection);
+
+      case 'mysqli':
+        return mysqli_insert_id($this->db_handle->connection);
+       
+      case 'sqlite':
+        return sqlite_last_insert_rowid($this->db_handle->connection);
+
+      default:
+        die("portability issue with this database, please have the developer fix");
+      }
+    }
+
+
+  /**
+   * Get an associative array for one row
+   * If no query handle is specified, the last query will be taken as reference
+   *
+   * @param  number  Optional query handle identifier
+   * @return mixed   Array with col values or FALSE on failure
+   * @access public
+   */
+  function fetch_assoc($res_id=NULL)
+    {
+    $result = $this->_get_result($res_id);
+    return $this->_fetch_row($result, DB_FETCHMODE_ASSOC);
+    }
+
+
+  /**
+   * Get an index array for one row
+   * If no query handle is specified, the last query will be taken as reference
+   *
+   * @param  number  Optional query handle identifier
+   * @return mixed   Array with col values or FALSE on failure
+   * @access public
+   */
+  function fetch_array($res_id=NULL)
+    {
+    $result = $this->_get_result($res_id);
+    return $this->_fetch_row($result, DB_FETCHMODE_ORDERED);
+    }
+
+
+  /**
+   * Get co values for a result row
+   *
+   * @param  object  Query result handle
+   * @param  number  Fetch mode identifier
+   * @return mixed   Array with col values or FALSE on failure
+   * @access private
+   */
+  function _fetch_row($result, $mode)
+    {
+    if (DB::isError($result))
+      {
+      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                        'message' => $this->db_link->getMessage()), TRUE, FALSE);
+      return FALSE;
+      }
+                         
+    return $result->fetchRow($mode);
+    }
+    
+
+  /**
+   * Formats input so it can be safely used in a query
+   *
+   * @param  mixed   Value to quote
+   * @return string  Quoted/converted string for use in query
+   * @access public
+   */
+  function quote($input)
+    {
+    // create DB handle if not available
+    if (!$this->db_handle)
+      $this->db_connect('r');
+      
+    // escape pear identifier chars
+    $rep_chars = array('?' => '\?',
+                       '!' => '\!',
+                       '&' => '\&');
+      
+    return $this->db_handle->quoteSmart(strtr($input, $rep_chars));
+    }
+    
+
+  /**
+   * Quotes a string so it can be safely used as a table or column name
+   *
+   * @param  string  Value to quote
+   * @return string  Quoted string for use in query
+   * @deprecated     Replaced by rcube_db::quote_identifier
+   * @see            rcube_db::quote_identifier
+   * @access public
+   */
+  function quoteIdentifier($str)
+       {
+    return $this->quote_identifier($str);
+       }
+
+
+  /**
+   * Quotes a string so it can be safely used as a table or column name
+   *
+   * @param  string  Value to quote
+   * @return string  Quoted string for use in query
+   * @access public
+   */
+  function quote_identifier($str)
+    {
+    if (!$this->db_handle)
+      $this->db_connect('r');
+                       
+    return $this->db_handle->quoteIdentifier($str);
+    }
+
+
+  /**
+   * Return SQL statement to convert a field value into a unix timestamp
+   *
+   * @param  string  Field name
+   * @return string  SQL statement to use in query
+   * @access public
+   */
+  function unixtimestamp($field)
+    {
+    switch($this->db_provider)
+      {
+      case 'pgsql':
+        return "EXTRACT (EPOCH FROM $field)";
+        break;
+
+      default:
+        return "UNIX_TIMESTAMP($field)";
+      }
+    }
+
+
+  /**
+   * Return SQL statement to convert from a unix timestamp
+   *
+   * @param  string  Field name
+   * @return string  SQL statement to use in query
+   * @access public
+   */
+  function fromunixtime($timestamp)
+    {
+    switch($this->db_provider)
+      {
+      case 'mysqli':
+      case 'mysql':
+      case 'sqlite':
+        return "FROM_UNIXTIME($timestamp)";
+
+      default:
+        return date("'Y-m-d H:i:s'", $timestamp);
+      }
+    }
+
+
+  /**
+   * Adds a query result and returns a handle ID
+   *
+   * @param  object  Query handle
+   * @return mixed   Handle ID or FALE on failure
+   * @access private
+   */
+  function _add_result($res)
+    {
+    // sql error occured
+    if (DB::isError($res))
+      {
+      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                        'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE);
+      return FALSE;
+      }
+    else
+      {
+      $res_id = sizeof($this->a_query_results);
+      $this->a_query_results[$res_id] = $res;
+      $this->last_res_id = $res_id;
+      return $res_id;
+      }
+    }
+
+
+  /**
+   * Resolves a given handle ID and returns the according query handle
+   * If no ID is specified, the last ressource handle will be returned
+   *
+   * @param  number  Handle ID
+   * @return mixed   Ressource handle or FALE on failure
+   * @access private
+   */
+  function _get_result($res_id=NULL)
+    {
+    if ($res_id==NULL)
+      $res_id = $this->last_res_id;
+    
+     if ($res_id && isset($this->a_query_results[$res_id]))
+       return $this->a_query_results[$res_id];
+     else
+       return FALSE;
+    }
+
+
+  /**
+   * Create a sqlite database from a file
+   *
+   * @param  object  SQLite database handle
+   * @param  string  File path to use for DB creation
+   * @access private
+   */
+  function _sqlite_create_database($dbh, $file_name)
+    {
+    if (empty($file_name) || !is_string($file_name))
+      return;
+
+    $data = '';
+    if ($fd = fopen($file_name, 'r'))
+      {
+      $data = fread($fd, filesize($file_name));
+      fclose($fd);
+      }
+
+    if (strlen($data))
+      sqlite_exec($dbh->connection, $data);
+    }
+
+
+  /**
+   * Add some proprietary database functions to the current SQLite handle
+   * in order to make it MySQL compatible
+   *
+   * @access private
+   */
+  function _sqlite_prepare()
+    {
+    include_once('include/rcube_sqlite.inc');
+
+    // we emulate via callback some missing MySQL function
+    sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
+    sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
+    sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
+    sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");    
+    }
+
+
+  }  // end class rcube_db
+
+?>
\ No newline at end of file
diff --git a/program/include/rcube_imap.inc b/program/include/rcube_imap.inc
new file mode 100644 (file)
index 0000000..5ce8b15
--- /dev/null
@@ -0,0 +1,2228 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_imap.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   IMAP wrapper that implements the Iloha IMAP Library (IIL)           |
+ |   See http://ilohamail.org/ for details                               |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_imap.inc 293 2006-08-04 13:56:08Z thomasb $
+
+*/
+
+
+/**
+ * Obtain classes from the Iloha IMAP library
+ */
+require_once('lib/imap.inc');
+require_once('lib/mime.inc');
+require_once('lib/utf7.inc');
+
+
+/**
+ * Interface class for accessing an IMAP server
+ *
+ * This is a wrapper that implements the Iloha IMAP Library (IIL)
+ *
+ * @package    RoundCube Webmail
+ * @author     Thomas Bruederli <roundcube@gmail.com>
+ * @version    1.31
+ * @link       http://ilohamail.org
+ */
+class rcube_imap
+  {
+  var $db;
+  var $conn;
+  var $root_ns = '';
+  var $root_dir = '';
+  var $mailbox = 'INBOX';
+  var $list_page = 1;
+  var $page_size = 10;
+  var $sort_field = 'date';
+  var $sort_order = 'DESC';
+  var $delimiter = NULL;
+  var $caching_enabled = FALSE;
+  var $default_folders = array('INBOX');
+  var $default_folders_lc = array('inbox');
+  var $cache = array();
+  var $cache_keys = array();  
+  var $cache_changes = array();
+  var $uid_id_map = array();
+  var $msg_headers = array();
+  var $capabilities = array();
+  var $skip_deleted = FALSE;
+  var $debug_level = 1;
+
+
+  /**
+   * Object constructor
+   *
+   * @param  object  Database connection
+   */
+  function __construct($db_conn)
+    {
+    $this->db = $db_conn;
+    }
+
+
+  /**
+   * PHP 4 object constructor
+   *
+   * @see  rcube_imap::__construct
+   */
+  function rcube_imap($db_conn)
+    {
+    $this->__construct($db_conn);
+    }
+
+
+  /**
+   * Connect to an IMAP server
+   *
+   * @param  string   Host to connect
+   * @param  string   Username for IMAP account
+   * @param  string   Password for IMAP account
+   * @param  number   Port to connect to
+   * @param  boolean  Use SSL connection
+   * @return boolean  TRUE on success, FALSE on failure
+   * @access public
+   */
+  function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
+    {
+    global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
+    
+    // check for Open-SSL support in PHP build
+    if ($use_ssl && in_array('openssl', get_loaded_extensions()))
+      $ICL_SSL = TRUE;
+    else if ($use_ssl)
+      {
+      raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
+                        'message' => 'Open SSL not available;'), TRUE, FALSE);
+      $port = 143;
+      }
+
+    $ICL_PORT = $port;
+    $IMAP_USE_INTERNAL_DATE = false;
+    
+    $this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
+    $this->host = $host;
+    $this->user = $user;
+    $this->pass = $pass;
+    $this->port = $port;
+    $this->ssl = $use_ssl;
+    
+    // print trace mesages
+    if ($this->conn && ($this->debug_level & 8))
+      console($this->conn->message);
+    
+    // write error log
+    else if (!$this->conn && $GLOBALS['iil_error'])
+      {
+      raise_error(array('code' => 403,
+                       'type' => 'imap',
+                       'message' => $GLOBALS['iil_error']), TRUE, FALSE);
+      }
+
+    // get account namespace
+    if ($this->conn)
+      {
+      $this->_parse_capability($this->conn->capability);
+      iil_C_NameSpace($this->conn);
+      
+      if (!empty($this->conn->delimiter))
+        $this->delimiter = $this->conn->delimiter;
+      if (!empty($this->conn->rootdir))
+        {
+        $this->set_rootdir($this->conn->rootdir);
+        $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
+        }
+      }
+
+    return $this->conn ? TRUE : FALSE;
+    }
+
+
+  /**
+   * Close IMAP connection
+   * Usually done on script shutdown
+   *
+   * @access public
+   */
+  function close()
+    {    
+    if ($this->conn)
+      iil_Close($this->conn);
+    }
+
+
+  /**
+   * Close IMAP connection and re-connect
+   * This is used to avoid some strange socket errors when talking to Courier IMAP
+   *
+   * @access public
+   */
+  function reconnect()
+    {
+    $this->close();
+    $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
+    }
+
+
+  /**
+   * Set a root folder for the IMAP connection.
+   *
+   * Only folders within this root folder will be displayed
+   * and all folder paths will be translated using this folder name
+   *
+   * @param  string   Root folder
+   * @access public
+   */
+  function set_rootdir($root)
+    {
+    if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
+      $root = substr($root, 0, -1);
+
+    $this->root_dir = $root;
+    
+    if (empty($this->delimiter))
+      $this->get_hierarchy_delimiter();
+    }
+
+
+  /**
+   * This list of folders will be listed above all other folders
+   *
+   * @param  array  Indexed list of folder names
+   * @access public
+   */
+  function set_default_mailboxes($arr)
+    {
+    if (is_array($arr))
+      {
+      $this->default_folders = $arr;
+      $this->default_folders_lc = array();
+
+      // add inbox if not included
+      if (!in_array_nocase('INBOX', $this->default_folders))
+        array_unshift($this->default_folders, 'INBOX');
+
+      // create a second list with lower cased names
+      foreach ($this->default_folders as $mbox)
+        $this->default_folders_lc[] = strtolower($mbox);
+      }
+    }
+
+
+  /**
+   * Set internal mailbox reference.
+   *
+   * All operations will be perfomed on this mailbox/folder
+   *
+   * @param  string  Mailbox/Folder name
+   * @access public
+   */
+  function set_mailbox($new_mbox)
+    {
+    $mailbox = $this->_mod_mailbox($new_mbox);
+
+    if ($this->mailbox == $mailbox)
+      return;
+
+    $this->mailbox = $mailbox;
+
+    // clear messagecount cache for this mailbox
+    $this->_clear_messagecount($mailbox);
+    }
+
+
+  /**
+   * Set internal list page
+   *
+   * @param  number  Page number to list
+   * @access public
+   */
+  function set_page($page)
+    {
+    $this->list_page = (int)$page;
+    }
+
+
+  /**
+   * Set internal page size
+   *
+   * @param  number  Number of messages to display on one page
+   * @access public
+   */
+  function set_pagesize($size)
+    {
+    $this->page_size = (int)$size;
+    }
+
+
+  /**
+   * Returns the currently used mailbox name
+   *
+   * @return  string Name of the mailbox/folder
+   * @access  public
+   */
+  function get_mailbox_name()
+    {
+    return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
+    }
+
+
+  /**
+   * Returns the IMAP server's capability
+   *
+   * @param   string  Capability name
+   * @return  mixed   Capability value or TRUE if supported, FALSE if not
+   * @access  public
+   */
+  function get_capability($cap)
+    {
+    $cap = strtoupper($cap);
+    return $this->capabilities[$cap];
+    }
+
+
+  /**
+   * Returns the delimiter that is used by the IMAP server for folder separation
+   *
+   * @return  string  Delimiter string
+   * @access  public
+   */
+  function get_hierarchy_delimiter()
+    {
+    if ($this->conn && empty($this->delimiter))
+      $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
+
+    if (empty($this->delimiter))
+      $this->delimiter = '/';
+
+    return $this->delimiter;
+    }
+
+
+  /**
+   * Public method for mailbox listing.
+   *
+   * Converts mailbox name with root dir first
+   *
+   * @param   string  Optional root folder
+   * @param   string  Optional filter for mailbox listing
+   * @return  array   List of mailboxes/folders
+   * @access  public
+   */
+  function list_mailboxes($root='', $filter='*')
+    {
+    $a_out = array();
+    $a_mboxes = $this->_list_mailboxes($root, $filter);
+
+    foreach ($a_mboxes as $mbox_row)
+      {
+      $name = $this->_mod_mailbox($mbox_row, 'out');
+      if (strlen($name))
+        $a_out[] = $name;
+      }
+
+    // INBOX should always be available
+    if (!in_array_nocase('INBOX', $a_out))
+      array_unshift($a_out, 'INBOX');
+
+    // sort mailboxes
+    $a_out = $this->_sort_mailbox_list($a_out);
+
+    return $a_out;
+    }
+
+
+  /**
+   * Private method for mailbox listing
+   *
+   * @return  array   List of mailboxes/folders
+   * @access  private
+   * @see     rcube_imap::list_mailboxes
+   */
+  function _list_mailboxes($root='', $filter='*')
+    {
+    $a_defaults = $a_out = array();
+    
+    // get cached folder list    
+    $a_mboxes = $this->get_cache('mailboxes');
+    if (is_array($a_mboxes))
+      return $a_mboxes;
+
+    // retrieve list of folders from IMAP server
+    $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
+    
+    if (!is_array($a_folders) || !sizeof($a_folders))
+      $a_folders = array();
+
+    // write mailboxlist to cache
+    $this->update_cache('mailboxes', $a_folders);
+    
+    return $a_folders;
+    }
+
+
+  /**
+   * Get message count for a specific mailbox
+   *
+   * @param   string   Mailbox/folder name
+   * @param   string   Mode for count [ALL|UNSEEN|RECENT]
+   * @param   boolean  Force reading from server and update cache
+   * @return  number   Number of messages
+   * @access  public   
+   */
+  function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
+    {
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+    return $this->_messagecount($mailbox, $mode, $force);
+    }
+
+
+  /**
+   * Private method for getting nr of messages
+   *
+   * @access  private
+   * @see     rcube_imap::messagecount
+   */
+  function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
+    {
+    $a_mailbox_cache = FALSE;
+    $mode = strtoupper($mode);
+
+    if (empty($mailbox))
+      $mailbox = $this->mailbox;
+
+    $a_mailbox_cache = $this->get_cache('messagecount');
+    
+    // return cached value
+    if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
+      return $a_mailbox_cache[$mailbox][$mode];
+
+    // RECENT count is fetched abit different      
+    if ($mode == 'RECENT')
+       $count = iil_C_CheckForRecent($this->conn, $mailbox);
+
+    // use SEARCH for message counting
+    else if ($this->skip_deleted)
+      {
+      $search_str = "ALL UNDELETED";
+
+      // get message count and store in cache
+      if ($mode == 'UNSEEN')
+        $search_str .= " UNSEEN";
+
+      // get message count using SEARCH
+      // not very performant but more precise (using UNDELETED)
+      $count = 0;
+      $index = $this->_search_index($mailbox, $search_str);
+      if (is_array($index))
+        {
+        $str = implode(",", $index);
+        if (!empty($str))
+          $count = count($index);
+        }
+      }
+    else
+      {
+      if ($mode == 'UNSEEN')
+        $count = iil_C_CountUnseen($this->conn, $mailbox);
+      else
+        $count = iil_C_CountMessages($this->conn, $mailbox);
+      }
+
+    if (!is_array($a_mailbox_cache[$mailbox]))
+      $a_mailbox_cache[$mailbox] = array();
+      
+    $a_mailbox_cache[$mailbox][$mode] = (int)$count;
+
+    // write back to cache
+    $this->update_cache('messagecount', $a_mailbox_cache);
+
+    return (int)$count;
+    }
+
+
+  /**
+   * Public method for listing headers
+   * convert mailbox name with root dir first
+   *
+   * @param   string   Mailbox/folder name
+   * @param   number   Current page to list
+   * @param   string   Header field to sort by
+   * @param   string   Sort order [ASC|DESC]
+   * @return  array    Indexed array with message header objects
+   * @access  public   
+   */
+  function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
+    {
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+    return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
+    }
+
+
+  /**
+   * Private method for listing message headers
+   *
+   * @access  private
+   * @see     rcube_imap::list_headers
+   */
+  function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
+    {
+    if (!strlen($mailbox))
+      return array();
+      
+    if ($sort_field!=NULL)
+      $this->sort_field = $sort_field;
+    if ($sort_order!=NULL)
+      $this->sort_order = strtoupper($sort_order);
+
+    $max = $this->_messagecount($mailbox);
+    $start_msg = ($this->list_page-1) * $this->page_size;
+
+    list($begin, $end) = $this->_get_message_range($max, $page);
+
+       // mailbox is empty
+    if ($begin >= $end)
+      return array();
+
+    $headers_sorted = FALSE;
+    $cache_key = $mailbox.'.msg';
+    $cache_status = $this->check_cache_status($mailbox, $cache_key);
+
+    // cache is OK, we can get all messages from local cache
+    if ($cache_status>0)
+      {
+      $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
+      $headers_sorted = TRUE;
+      }
+    // cache is dirty, sync it
+    else if ($this->caching_enabled && $cache_status==-1 && !$recursive)
+      {
+      $this->sync_header_index($mailbox);
+      return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
+      }
+    else
+      {
+      // retrieve headers from IMAP
+      if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
+        {        
+        $msgs = $msg_index[$begin];
+        for ($i=$begin+1; $i < $end; $i++)
+          $msgs = $msgs.','.$msg_index[$i];
+        }
+      else
+        {
+        $msgs = sprintf("%d:%d", $begin+1, $end);
+
+        $i = 0;
+        for ($msg_seqnum = $begin; $msg_seqnum <= $end; $msg_seqnum++)
+          $msg_index[$i++] = $msg_seqnum;
+        }
+
+      // use this class for message sorting
+      $sorter = new rcube_header_sorter();
+      $sorter->set_sequence_numbers($msg_index);
+
+      // fetch reuested headers from server
+      $a_msg_headers = array();
+      $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
+
+      // delete cached messages with a higher index than $max
+      $this->clear_message_cache($cache_key, $max);
+
+
+      // kick child process to sync cache
+      // ...
+
+      }
+
+
+    // return empty array if no messages found
+       if (!is_array($a_msg_headers) || empty($a_msg_headers))
+               return array();
+
+
+    // if not already sorted
+    if (!$headers_sorted)
+      {
+      $sorter->sort_headers($a_msg_headers);
+
+      if ($this->sort_order == 'DESC')
+        $a_msg_headers = array_reverse($a_msg_headers);
+      }
+
+    return array_values($a_msg_headers);
+    }
+
+
+
+  /**
+   * Public method for listing a specific set of headers
+   * convert mailbox name with root dir first
+   *
+   * @param   string   Mailbox/folder name
+   * @param   array    List of message ids to list
+   * @param   number   Current page to list
+   * @param   string   Header field to sort by
+   * @param   string   Sort order [ASC|DESC]
+   * @return  array    Indexed array with message header objects
+   * @access  public   
+   */
+  function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
+    {
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+    return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);    
+    }
+    
+
+  /**
+   * Private method for listing a set of message headers
+   *
+   * @access  private
+   * @see     rcube_imap::list_header_set
+   */
+  function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
+    {
+    // also accept a comma-separated list of message ids
+    if (is_string($msgs))
+      $msgs = split(',', $msgs);
+      
+    if (!strlen($mailbox) || empty($msgs))
+      return array();
+
+    if ($sort_field!=NULL)
+      $this->sort_field = $sort_field;
+    if ($sort_order!=NULL)
+      $this->sort_order = strtoupper($sort_order);
+
+    $max = count($msgs);
+    $start_msg = ($this->list_page-1) * $this->page_size;
+
+    // fetch reuested headers from server
+    $a_msg_headers = array();
+    $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
+
+    // return empty array if no messages found
+       if (!is_array($a_msg_headers) || empty($a_msg_headers))
+               return array();
+
+    // if not already sorted
+    $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
+
+       // only return the requested part of the set
+       return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
+    }
+
+
+  /**
+   * Helper function to get first and last index of the requested set
+   *
+   * @param  number  message count
+   * @param  mixed   page number to show, or string 'all'
+   * @return array   array with two values: first index, last index
+   * @access private
+   */
+  function _get_message_range($max, $page)
+    {
+    $start_msg = ($this->list_page-1) * $this->page_size;
+    
+    if ($page=='all')
+      {
+      $begin = 0;
+      $end = $max;
+      }
+    else if ($this->sort_order=='DESC')
+      {
+      $begin = $max - $this->page_size - $start_msg;
+      $end =   $max - $start_msg;
+      }
+    else
+      {
+      $begin = $start_msg;
+      $end   = $start_msg + $this->page_size;
+      }
+
+    if ($begin < 0) $begin = 0;
+    if ($end < 0) $end = $max;
+    if ($end > $max) $end = $max;
+    
+    return array($begin, $end);
+    }
+    
+    
+
+  /**
+   * Fetches message headers
+   * Used for loop
+   *
+   * @param  string  Mailbox name
+   * @param  string  Message index to fetch
+   * @param  array   Reference to message headers array
+   * @param  array   Array with cache index
+   * @return number  Number of deleted messages
+   * @access private
+   */
+  function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
+    {
+    // cache is incomplete
+    $cache_index = $this->get_message_cache_index($cache_key);
+    
+    // fetch reuested headers from server
+    $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
+    $deleted_count = 0;
+    
+    if (!empty($a_header_index))
+      {
+      foreach ($a_header_index as $i => $headers)
+        {
+        if ($headers->deleted && $this->skip_deleted)
+          {
+          // delete from cache
+          if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
+            $this->remove_message_cache($cache_key, $headers->id);
+
+          $deleted_count++;
+          continue;
+          }
+
+        // add message to cache
+        if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
+          $this->add_message_cache($cache_key, $headers->id, $headers);
+
+        $a_msg_headers[$headers->uid] = $headers;
+        }
+      }
+        
+    return $deleted_count;
+    }
+    
+  
+  // return sorted array of message UIDs
+  function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
+    {
+    if ($sort_field!=NULL)
+      $this->sort_field = $sort_field;
+    if ($sort_order!=NULL)
+      $this->sort_order = strtoupper($sort_order);
+
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+    $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
+
+    // have stored it in RAM
+    if (isset($this->cache[$key]))
+      return $this->cache[$key];
+
+    // check local cache
+    $cache_key = $mailbox.'.msg';
+    $cache_status = $this->check_cache_status($mailbox, $cache_key);
+
+    // cache is OK
+    if ($cache_status>0)
+      {
+      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
+      return array_values($a_index);
+      }
+
+
+    // fetch complete message index
+    $msg_count = $this->_messagecount($mailbox);
+    if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
+      {
+      if ($this->sort_order == 'DESC')
+        $a_index = array_reverse($a_index);
+
+      $this->cache[$key] = $a_index;
+
+      }
+    else
+      {
+      $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
+      $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
+    
+      if ($this->sort_order=="ASC")
+        asort($a_index);
+      else if ($this->sort_order=="DESC")
+        arsort($a_index);
+        
+      $i = 0;
+      $this->cache[$key] = array();
+      foreach ($a_index as $index => $value)
+        $this->cache[$key][$i++] = $a_uids[$index];
+      }
+
+    return $this->cache[$key];
+    }
+
+
+  function sync_header_index($mailbox)
+    {
+    $cache_key = $mailbox.'.msg';
+    $cache_index = $this->get_message_cache_index($cache_key);
+    $msg_count = $this->_messagecount($mailbox);
+
+    // fetch complete message index
+    $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
+        
+    foreach ($a_message_index as $id => $uid)
+      {
+      // message in cache at correct position
+      if ($cache_index[$id] == $uid)
+        {
+// console("$id / $uid: OK");
+        unset($cache_index[$id]);
+        continue;
+        }
+        
+      // message in cache but in wrong position
+      if (in_array((string)$uid, $cache_index, TRUE))
+        {
+// console("$id / $uid: Moved");
+        unset($cache_index[$id]);        
+        }
+      
+      // other message at this position
+      if (isset($cache_index[$id]))
+        {
+// console("$id / $uid: Delete");
+        $this->remove_message_cache($cache_key, $id);
+        unset($cache_index[$id]);
+        }
+        
+
+// console("$id / $uid: Add");
+
+      // fetch complete headers and add to cache
+      $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
+      $this->add_message_cache($cache_key, $headers->id, $headers);
+      }
+
+    // those ids that are still in cache_index have been deleted      
+    if (!empty($cache_index))
+      {
+      foreach ($cache_index as $id => $uid)
+        $this->remove_message_cache($cache_key, $id);
+      }
+    }
+
+
+  /**
+   * Invoke search request to IMAP server
+   *
+   * @param  string  mailbox name to search in
+   * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
+   * @param  string  search string
+   * @return array   search results as list of message ids
+   * @access public
+   */
+  function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
+    {
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+    if ($str && $criteria)
+      {
+      $search = (!empty($charset) ? "CHARSET $charset " : '') . sprintf("%s {%d}\r\n%s", $criteria, strlen($str), $str);
+      $results = $this->_search_index($mailbox, $search);
+
+      // try search without charset (probably not supported by server)
+      if (empty($results))
+        $results = $this->_search_index($mailbox, "$criteria $str");
+      
+      return $results;
+      }
+    else
+      return $this->_search_index($mailbox, $criteria);
+    }    
+
+
+  /**
+   * Private search method
+   *
+   * @return array   search results as list of message ids
+   * @access private
+   * @see rcube_imap::search()
+   */
+  function _search_index($mailbox, $criteria='ALL')
+    {
+    $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
+    // clean message list (there might be some empty entries)
+    if (is_array($a_messages))
+      {
+      foreach ($a_messages as $i => $val)
+        if (empty($val))
+          unset($a_messages[$i]);
+      }
+        
+    return $a_messages;
+    }
+
+
+  function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
+    {
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+
+    // get cached headers
+    if ($is_uid && ($headers = $this->get_cached_message($mailbox.'.msg', $id)))
+      return $headers;
+
+    $msg_id = $is_uid ? $this->_uid2id($id) : $id;
+    $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
+
+    // write headers cache
+    if ($headers)
+      $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
+
+    return $headers;
+    }
+
+
+  function get_body($uid, $part=1)
+    {
+    if (!($msg_id = $this->_uid2id($uid)))
+      return FALSE;
+
+       $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
+       $structure = iml_GetRawStructureArray($structure_str);
+    $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
+
+    $encoding = iml_GetPartEncodingCode($structure, $part);
+    
+    if ($encoding==3) $body = $this->mime_decode($body, 'base64');
+    else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
+
+    return $body;
+    }
+
+
+  function get_raw_body($uid)
+    {
+    if (!($msg_id = $this->_uid2id($uid)))
+      return FALSE;
+
+       $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
+       $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
+
+    return $body;    
+    }
+
+
+  // set message flag to one or several messages
+  // possible flags are: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
+  function set_flag($uids, $flag)
+    {
+    $flag = strtoupper($flag);
+    $msg_ids = array();
+    if (!is_array($uids))
+      $uids = explode(',',$uids);
+      
+    foreach ($uids as $uid) {
+      $msg_ids[$uid] = $this->_uid2id($uid);
+    }
+      
+    if ($flag=='UNDELETED')
+      $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
+    else if ($flag=='UNSEEN')
+      $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
+    else
+      $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
+
+    // reload message headers if cached
+    $cache_key = $this->mailbox.'.msg';
+    if ($this->caching_enabled)
+      {
+      foreach ($msg_ids as $uid => $id)
+        {
+        if ($cached_headers = $this->get_cached_message($cache_key, $uid))
+          {
+          $this->remove_message_cache($cache_key, $id);
+          //$this->get_headers($uid);
+          }
+        }
+
+      // close and re-open connection
+      // this prevents connection problems with Courier 
+      $this->reconnect();
+      }
+
+    // set nr of messages that were flaged
+    $count = count($msg_ids);
+
+    // clear message count cache
+    if ($result && $flag=='SEEN')
+      $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
+    else if ($result && $flag=='UNSEEN')
+      $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
+    else if ($result && $flag=='DELETED')
+      $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
+
+    return $result;
+    }
+
+
+  // append a mail message (source) to a specific mailbox
+  function save_message($mbox_name, &$message)
+    {
+    $mbox_name = stripslashes($mbox_name);
+    $mailbox = $this->_mod_mailbox($mbox_name);
+
+    // make sure mailbox exists
+    if (in_array($mailbox, $this->_list_mailboxes()))
+      $saved = iil_C_Append($this->conn, $mailbox, $message);
+
+    if ($saved)
+      {
+      // increase messagecount of the target mailbox
+      $this->_set_messagecount($mailbox, 'ALL', 1);
+      }
+          
+    return $saved;
+    }
+
+
+  // move a message from one mailbox to another
+  function move_message($uids, $to_mbox, $from_mbox='')
+    {
+    $to_mbox = stripslashes($to_mbox);
+    $from_mbox = stripslashes($from_mbox);
+    $to_mbox = $this->_mod_mailbox($to_mbox);
+    $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
+
+    // make sure mailbox exists
+    if (!in_array($to_mbox, $this->_list_mailboxes()))
+      {
+      if (in_array(strtolower($to_mbox), $this->default_folders))
+        $this->create_mailbox($to_mbox, TRUE);
+      else
+        return FALSE;
+      }
+
+    // convert the list of uids to array
+    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
+    
+    // exit if no message uids are specified
+    if (!is_array($a_uids))
+      return false;
+
+    // convert uids to message ids
+    $a_mids = array();
+    foreach ($a_uids as $uid)
+      $a_mids[] = $this->_uid2id($uid, $from_mbox);
+
+    $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
+    
+    // send expunge command in order to have the moved message
+    // really deleted from the source mailbox
+    if ($moved)
+      {
+      $this->_expunge($from_mbox, FALSE);
+      $this->_clear_messagecount($from_mbox);
+      $this->_clear_messagecount($to_mbox);
+      }
+
+    // update cached message headers
+    $cache_key = $from_mbox.'.msg';
+    if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
+      {
+      $start_index = 100000;
+      foreach ($a_uids as $uid)
+        {
+        if(($index = array_search($uid, $a_cache_index)) !== FALSE)
+         $start_index = min($index, $start_index);
+        }
+
+      // clear cache from the lowest index on
+      $this->clear_message_cache($cache_key, $start_index);
+      }
+
+    return $moved;
+    }
+
+
+  // mark messages as deleted and expunge mailbox
+  function delete_message($uids, $mbox_name='')
+    {
+    $mbox_name = stripslashes($mbox_name);
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+
+    // convert the list of uids to array
+    $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
+    
+    // exit if no message uids are specified
+    if (!is_array($a_uids))
+      return false;
+
+
+    // convert uids to message ids
+    $a_mids = array();
+    foreach ($a_uids as $uid)
+      $a_mids[] = $this->_uid2id($uid, $mailbox);
+        
+    $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
+    
+    // send expunge command in order to have the deleted message
+    // really deleted from the mailbox
+    if ($deleted)
+      {
+      $this->_expunge($mailbox, FALSE);
+      $this->_clear_messagecount($mailbox);
+      }
+
+    // remove deleted messages from cache
+    $cache_key = $mailbox.'.msg';
+    if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
+      {
+      $start_index = 100000;
+      foreach ($a_uids as $uid)
+        {
+        $index = array_search($uid, $a_cache_index);
+        $start_index = min($index, $start_index);
+        }
+
+      // clear cache from the lowest index on
+      $this->clear_message_cache($cache_key, $start_index);
+      }
+
+    return $deleted;
+    }
+
+
+  // clear all messages in a specific mailbox
+  function clear_mailbox($mbox_name=NULL)
+    {
+    $mbox_name = stripslashes($mbox_name);
+    $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+    $msg_count = $this->_messagecount($mailbox, 'ALL');
+    
+    if ($msg_count>0)
+      {
+      $cleared = iil_C_ClearFolder($this->conn, $mailbox);
+      
+      // make sure the message count cache is cleared as well
+      if ($cleared)
+        {
+        $this->clear_message_cache($mailbox.'.msg');      
+        $a_mailbox_cache = $this->get_cache('messagecount');
+        unset($a_mailbox_cache[$mailbox]);
+        $this->update_cache('messagecount', $a_mailbox_cache);
+        }
+        
+      return $cleared;
+      }
+    else
+      return 0;
+    }
+
+
+  // send IMAP expunge command and clear cache
+  function expunge($mbox_name='', $clear_cache=TRUE)
+    {
+    $mbox_name = stripslashes($mbox_name);
+    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
+    return $this->_expunge($mailbox, $clear_cache);
+    }
+
+
+  // send IMAP expunge command and clear cache
+  function _expunge($mailbox, $clear_cache=TRUE)
+    {
+    $result = iil_C_Expunge($this->conn, $mailbox);
+
+    if ($result>=0 && $clear_cache)
+      {
+      //$this->clear_message_cache($mailbox.'.msg');
+      $this->_clear_messagecount($mailbox);
+      }
+      
+    return $result;
+    }
+
+
+  /* --------------------------------
+   *        folder managment
+   * --------------------------------*/
+
+
+  /**
+   * Get a list of all folders available on the IMAP server
+   * 
+   * @param string IMAP root dir
+   * @return array Inbdexed array with folder names 
+   */
+  function list_unsubscribed($root='')
+    {
+    static $sa_unsubscribed;
+    
+    if (is_array($sa_unsubscribed))
+      return $sa_unsubscribed;
+      
+    // retrieve list of folders from IMAP server
+    $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
+
+    // modify names with root dir
+    foreach ($a_mboxes as $mbox_name)
+      {
+      $name = $this->_mod_mailbox($mbox_name, 'out');
+      if (strlen($name))
+        $a_folders[] = $name;
+      }
+
+    // filter folders and sort them
+    $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
+    return $sa_unsubscribed;
+    }
+
+
+  /**
+   * Get quota
+   * added by Nuny
+   */
+  function get_quota()
+    {
+    if ($this->get_capability('QUOTA'))
+      {
+      $result = iil_C_GetQuota($this->conn);
+      if ($result["total"])
+        return sprintf("%.2fMB / %.2fMB (%.0f%%)", $result["used"] / 1000.0, $result["total"] / 1000.0, $result["percent"]);       
+      }
+
+    return FALSE;
+    }
+
+
+  /**
+   * subscribe to a specific mailbox(es)
+   */ 
+  function subscribe($mbox_name, $mode='subscribe')
+    {
+    if (is_array($mbox_name))
+      $a_mboxes = $mbox_name;
+    else if (is_string($mbox_name) && strlen($mbox_name))
+      $a_mboxes = explode(',', $mbox_name);
+    
+    // let this common function do the main work
+    return $this->_change_subscription($a_mboxes, 'subscribe');
+    }
+
+
+  /**
+   * unsubscribe mailboxes
+   */
+  function unsubscribe($mbox_name)
+    {
+    if (is_array($mbox_name))
+      $a_mboxes = $mbox_name;
+    else if (is_string($mbox_name) && strlen($mbox_name))
+      $a_mboxes = explode(',', $mbox_name);
+
+    // let this common function do the main work
+    return $this->_change_subscription($a_mboxes, 'unsubscribe');
+    }
+
+
+  /**
+   * create a new mailbox on the server and register it in local cache
+   */
+  function create_mailbox($name, $subscribe=FALSE)
+    {
+    $result = FALSE;
+    
+    // replace backslashes
+    $name = preg_replace('/[\\\]+/', '-', $name);
+
+    $name_enc = UTF7EncodeString($name);
+
+    // reduce mailbox name to 100 chars
+    $name_enc = substr($name_enc, 0, 100);
+
+    $abs_name = $this->_mod_mailbox($name_enc);
+    $a_mailbox_cache = $this->get_cache('mailboxes');
+
+    if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array_nocase($abs_name, $a_mailbox_cache)))
+      $result = iil_C_CreateFolder($this->conn, $abs_name);
+
+    // try to subscribe it
+    if ($subscribe)
+      $this->subscribe($name_enc);
+
+    return $result ? $name : FALSE;
+    }
+
+
+  /**
+   * set a new name to an existing mailbox
+   */
+  function rename_mailbox($mbox_name, $new_name)
+    {
+    $result = FALSE;
+
+    // replace backslashes
+    $name = preg_replace('/[\\\]+/', '-', $new_name);
+        
+    // encode mailbox name and reduce it to 100 chars
+    $name_enc = substr(UTF7EncodeString($new_name), 0, 100);
+
+    // make absolute path
+    $mailbox = $this->_mod_mailbox($mbox_name);
+    $abs_name = $this->_mod_mailbox($name_enc);
+    
+    if (strlen($abs_name))
+      $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
+    
+    // clear cache
+    if ($result)
+      {
+      $this->clear_message_cache($mailbox.'.msg');
+      $this->clear_cache('mailboxes');
+      }
+
+    return $result ? $name : FALSE;
+    }
+
+
+  /**
+   * remove mailboxes from server
+   */
+  function delete_mailbox($mbox_name)
+    {
+    $deleted = FALSE;
+
+    if (is_array($mbox_name))
+      $a_mboxes = $mbox_name;
+    else if (is_string($mbox_name) && strlen($mbox_name))
+      $a_mboxes = explode(',', $mbox_name);
+
+    if (is_array($a_mboxes))
+      foreach ($a_mboxes as $mbox_name)
+        {
+        $mailbox = $this->_mod_mailbox($mbox_name);
+
+        // unsubscribe mailbox before deleting
+        iil_C_UnSubscribe($this->conn, $mailbox);
+
+        // send delete command to server
+        $result = iil_C_DeleteFolder($this->conn, $mailbox);
+        if ($result>=0)
+          $deleted = TRUE;
+        }
+
+    // clear mailboxlist cache
+    if ($deleted)
+      {
+      $this->clear_message_cache($mailbox.'.msg');
+      $this->clear_cache('mailboxes');
+      }
+
+    return $deleted;
+    }
+
+
+  /**
+   * Create all folders specified as default
+   */
+  function create_default_folders()
+    {
+    $a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
+    $a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
+    
+    // create default folders if they do not exist
+    foreach ($this->default_folders as $folder)
+      {
+      $abs_name = $this->_mod_mailbox($folder);
+      if (!in_array_nocase($abs_name, $a_subscribed))
+        {
+        if (!in_array_nocase($abs_name, $a_folders))
+          $this->create_mailbox($folder, TRUE);
+        else
+          $this->subscribe($folder);
+        }
+      }
+    }
+
+
+
+  /* --------------------------------
+   *   internal caching methods
+   * --------------------------------*/
+
+
+  function set_caching($set)
+    {
+    if ($set && is_object($this->db))
+      $this->caching_enabled = TRUE;
+    else
+      $this->caching_enabled = FALSE;
+    }
+
+
+  function get_cache($key)
+    {
+    // read cache
+    if (!isset($this->cache[$key]) && $this->caching_enabled)
+      {
+      $cache_data = $this->_read_cache_record('IMAP.'.$key);
+      $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
+      }
+    
+    return $this->cache[$key];
+    }
+
+
+  function update_cache($key, $data)
+    {
+    $this->cache[$key] = $data;
+    $this->cache_changed = TRUE;
+    $this->cache_changes[$key] = TRUE;
+    }
+
+
+  function write_cache()
+    {
+    if ($this->caching_enabled && $this->cache_changed)
+      {
+      foreach ($this->cache as $key => $data)
+        {
+        if ($this->cache_changes[$key])
+          $this->_write_cache_record('IMAP.'.$key, serialize($data));
+        }
+      }    
+    }
+
+
+  function clear_cache($key=NULL)
+    {
+    if ($key===NULL)
+      {
+      foreach ($this->cache as $key => $data)
+        $this->_clear_cache_record('IMAP.'.$key);
+
+      $this->cache = array();
+      $this->cache_changed = FALSE;
+      $this->cache_changes = array();
+      }
+    else
+      {
+      $this->_clear_cache_record('IMAP.'.$key);
+      $this->cache_changes[$key] = FALSE;
+      unset($this->cache[$key]);
+      }
+    }
+
+
+
+  function _read_cache_record($key)
+    {
+    $cache_data = FALSE;
+    
+    if ($this->db)
+      {
+      // get cached data from DB
+      $sql_result = $this->db->query(
+        "SELECT cache_id, data
+         FROM ".get_table_name('cache')."
+         WHERE  user_id=?
+         AND    cache_key=?",
+        $_SESSION['user_id'],
+        $key);
+
+      if ($sql_arr = $this->db->fetch_assoc($sql_result))
+        {
+        $cache_data = $sql_arr['data'];
+        $this->cache_keys[$key] = $sql_arr['cache_id'];
+        }
+      }
+
+    return $cache_data;    
+    }
+    
+
+  function _write_cache_record($key, $data)
+    {
+    if (!$this->db)
+      return FALSE;
+
+    // check if we already have a cache entry for this key
+    if (!isset($this->cache_keys[$key]))
+      {
+      $sql_result = $this->db->query(
+        "SELECT cache_id
+         FROM ".get_table_name('cache')."
+         WHERE  user_id=?
+         AND    cache_key=?",
+        $_SESSION['user_id'],
+        $key);
+                                     
+      if ($sql_arr = $this->db->fetch_assoc($sql_result))
+        $this->cache_keys[$key] = $sql_arr['cache_id'];
+      else
+        $this->cache_keys[$key] = FALSE;
+      }
+
+    // update existing cache record
+    if ($this->cache_keys[$key])
+      {
+      $this->db->query(
+        "UPDATE ".get_table_name('cache')."
+         SET    created=now(),
+                data=?
+         WHERE  user_id=?
+         AND    cache_key=?",
+        $data,
+        $_SESSION['user_id'],
+        $key);
+      }
+    // add new cache record
+    else
+      {
+      $this->db->query(
+        "INSERT INTO ".get_table_name('cache')."
+         (created, user_id, cache_key, data)
+         VALUES (now(), ?, ?, ?)",
+        $_SESSION['user_id'],
+        $key,
+        $data);
+      }
+    }
+
+
+  function _clear_cache_record($key)
+    {
+    $this->db->query(
+      "DELETE FROM ".get_table_name('cache')."
+       WHERE  user_id=?
+       AND    cache_key=?",
+      $_SESSION['user_id'],
+      $key);
+    }
+
+
+
+  /* --------------------------------
+   *   message caching methods
+   * --------------------------------*/
+   
+
+  // checks if the cache is up-to-date
+  // return: -3 = off, -2 = incomplete, -1 = dirty
+  function check_cache_status($mailbox, $cache_key)
+    {
+    if (!$this->caching_enabled)
+      return -3;
+
+    $cache_index = $this->get_message_cache_index($cache_key, TRUE);
+    $msg_count = $this->_messagecount($mailbox);
+    $cache_count = count($cache_index);
+
+    // console("Cache check: $msg_count !== ".count($cache_index));
+
+    if ($cache_count==$msg_count)
+      {
+      // get highest index
+      $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
+      $cache_uid = array_pop($cache_index);
+      
+      // uids of highest message matches -> cache seems OK
+      if ($cache_uid == $header->uid)
+        return 1;
+
+      // cache is dirty
+      return -1;
+      }
+    // if cache count differs less than 10% report as dirty
+    else if (abs($msg_count - $cache_count) < $msg_count/10)
+      return -1;
+    else
+      return -2;
+    }
+
+
+
+  function get_message_cache($key, $from, $to, $sort_field, $sort_order)
+    {
+    $cache_key = "$key:$from:$to:$sort_field:$sort_order";
+    $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
+    
+    if (!in_array($sort_field, $db_header_fields))
+      $sort_field = 'idx';
+    
+    if ($this->caching_enabled && !isset($this->cache[$cache_key]))
+      {
+      $this->cache[$cache_key] = array();
+      $sql_result = $this->db->limitquery(
+        "SELECT idx, uid, headers
+         FROM ".get_table_name('messages')."
+         WHERE  user_id=?
+         AND    cache_key=?
+         ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
+         strtoupper($sort_order),
+        $from,
+        $to-$from,
+        $_SESSION['user_id'],
+        $key);
+
+      while ($sql_arr = $this->db->fetch_assoc($sql_result))
+        {
+        $uid = $sql_arr['uid'];
+        $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
+        }
+      }
+      
+    return $this->cache[$cache_key];
+    }
+
+
+  function get_cached_message($key, $uid, $body=FALSE)
+    {
+    if (!$this->caching_enabled)
+      return FALSE;
+
+    $internal_key = '__single_msg';
+    if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
+      {
+      $sql_select = "idx, uid, headers";
+      if ($body)
+        $sql_select .= ", body";
+      
+      $sql_result = $this->db->query(
+        "SELECT $sql_select
+         FROM ".get_table_name('messages')."
+         WHERE  user_id=?
+         AND    cache_key=?
+         AND    uid=?",
+        $_SESSION['user_id'],
+        $key,
+        $uid);
+      
+      if ($sql_arr = $this->db->fetch_assoc($sql_result))
+        {
+        $headers = unserialize($sql_arr['headers']);
+        if (is_object($headers) && !empty($sql_arr['body']))
+          $headers->body = $sql_arr['body'];
+
+        $this->cache[$internal_key][$uid] = $headers;
+        }
+      }
+
+    return $this->cache[$internal_key][$uid];
+    }
+
+   
+  function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
+    {
+    static $sa_message_index = array();
+    
+    // empty key -> empty array
+    if (empty($key))
+      return array();
+    
+    if (!empty($sa_message_index[$key]) && !$force)
+      return $sa_message_index[$key];
+    
+    $sa_message_index[$key] = array();
+    $sql_result = $this->db->query(
+      "SELECT idx, uid
+       FROM ".get_table_name('messages')."
+       WHERE  user_id=?
+       AND    cache_key=?
+       ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
+      $_SESSION['user_id'],
+      $key);
+
+    while ($sql_arr = $this->db->fetch_assoc($sql_result))
+      $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
+      
+    return $sa_message_index[$key];
+    }
+
+
+  function add_message_cache($key, $index, $headers)
+    {
+    if (!$key || !is_object($headers) || empty($headers->uid))
+      return;
+
+    $this->db->query(
+      "INSERT INTO ".get_table_name('messages')."
+       (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
+       VALUES (?, 0, ?, now(), ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
+      $_SESSION['user_id'],
+      $key,
+      $index,
+      $headers->uid,
+      (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
+      (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
+      (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
+      (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
+      (int)$headers->size,
+      serialize($headers));
+    }
+    
+    
+  function remove_message_cache($key, $index)
+    {
+    $this->db->query(
+      "DELETE FROM ".get_table_name('messages')."
+       WHERE  user_id=?
+       AND    cache_key=?
+       AND    idx=?",
+      $_SESSION['user_id'],
+      $key,
+      $index);
+    }
+
+
+  function clear_message_cache($key, $start_index=1)
+    {
+    $this->db->query(
+      "DELETE FROM ".get_table_name('messages')."
+       WHERE  user_id=?
+       AND    cache_key=?
+       AND    idx>=?",
+      $_SESSION['user_id'],
+      $key,
+      $start_index);
+    }
+
+
+
+
+  /* --------------------------------
+   *   encoding/decoding methods
+   * --------------------------------*/
+
+  
+  function decode_address_list($input, $max=NULL)
+    {
+    $a = $this->_parse_address_list($input);
+    $out = array();
+    
+    if (!is_array($a))
+      return $out;
+
+    $c = count($a);
+    $j = 0;
+
+    foreach ($a as $val)
+      {
+      $j++;
+      $address = $val['address'];
+      $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
+      $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
+      
+      $out[$j] = array('name' => $name,
+                       'mailto' => $address,
+                       'string' => $string);
+              
+      if ($max && $j==$max)
+        break;
+      }
+    
+    return $out;
+    }
+
+
+  function decode_header($input, $remove_quotes=FALSE)
+    {
+    $str = $this->decode_mime_string((string)$input);
+    if ($str{0}=='"' && $remove_quotes)
+      {
+      $str = str_replace('"', '', $str);
+      }
+    
+    return $str;
+    }
+
+
+  /**
+   * Decode a mime-encoded string to internal charset
+   *
+   * @access static
+   */
+  function decode_mime_string($input, $recursive=false)
+    {
+    $out = '';
+
+    $pos = strpos($input, '=?');
+    if ($pos !== false)
+      {
+      $out = substr($input, 0, $pos);
+  
+      $end_cs_pos = strpos($input, "?", $pos+2);
+      $end_en_pos = strpos($input, "?", $end_cs_pos+1);
+      $end_pos = strpos($input, "?=", $end_en_pos+1);
+  
+      $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
+      $rest = substr($input, $end_pos+2);
+
+      $out .= rcube_imap::_decode_mime_string_part($encstr);
+      $out .= rcube_imap::decode_mime_string($rest);
+
+      return $out;
+      }
+      
+    // no encoding information, defaults to what is specified in the class header
+    return rcube_charset_convert($input, 'ISO-8859-1');
+    }
+
+
+  /**
+   * Decode a part of a mime-encoded string
+   *
+   * @access static
+   */
+  function _decode_mime_string_part($str)
+    {
+    $a = explode('?', $str);
+    $count = count($a);
+
+    // should be in format "charset?encoding?base64_string"
+    if ($count >= 3)
+      {
+      for ($i=2; $i<$count; $i++)
+        $rest.=$a[$i];
+
+      if (($a[1]=="B")||($a[1]=="b"))
+        $rest = base64_decode($rest);
+      else if (($a[1]=="Q")||($a[1]=="q"))
+        {
+        $rest = str_replace("_", " ", $rest);
+        $rest = quoted_printable_decode($rest);
+        }
+
+      return rcube_charset_convert($rest, $a[0]);
+      }
+    else
+      return $str;    // we dont' know what to do with this  
+    }
+
+
+  function mime_decode($input, $encoding='7bit')
+    {
+    switch (strtolower($encoding))
+      {
+      case '7bit':
+        return $input;
+        break;
+      
+      case 'quoted-printable':
+        return quoted_printable_decode($input);
+        break;
+      
+      case 'base64':
+        return base64_decode($input);
+        break;
+      
+      default:
+        return $input;
+      }
+    }
+
+
+  function mime_encode($input, $encoding='7bit')
+    {
+    switch ($encoding)
+      {
+      case 'quoted-printable':
+        return quoted_printable_encode($input);
+        break;
+
+      case 'base64':
+        return base64_encode($input);
+        break;
+
+      default:
+        return $input;
+      }
+    }
+
+
+  // convert body chars according to the ctype_parameters
+  function charset_decode($body, $ctype_param)
+    {
+    if (is_array($ctype_param) && !empty($ctype_param['charset']))
+      return rcube_charset_convert($body, $ctype_param['charset']);
+
+    // defaults to what is specified in the class header
+    return rcube_charset_convert($body,  'ISO-8859-1');
+    }
+
+
+
+
+  /* --------------------------------
+   *         private methods
+   * --------------------------------*/
+
+
+  function _mod_mailbox($mbox_name, $mode='in')
+    {
+    if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
+      return $mbox_name;
+
+    if (!empty($this->root_dir) && $mode=='in') 
+      $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
+    else if (strlen($this->root_dir) && $mode=='out') 
+      $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
+
+    return $mbox_name;
+    }
+
+
+  // sort mailboxes first by default folders and then in alphabethical order
+  function _sort_mailbox_list($a_folders)
+    {
+    $a_out = $a_defaults = array();
+
+    // find default folders and skip folders starting with '.'
+    foreach($a_folders as $i => $folder)
+      {
+      if ($folder{0}=='.')
+       continue;
+
+      if (($p = array_search(strtolower($folder), $this->default_folders_lc))!==FALSE)
+       $a_defaults[$p] = $folder;
+      else
+        $a_out[] = $folder;
+      }
+
+    sort($a_out);
+    ksort($a_defaults);
+    
+    return array_merge($a_defaults, $a_out);
+    }
+
+  function get_id($uid, $mbox_name=NULL) 
+    {
+      return $this->_uid2id($uid, $mbox_name);
+    }
+  
+  function get_uid($id,$mbox_name=NULL)
+    {
+      return $this->_id2uid($id, $mbox_name);
+    }
+
+  function _uid2id($uid, $mbox_name=NULL)
+    {
+    if (!$mbox_name)
+      $mbox_name = $this->mailbox;
+      
+    if (!isset($this->uid_id_map[$mbox_name][$uid]))
+      $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
+
+    return $this->uid_id_map[$mbox_name][$uid];
+    }
+
+  function _id2uid($id, $mbox_name=NULL)
+    {
+    if (!$mbox_name)
+      $mbox_name = $this->mailbox;
+      
+    return iil_C_ID2UID($this->conn, $mbox_name, $id);
+    }
+
+
+  // parse string or array of server capabilities and put them in internal array
+  function _parse_capability($caps)
+    {
+    if (!is_array($caps))
+      $cap_arr = explode(' ', $caps);
+    else
+      $cap_arr = $caps;
+    
+    foreach ($cap_arr as $cap)
+      {
+      if ($cap=='CAPABILITY')
+        continue;
+
+      if (strpos($cap, '=')>0)
+        {
+        list($key, $value) = explode('=', $cap);
+        if (!is_array($this->capabilities[$key]))
+          $this->capabilities[$key] = array();
+          
+        $this->capabilities[$key][] = $value;
+        }
+      else
+        $this->capabilities[$cap] = TRUE;
+      }
+    }
+
+
+  // subscribe/unsubscribe a list of mailboxes and update local cache
+  function _change_subscription($a_mboxes, $mode)
+    {
+    $updated = FALSE;
+    
+    if (is_array($a_mboxes))
+      foreach ($a_mboxes as $i => $mbox_name)
+        {
+        $mailbox = $this->_mod_mailbox($mbox_name);
+        $a_mboxes[$i] = $mailbox;
+
+        if ($mode=='subscribe')
+          $result = iil_C_Subscribe($this->conn, $mailbox);
+        else if ($mode=='unsubscribe')
+          $result = iil_C_UnSubscribe($this->conn, $mailbox);
+
+        if ($result>=0)
+          $updated = TRUE;
+        }
+        
+    // get cached mailbox list    
+    if ($updated)
+      {
+      $a_mailbox_cache = $this->get_cache('mailboxes');
+      if (!is_array($a_mailbox_cache))
+        return $updated;
+
+      // modify cached list
+      if ($mode=='subscribe')
+        $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
+      else if ($mode=='unsubscribe')
+        $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
+        
+      // write mailboxlist to cache
+      $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
+      }
+
+    return $updated;
+    }
+
+
+  // increde/decrese messagecount for a specific mailbox
+  function _set_messagecount($mbox_name, $mode, $increment)
+    {
+    $a_mailbox_cache = FALSE;
+    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
+    $mode = strtoupper($mode);
+
+    $a_mailbox_cache = $this->get_cache('messagecount');
+    
+    if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
+      return FALSE;
+    
+    // add incremental value to messagecount
+    $a_mailbox_cache[$mailbox][$mode] += $increment;
+    
+    // there's something wrong, delete from cache
+    if ($a_mailbox_cache[$mailbox][$mode] < 0)
+      unset($a_mailbox_cache[$mailbox][$mode]);
+
+    // write back to cache
+    $this->update_cache('messagecount', $a_mailbox_cache);
+    
+    return TRUE;
+    }
+
+
+  // remove messagecount of a specific mailbox from cache
+  function _clear_messagecount($mbox_name='')
+    {
+    $a_mailbox_cache = FALSE;
+    $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
+
+    $a_mailbox_cache = $this->get_cache('messagecount');
+
+    if (is_array($a_mailbox_cache[$mailbox]))
+      {
+      unset($a_mailbox_cache[$mailbox]);
+      $this->update_cache('messagecount', $a_mailbox_cache);
+      }
+    }
+
+
+  function _parse_address_list($str)
+    {
+    $a = $this->_explode_quoted_string(',', $str);
+    $result = array();
+    
+    foreach ($a as $key => $val)
+      {
+      $val = str_replace("\"<", "\" <", $val);
+      $sub_a = $this->_explode_quoted_string(' ', $this->decode_header($val));
+      $result[$key]['name'] = '';
+
+      foreach ($sub_a as $k => $v)
+        {
+        if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
+          $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
+        else
+          $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
+        }
+        
+      if (empty($result[$key]['name']))
+        $result[$key]['name'] = $result[$key]['address'];        
+      }
+    
+    return $result;
+    }
+
+
+  function _explode_quoted_string($delimiter, $string)
+    {
+    $quotes = explode("\"", $string);
+    foreach ($quotes as $key => $val)
+      if (($key % 2) == 1)
+        $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
+        
+    $string = implode("\"", $quotes);
+
+    $result = explode($delimiter, $string);
+    foreach ($result as $key => $val) 
+      $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
+    
+    return $result;
+    }
+  }
+
+
+
+/**
+ * rcube_header_sorter
+ * 
+ * Class for sorting an array of iilBasicHeader objects in a predetermined order.
+ *
+ * @author Eric Stadtherr
+ */
+class rcube_header_sorter
+{
+   var $sequence_numbers = array();
+   
+   /**
+    * set the predetermined sort order.
+    *
+    * @param array $seqnums numerically indexed array of IMAP message sequence numbers
+    */
+   function set_sequence_numbers($seqnums)
+   {
+      $this->sequence_numbers = $seqnums;
+   }
+   /**
+    * sort the array of header objects
+    *
+    * @param array $headers array of iilBasicHeader objects indexed by UID
+    */
+   function sort_headers(&$headers)
+   {
+      /*
+       * uksort would work if the keys were the sequence number, but unfortunately
+       * the keys are the UIDs.  We'll use uasort instead and dereference the value
+       * to get the sequence number (in the "id" field).
+       * 
+       * uksort($headers, array($this, "compare_seqnums")); 
+       */
+       uasort($headers, array($this, "compare_seqnums"));
+   }
+   /**
+    * get the position of a message sequence number in my sequence_numbers array
+    *
+    * @param integer $seqnum message sequence number contained in sequence_numbers  
+    */
+   function position_of($seqnum)
+   {
+      $c = count($this->sequence_numbers);
+      for ($pos = 0; $pos <= $c; $pos++)
+      {
+         if ($this->sequence_numbers[$pos] == $seqnum)
+            return $pos;
+      }
+      return -1;
+   }
+   /**
+    * Sort method called by uasort()
+    */
+   function compare_seqnums($a, $b)
+   {
+      // First get the sequence number from the header object (the 'id' field).
+      $seqa = $a->id;
+      $seqb = $b->id;
+      
+      // then find each sequence number in my ordered list
+      $posa = $this->position_of($seqa);
+      $posb = $this->position_of($seqb);
+      
+      // return the relative position as the comparison value
+      $ret = $posa - $posb;
+      return $ret;
+   }
+}
+
+
+/**
+ * Add quoted-printable encoding to a given string
+ * 
+ * @param string  $input      string to encode
+ * @param int     $line_max   add new line after this number of characters
+ * @param boolena $space_conf true if spaces should be converted into =20
+ * @return encoded string
+ */
+function quoted_printable_encode($input, $line_max=76, $space_conv=false)
+  {
+  $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
+  $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
+  $eol = "\r\n";
+  $escape = "=";
+  $output = "";
+
+  while( list(, $line) = each($lines))
+    {
+    //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
+    $linlen = strlen($line);
+    $newline = "";
+    for($i = 0; $i < $linlen; $i++)
+      {
+      $c = substr( $line, $i, 1 );
+      $dec = ord( $c );
+      if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
+        {
+        $c = "=2E";
+        }
+      if ( $dec == 32 )
+        {
+        if ( $i == ( $linlen - 1 ) ) // convert space at eol only
+          {
+          $c = "=20";
+          }
+        else if ( $space_conv )
+          {
+          $c = "=20";
+          }
+        }
+      else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
+        {
+        $h2 = floor($dec/16);
+        $h1 = floor($dec%16);
+        $c = $escape.$hex["$h2"].$hex["$h1"];
+        }
+         
+      if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
+        {
+        $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
+        $newline = "";
+        // check if newline first character will be point or not
+        if ( $dec == 46 )
+          {
+          $c = "=2E";
+          }
+        }
+      $newline .= $c;
+      } // end of for
+    $output .= $newline.$eol;
+    } // end of while
+
+  return trim($output);
+  }
+
+?>
diff --git a/program/include/rcube_ldap.inc b/program/include/rcube_ldap.inc
new file mode 100644 (file)
index 0000000..65857b7
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_ldap.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Manage an LDAP connection                                           |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Jeremy Jongsma <jeremy@jongsma.org>                           |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_ldap.inc 95 2006-01-08 07:15:44Z justinrandell $
+
+*/
+
+require_once("bugs.inc");
+
+class rcube_ldap
+  {
+  var $conn;
+  var $host;
+  var $port;
+  var $protocol;
+  var $base_dn;
+  var $bind_dn;
+  var $bind_pass;
+
+  // PHP 5 constructor
+  function __construct()
+    {
+    }
+
+  // PHP 4 constructor
+  function rcube_ldap()
+    {
+    $this->__construct();
+    }
+
+  function connect($hosts, $port=389, $protocol=3)
+    {
+    if (!function_exists('ldap_connect'))
+      raise_error(array("type" => "ldap",
+                        "message" => "No ldap support in this installation of php."),
+                         TRUE);
+
+    if (is_resource($this->conn))
+      return TRUE;
+    
+    if (!is_array($hosts))
+      $hosts = array($hosts);
+
+    foreach ($hosts as $host)
+      {
+      if ($lc = @ldap_connect($host, $port))
+        {
+        @ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $protocol);
+        $this->host = $host;
+        $this->port = $port;
+        $this->protocol = $protocol;
+        $this->conn = $lc;
+        return TRUE;
+        }
+      }
+    
+    if (!is_resource($this->conn))
+      raise_error(array("type" => "ldap",
+                        "message" => "Could not connect to any LDAP server, tried $host:$port last"),
+                         TRUE);
+    }
+
+  function close()
+    {
+    if ($this->conn)
+      {
+      if (@ldap_unbind($this->conn))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Could not close connection to LDAP server: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    return FALSE;
+    }
+
+  // Merge with connect()?
+  function bind($dn=null, $pass=null)
+    {
+    if ($this->conn)
+      {
+      if ($dn)
+        if (@ldap_bind($this->conn, $dn, $pass))
+          return TRUE;
+        else
+          raise_error(array("code" => ldap_errno($this->conn),
+                            "type" => "ldap",
+                            "message" => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
+                      TRUE);
+      else
+        if (@ldap_bind($this->conn))
+          return TRUE;
+        else
+          raise_error(array("code" => ldap_errno($this->conn),
+                            "type" => "ldap",
+                            "message" => "Anonymous bind failed: ".ldap_error($this->conn)),
+                      TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Attempted bind on nonexistent connection"), TRUE);
+    return FALSE;
+    }
+
+  function count($base, $filter=null, $attributes=null, $scope="sub")
+    {
+    if ($this->conn)
+      {
+      if ($scope === 'sub')
+        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'one')
+        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'base')
+        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
+      if ($sr)
+        return @ldap_count_entries($this->conn, $sr);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Attempted count search on nonexistent connection"), TRUE);
+    return FALSE;
+    }
+
+  function search($base, $filter=null, $attributes=null, $scope='sub', $sort=null, $limit=0)
+    {
+    if ($this->conn)
+      {
+      if ($scope === 'sub')
+        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'one')
+        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
+      else if ($scope === 'base')
+        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
+      if ($sr)
+        {
+        if ($sort && $scope !== "base")
+          {
+          if (is_array($sort))
+            {
+            // Start from the end so first sort field has highest priority
+            $sortfields = array_reverse($sort);
+            foreach ($sortfields as $sortfield)
+              @ldap_sort($this->conn, $sr, $sortfield);
+            }
+          else
+            @ldap_sort($this->conn, $sr, $sort);
+          }
+        return @ldap_get_entries($this->conn, $sr);
+        }
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Attempted search on nonexistent connection"), TRUE);
+    return FALSE;
+    }
+
+  function add($dn, $object)
+    {
+    if ($this->conn)
+      {
+      if (@ldap_add($this->conn, $dn, $object))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Add object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Add object faile: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  function modify($dn, $object)
+    {
+    if ($this->conn)
+      {
+      if (@ldap_modify($this->conn, $dn, $object))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Modify object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Modify object failed: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  function rename($dn, $newrdn, $parentdn)
+    {
+    if ($this->protocol < 3)
+      {
+      raise_error(array("type" => "ldap",
+                        "message" => "rename() support requires LDAPv3 or above "),
+                  TRUE);
+      return FALSE;
+      }
+
+    if ($this->conn)
+      {
+      if (@ldap_rename($this->conn, $dn, $newrdn, $parentdn, TRUE))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Rename object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Rename object failed: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  function delete($dn)
+    {
+    if ($this->conn)
+      {
+      if (@ldap_delete($this->conn, $dn))
+        return TRUE;
+      else
+        raise_error(array("code" => ldap_errno($this->conn),
+                          "type" => "ldap",
+                          "message" => "Delete object failed: ".ldap_error($this->conn)),
+                    TRUE);
+      }
+    else
+      raise_error(array("type" => "ldap",
+                        "message" => "Delete object failed: no connection"),
+                  TRUE);
+    return FALSE;
+    }
+
+  }
+
+// vi: et ts=2 sw=2
+?>
diff --git a/program/include/rcube_mdb2.inc b/program/include/rcube_mdb2.inc
new file mode 100755 (executable)
index 0000000..f37c269
--- /dev/null
@@ -0,0 +1,546 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_mdb2.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   PEAR:DB wrapper class that implements PEAR MDB2 functions           |
+ |   See http://pear.php.net/package/MDB2                                |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Lukas Kahwe Smith <smith@pooteeweet.org>                      |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_mdb2.inc 239 2006-05-18 21:25:11Z roundcube $
+
+*/
+
+
+/**
+ * Obtain the PEAR::DB class that is used for abstraction
+ */
+require_once('MDB2.php');
+
+
+/**
+ * Database independent query interface
+ *
+ * This is a wrapper for the PEAR::MDB2 class
+ *
+ * @package    RoundCube Webmail
+ * @author     David Saez Padros <david@ols.es>
+ * @author     Thomas Bruederli <roundcube@gmail.com>
+ * @author     Lukas Kahwe Smith <smith@pooteeweet.org>
+ * @version    1.16
+ * @link       http://pear.php.net/package/MDB2
+ */
+class rcube_db
+  {
+  var $db_dsnw;               // DSN for write operations
+  var $db_dsnr;               // DSN for read operations
+  var $db_connected = false;  // Already connected ?
+  var $db_mode = '';          // Connection mode
+  var $db_handle = 0;         // Connection handle
+  var $db_error = false;
+  var $db_error_msg = '';
+
+  var $a_query_results = array('dummy');
+  var $last_res_id = 0;
+
+
+  /**
+   * Object constructor
+   *
+   * @param  string  DSN for read/write operations
+   * @param  string  Optional DSN for read only operations
+   */
+  function __construct($db_dsnw, $db_dsnr='')
+    {
+    if ($db_dsnr=='')
+      $db_dsnr=$db_dsnw;
+
+    $this->db_dsnw = $db_dsnw;
+    $this->db_dsnr = $db_dsnr;
+
+    $dsn_array = MDB2::parseDSN($db_dsnw);
+    $this->db_provider = $dsn_array['phptype'];
+    }
+
+
+  /**
+   * PHP 4 object constructor
+   *
+   * @see  rcube_MDB2::__construct
+   */
+  function rcube_db($db_dsnw,$db_dsnr='')
+    {
+    $this->__construct($db_dsnw,$db_dsnr);
+    }
+
+
+  /**
+   * Connect to specific database
+   *
+   * @param  string  DSN for DB connections
+   * @return object  PEAR database handle
+   * @access private
+   */
+  function dsn_connect($dsn)
+    {
+    // Use persistent connections if available
+    $dbh = MDB2::connect($dsn, array('persistent' => TRUE, 'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL));
+
+    if (PEAR::isError($dbh))
+      {
+      $this->db_error = TRUE;
+      $this->db_error_msg = $dbh->getMessage();
+      
+      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                        'message' => $dbh->getMessage()), TRUE, FALSE);
+      }
+
+    else if ($this->db_provider=='sqlite')
+      {
+      $dsn_array = MDB2::parseDSN($dsn);
+      if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials))
+        $this->_sqlite_create_database($dbh, $this->sqlite_initials);
+      }
+
+    return $dbh;
+    }
+
+
+  /**
+   * Connect to appropiate databse
+   * depending on the operation
+   *
+   * @param  string  Connection mode (r|w)
+   * @access public
+   */
+  function db_connect($mode)
+    {
+    $this->db_mode = $mode;
+
+    // Already connected
+    if ($this->db_connected)
+      {
+      // no replication, current connection is ok
+      if ($this->db_dsnw==$this->db_dsnr)
+        return;
+
+      // connected to master, current connection is ok
+      if ($this->db_mode=='w')
+        return;
+
+      // Same mode, current connection is ok
+      if ($this->db_mode==$mode)
+        return;
+      }
+
+    if ($mode=='r')
+      $dsn = $this->db_dsnr;
+    else
+      $dsn = $this->db_dsnw;
+
+    $this->db_handle = $this->dsn_connect($dsn);
+    $this->db_connected = true;
+    }
+
+
+    
+  /**
+   * Getter for error state
+   *
+   * @param  boolean  True on error
+   */
+  function is_error()
+    {
+    return $this->db_error ? $this->db_error_msg : FALSE;
+    }
+    
+
+  /**
+   * Execute a SQL query
+   *
+   * @param  string  SQL query to execute
+   * @param  mixed   Values to be inserted in query
+   * @return number  Query handle identifier
+   * @access public
+   */
+  function query()
+    {
+    $params = func_get_args();
+    $query = array_shift($params);
+
+    return $this->_query($query, 0, 0, $params);
+    }
+
+
+  /**
+   * Execute a SQL query with limits
+   *
+   * @param  string  SQL query to execute
+   * @param  number  Offset for LIMIT statement
+   * @param  number  Number of rows for LIMIT statement
+   * @param  mixed   Values to be inserted in query
+   * @return number  Query handle identifier
+   * @access public
+   */
+  function limitquery()
+    {
+    $params = func_get_args();
+    $query = array_shift($params);
+    $offset = array_shift($params);
+    $numrows = array_shift($params);
+
+    return $this->_query($query, $offset, $numrows, $params);
+    }
+
+
+  /**
+   * Execute a SQL query with limits
+   *
+   * @param  string  SQL query to execute
+   * @param  number  Offset for LIMIT statement
+   * @param  number  Number of rows for LIMIT statement
+   * @param  array   Values to be inserted in query
+   * @return number  Query handle identifier
+   * @access private
+   */
+  function _query($query, $offset, $numrows, $params)
+    {
+    // Read or write ?
+    if (strtolower(trim(substr($query,0,6)))=='select')
+      $mode='r';
+    else
+      $mode='w';
+
+    $this->db_connect($mode);
+
+    if ($this->db_provider == 'sqlite')
+      $this->_sqlite_prepare();
+
+    if ($numrows || $offset)
+      $result = $this->db_handle->setLimit($numrows,$offset);
+
+    if (empty($params))
+        $result = $this->db_handle->query($query);
+    else
+      {
+      $params = (array)$params;
+      $q = $this->db_handle->prepare($query);
+      if ($this->db_handle->isError($q))
+        {
+        $this->db_error = TRUE;
+        $this->db_error_msg = $q->userinfo;
+
+        raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                          'message' => $this->db_error_msg), TRUE, TRUE);
+        }
+      else
+        {
+        $result = $q->execute($params);
+        $q->free();
+        }
+      }
+
+    // add result, even if it's an error
+    return $this->_add_result($result);
+    }
+
+
+  /**
+   * Get number of rows for a SQL query
+   * If no query handle is specified, the last query will be taken as reference
+   *
+   * @param  number  Optional query handle identifier
+   * @return mixed   Number of rows or FALSE on failure
+   * @access public
+   */
+  function num_rows($res_id=NULL)
+    {
+    if (!$this->db_handle)
+      return FALSE;
+
+    if ($result = $this->_get_result($res_id))
+      return $result->numRows();
+    else
+      return FALSE;
+    }
+
+
+  /**
+   * Get number of affected rows fort he last query
+   *
+   * @return mixed   Number of rows or FALSE on failure
+   * @access public
+   */
+  function affected_rows($result = null)
+    {
+    if (!$this->db_handle)
+      return FALSE;
+
+    return $result;
+    }
+
+
+  /**
+   * Get last inserted record ID
+   * For Postgres databases, a sequence name is required
+   *
+   * @param  string  Sequence name for increment
+   * @return mixed   ID or FALSE on failure
+   * @access public
+   */
+  function insert_id($sequence = '')
+    {
+    if (!$this->db_handle || $this->db_mode=='r')
+      return FALSE;
+
+    return $this->db_handle->lastInsertID($sequence);
+    }
+
+
+  /**
+   * Get an associative array for one row
+   * If no query handle is specified, the last query will be taken as reference
+   *
+   * @param  number  Optional query handle identifier
+   * @return mixed   Array with col values or FALSE on failure
+   * @access public
+   */
+  function fetch_assoc($res_id=NULL)
+    {
+    $result = $this->_get_result($res_id);
+    return $this->_fetch_row($result, MDB2_FETCHMODE_ASSOC);
+    }
+
+
+  /**
+   * Get an index array for one row
+   * If no query handle is specified, the last query will be taken as reference
+   *
+   * @param  number  Optional query handle identifier
+   * @return mixed   Array with col values or FALSE on failure
+   * @access public
+   */
+  function fetch_array($res_id=NULL)
+    {
+    $result = $this->_get_result($res_id);
+    return $this->_fetch_row($result, MDB2_FETCHMODE_ORDERED);
+    }
+
+
+  /**
+   * Get co values for a result row
+   *
+   * @param  object  Query result handle
+   * @param  number  Fetch mode identifier
+   * @return mixed   Array with col values or FALSE on failure
+   * @access private
+   */
+  function _fetch_row($result, $mode)
+    {
+    if (PEAR::isError($result))
+      {
+      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                        'message' => $this->db_link->getMessage()), TRUE, FALSE);
+      return FALSE;
+      }
+
+    return $result->fetchRow($mode);
+    }
+
+
+  /**
+   * Formats input so it can be safely used in a query
+   *
+   * @param  mixed   Value to quote
+   * @return string  Quoted/converted string for use in query
+   * @access public
+   */
+  function quote($input, $type = null)
+    {
+    // create DB handle if not available
+    if (!$this->db_handle)
+      $this->db_connect('r');
+
+    // escape pear identifier chars
+    $rep_chars = array('?' => '\?',
+                       '!' => '\!',
+                       '&' => '\&');
+
+    return $this->db_handle->quote($input, $type);
+    }
+
+
+  /**
+   * Quotes a string so it can be safely used as a table or column name
+   *
+   * @param  string  Value to quote
+   * @return string  Quoted string for use in query
+   * @deprecated     Replaced by rcube_MDB2::quote_identifier
+   * @see            rcube_MDB2::quote_identifier
+   * @access public
+   */
+  function quoteIdentifier($str)
+       {
+    return $this->quote_identifier($str);
+       }
+
+
+  /**
+   * Quotes a string so it can be safely used as a table or column name
+   *
+   * @param  string  Value to quote
+   * @return string  Quoted string for use in query
+   * @access public
+   */
+  function quote_identifier($str)
+    {
+    if (!$this->db_handle)
+      $this->db_connect('r');
+
+    return $this->db_handle->quoteIdentifier($str);
+    }
+
+
+  /**
+   * Return SQL statement to convert a field value into a unix timestamp
+   *
+   * @param  string  Field name
+   * @return string  SQL statement to use in query
+   * @access public
+   */
+  function unixtimestamp($field)
+    {
+    switch($this->db_provider)
+      {
+      case 'pgsql':
+        return "EXTRACT (EPOCH FROM $field)";
+        break;
+
+      default:
+        return "UNIX_TIMESTAMP($field)";
+      }
+    }
+
+
+  /**
+   * Return SQL statement to convert from a unix timestamp
+   *
+   * @param  string  Field name
+   * @return string  SQL statement to use in query
+   * @access public
+   */
+  function fromunixtime($timestamp)
+    {
+    switch($this->db_provider)
+      {
+      case 'mysqli':
+      case 'mysql':
+      case 'sqlite':
+        return "FROM_UNIXTIME($timestamp)";
+
+      default:
+        return date("'Y-m-d H:i:s'", $timestamp);
+      }
+    }
+
+
+  /**
+   * Adds a query result and returns a handle ID
+   *
+   * @param  object  Query handle
+   * @return mixed   Handle ID or FALE on failure
+   * @access private
+   */
+  function _add_result($res)
+    {
+    // sql error occured
+    if (PEAR::isError($res))
+      {
+      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+                        'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE);
+      return FALSE;
+      }
+    else
+      {
+      $res_id = sizeof($this->a_query_results);
+      $this->a_query_results[$res_id] = $res;
+      $this->last_res_id = $res_id;
+      return $res_id;
+      }
+    }
+
+
+  /**
+   * Resolves a given handle ID and returns the according query handle
+   * If no ID is specified, the last ressource handle will be returned
+   *
+   * @param  number  Handle ID
+   * @return mixed   Ressource handle or FALE on failure
+   * @access private
+   */
+  function _get_result($res_id=NULL)
+    {
+    if ($res_id==NULL)
+      $res_id = $this->last_res_id;
+
+     if ($res_id && isset($this->a_query_results[$res_id]))
+       return $this->a_query_results[$res_id];
+     else
+       return FALSE;
+    }
+
+
+  /**
+   * Create a sqlite database from a file
+   *
+   * @param  object  SQLite database handle
+   * @param  string  File path to use for DB creation
+   * @access private
+   */
+  function _sqlite_create_database($dbh, $file_name)
+    {
+    if (empty($file_name) || !is_string($file_name))
+      return;
+
+    $data = '';
+    if ($fd = fopen($file_name, 'r'))
+      {
+      $data = fread($fd, filesize($file_name));
+      fclose($fd);
+      }
+
+    if (strlen($data))
+      sqlite_exec($dbh->connection, $data);
+    }
+
+
+  /**
+   * Add some proprietary database functions to the current SQLite handle
+   * in order to make it MySQL compatible
+   *
+   * @access private
+   */
+  function _sqlite_prepare()
+    {
+    include_once('include/rcube_sqlite.inc');
+
+    // we emulate via callback some missing MySQL function
+    sqlite_create_function($this->db_handle->connection, "from_unixtime", "rcube_sqlite_from_unixtime");
+    sqlite_create_function($this->db_handle->connection, "unix_timestamp", "rcube_sqlite_unix_timestamp");
+    sqlite_create_function($this->db_handle->connection, "now", "rcube_sqlite_now");
+    sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5");
+    }
+
+
+  }  // end class rcube_db
+
+?>
\ No newline at end of file
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
new file mode 100644 (file)
index 0000000..ee95965
--- /dev/null
@@ -0,0 +1,1427 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | rcube_shared.inc                                                      |
+ |                                                                       |
+ | This file is part of the RoundCube PHP suite                          |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | CONTENTS:                                                             |
+ |   Shared functions and classes used in PHP projects                   |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_shared.inc 288 2006-07-31 22:51:23Z thomasb $
+
+*/
+
+
+// ********* round cube schared classes *********
+
+class rcube_html_page
+  {
+  var $css;
+  
+  var $scripts_path = '';
+  var $script_files = array();
+  var $scripts = array();
+  var $charset = 'ISO-8859-1';
+  
+  var $script_tag_file = "<script type=\"text/javascript\" src=\"%s%s\"></script>\n";
+  var $script_tag      = "<script type=\"text/javascript\">\n<!--\n%s\n\n//-->\n</script>\n";
+  var $default_template = "<html>\n<body></body>\n</html>";
+  
+  var $title = '';
+  var $header = '';
+  var $footer = '';
+  var $body = '';
+  var $body_attrib = array();
+  var $meta_tags = array();
+
+
+  // PHP 5 constructor
+  function __construct()
+    {
+    $this->css = new rcube_css();
+    }
+
+  // PHP 4 compatibility
+  function rcube_html_page()
+    {
+    $this->__construct();
+    }
+
+
+  function include_script($file, $position='head')
+    {
+    static $sa_files = array();
+    
+    if (in_array($file, $sa_files))
+      return;
+      
+    if (!is_array($this->script_files[$position]))
+      $this->script_files[$position] = array();
+      
+    $this->script_files[$position][] = $file;
+    }
+    
+  
+  function add_script($script, $position='head')
+    {
+    if (!isset($this->scripts[$position]))
+      $this->scripts[$position] = '';
+
+    $this->scripts[$position] .= "\n$script";
+    }
+
+
+  function set_title($t)
+    {
+    $this->title = $t;
+    }
+
+
+  function set_charset($charset)
+    {
+    global $MBSTRING;
+    
+    $this->charset = $charset;
+    
+    if ($MBSTRING && function_exists("mb_internal_encoding"))
+      {
+      if(!@mb_internal_encoding($charset))
+        $MBSTRING = FALSE;
+      }
+    }
+
+  function get_charset()
+    {
+    return $this->charset;
+    }
+
+
+  function reset()
+    {
+    $this->css = new rcube_css();
+    $this->script_files = array();
+    $this->scripts = array();
+    $this->title = '';
+    }
+
+
+  function write($templ='', $base_path='')
+    {
+    $output = empty($templ) ? $this->default_template : trim($templ);
+  
+    // set default page title
+    if (!strlen($this->title))
+      $this->title = 'RoundCube Mail';
+  
+    // replace specialchars in content
+    $__page_title = rep_specialchars_output($this->title, 'html', 'show', FALSE);
+    $__page_header = $__page_body = $__page_footer = '';
+    
+    
+    // include meta tag with charset
+    if (!empty($this->charset))
+      {
+      header('Content-Type: text/html; charset='.$this->charset);
+      $__page_header = '<meta http-equiv="content-type" content="text/html; charset='.$this->charset.'" />'."\n";
+      }
+  
+  
+    // definition of the code to be placed in the document header and footer
+    if (is_array($this->script_files['head']))
+      foreach ($this->script_files['head'] as $file)
+        $__page_header .= sprintf($this->script_tag_file, $this->scripts_path, $file);
+
+    if (strlen($this->scripts['head']))
+      $__page_header .= sprintf($this->script_tag, $this->scripts['head']);
+          
+    if (is_array($this->script_files['foot']))
+      foreach ($this->script_files['foot'] as $file)
+        $__page_footer .= sprintf($this->script_tag_file, $this->scripts_path, $file);
+
+    if (strlen($this->scripts['foot']))
+      $__page_footer .= sprintf($this->script_tag, $this->scripts['foot']);
+
+
+    $__page_header .= $this->css->show();
+
+  
+    // find page header
+    if($hpos = strpos(strtolower($output), '</head>'))
+      $__page_header .= "\n";
+    else 
+      {
+      if (!is_numeric($hpos))
+        $hpos = strpos(strtolower($output), '<body');
+      if (!is_numeric($hpos) && ($hpos = strpos(strtolower($output), '<html')))
+        {
+        while($output[$hpos]!='>')
+        $hpos++;
+        $hpos++;
+        }
+  
+      $__page_header = "<head>\n<title>$__page_title</title>\n$__page_header\n</head>\n";
+      }
+  
+    // add page hader
+    if($hpos)
+      $output = substr($output,0,$hpos) . $__page_header . substr($output,$hpos,strlen($output));
+    else
+      $output = $__page_header . $output;
+  
+  
+    // find page body
+    if($bpos = strpos(strtolower($output), '<body'))
+      {
+      while($output[$bpos]!='>') $bpos++;
+      $bpos++;
+      }
+    else
+      $bpos = strpos(strtolower($output), '</head>')+7;
+  
+    // add page body
+    if($bpos && $__page_body)
+      $output = substr($output,0,$bpos) . "\n$__page_body\n" . substr($output,$bpos,strlen($output));
+  
+  
+    // find and add page footer
+    if(($fpos = strpos(strtolower($output), '</body>')) || ($fpos = strpos(strtolower($output), '</html>')))
+      $output = substr($output,0,$fpos) . "$__page_footer\n" . substr($output,$fpos,strlen($output));
+    else
+      $output .= "\n$__page_footer";
+  
+  
+    // reset those global vars
+    $__page_header = $__page_footer = '';
+  
+  
+    // correct absolute pathes in images and other tags
+    $output = preg_replace('/(src|href|background)=(["\']?)(\/[a-z0-9_\-]+)/Ui', "\\1=\\2$base_path\\3", $output);
+    $output = str_replace('$__skin_path', $base_path, $output);
+  
+    print rcube_charset_convert($output, 'UTF-8', $this->charset);
+    }
+    
+    
+  function _parse($templ)
+    {
+    
+    }
+  }
+
+
+
+
+class rcube_css
+  {
+  var $css_data = array();
+
+  var $css_groups = array();
+
+  var $include_files = array();
+
+  var $grouped_output = TRUE;
+
+  var $content_type = 'text/css';
+
+  var $base_path = '';
+
+  var $indent_chars = "\t";
+
+
+  // add or overwrite a css definition
+  // either pass porperty and value as separate arguments
+  // or provide an associative array as second argument
+  function set_style($selector, $property, $value='')
+    {
+    $a_elements = $this->_parse_selectors($selector);
+    foreach ($a_elements as $element)
+      {
+      if (!is_array($property))
+        $property = array($property => $value);
+
+      foreach ($property as $name => $value)
+        $this->css_data[$element][strtolower($name)] = $value;
+      }
+
+    // clear goups array
+    $this->css_groups = array();
+    }
+
+
+  // unset a style property
+  function remove_style($selector, $property)
+    {
+    if (!is_array($property))
+      $property = array($property);
+
+    foreach ($property as $key)
+      unset($this->css_data[$selector][strtolower($key)]);
+
+    // clear goups array
+    $this->css_groups = array();
+    }
+
+
+  // define base path for external css files
+  function set_basepath($path)
+    {
+    $this->base_path = preg_replace('/\/$/', '', $path);
+    }
+
+
+  // enable/disable grouped output
+  function set_grouped_output($grouped)
+    {
+    $this->grouped_output = $grouped;
+    }
+
+
+  // add a css file as external source
+  function include_file($filename, $media='')
+    {
+    // include multiple files
+    if (is_array($filename))
+      {
+      foreach ($filename as $file)
+        $this->include_file($file, $media);
+      }
+    // add single file
+    else if (!in_array($filename, $this->include_files))
+      $this->include_files[] = array('file' => $filename,
+                                     'media' => $media);
+    }
+
+
+  // parse css code
+  function import_string($str)
+    {
+    $ret = FALSE;
+    if (strlen($str))
+      $ret = $this->_parse($str);
+
+    return $ret;
+    }
+
+
+  // open and parse a css file
+  function import_file($file)
+    {
+    $ret = FALSE;
+
+    if (!is_file($file))
+      return $ret;
+
+    // for php version >= 4.3.0
+    if (function_exists('file_get_contents'))
+      $ret = $this->_parse(file_get_contents($file));
+
+    // for order php versions
+    else if ($fp = fopen($file, 'r'))
+      {
+      $ret = $this->_parse(fread($fp, filesize($file)));
+      fclose($fp);
+      }
+
+    return $ret;
+    }
+
+
+  // copy all properties inherited from superior styles to a specific selector
+  function copy_inherited_styles($selector)
+    {
+    // get inherited props from body and tag/class selectors
+    $css_props = $this->_get_inherited_styles($selector);
+
+    // write modified props back and clear goups array
+    if (sizeof($css_props))
+      {
+      $this->css_data[$selector] = $css_props;
+      $this->css_groups = array();
+      }
+    }
+
+
+  // return css definition for embedding in HTML
+  function show()
+    {
+    $out = '';
+
+    // include external css files
+    if (sizeof($this->include_files))
+      foreach ($this->include_files as $file_arr)
+      $out .= sprintf('<link rel="stylesheet" type="%s" href="%s"%s>'."\n",
+                        $this->content_type,
+                        $this->_get_file_path($file_arr['file']),
+                        $file_arr['media'] ? ' media="'.$file_arr['media'].'"' : '');
+
+
+    // compose css string
+    if (sizeof($this->css_data))
+      $out .= sprintf("<style type=\"%s\">\n<!--\n\n%s-->\n</style>",
+                      $this->content_type,
+                      $this->to_string());
+
+
+    return $out;
+    }
+
+
+  // return valid css code of the current styles grid
+  function to_string($selector=NULL)
+    {
+    // return code for a single selector
+    if ($selector)
+      {
+      $indent_str = $this->indent_chars;
+      $this->indent_chars = '';
+
+      $prop_arr = $this->to_array($selector);
+      $out = $this->_style2string($prop_arr, TRUE);
+
+      $this->indent_chars = $indent_str;
+      }
+
+    // compose css code for complete data grid
+    else
+      {
+      $out = '';
+      $css_data = $this->to_array();
+
+      foreach ($css_data as $key => $prop_arr)
+        $out .= sprintf("%s {\n%s}\n\n",
+                        $key,
+                        $this->_style2string($prop_arr, TRUE));
+      }
+
+    return $out;
+    }
+
+
+  // return a single-line string of a css definition
+  function to_inline($selector)
+    {
+    if ($this->css_data[$selector])
+      return str_replace('"', '\\"', $this->_style2string($this->css_data[$selector], FALSE));
+    }
+
+
+  // return an associative array with selector(s) as key and styles array as value
+  function to_array($selector=NULL)
+    {
+    if (!$selector && $this->grouped_output)
+      {
+      // build groups if desired
+      if (!sizeof($this->css_groups))
+        $this->_build_groups();
+
+      // modify group array to get an array(selector => properties)
+      $out_arr = array();
+      foreach ($this->css_groups as $group_arr)
+        {
+        $key = join(', ', $group_arr['selectors']);
+        $out_arr[$key] = $group_arr['properties'];
+        }
+      }
+    else
+      $out_arr = $this->css_data;
+
+    return $selector ? $out_arr[$selector] : $out_arr;
+    }
+
+
+  // create a css file
+  function to_file($filepath)
+    {
+    if ($fp = fopen($filepath, 'w'))
+      {
+      fwrite($fp, $this->to_string());
+      fclose($fp);
+      return TRUE;
+      }
+
+    return FALSE;
+    }
+
+
+  // alias method for import_string() [DEPRECATED]
+  function add($str)
+    {
+    $this->import_string($str);
+    }
+
+  // alias method for to_string() [DEPRECATED]
+  function get()
+    {
+    return $this->to_string();
+    }
+
+
+
+  // ******** private methods ********
+
+
+  // parse a string and add styles to internal data grid
+  function _parse($str)
+    {
+    // remove comments
+    $str = preg_replace("/\/\*(.*)?\*\//Usi", '', $str);
+
+    // parse style definitions
+    if (!preg_match_all ('/([a-z0-9\.#*:_][a-z0-9\.\-_#:*,\[\]\(\)\s\"\'\+\|>~=]+)\s*\{([^\}]*)\}/ims', $str, $matches, PREG_SET_ORDER))
+      return FALSE;
+
+
+    foreach ($matches as $match_arr)
+      {
+      // split selectors into array
+      $a_keys = $this->_parse_selectors(trim($match_arr[1]));
+
+      // parse each property of an element
+      $codes = explode(";", trim($match_arr[2]));
+      foreach ($codes as $code)
+        {
+        if (strlen(trim($code))>0)
+          {
+          // find the property and the value
+          if (!($sep = strpos($code, ':')))
+            continue;
+
+          $property = strtolower(trim(substr($code, 0, $sep)));
+          $value    = trim(substr($code, $sep+1));
+
+          // add the property to the object array
+          foreach ($a_keys as $key)
+            $this->css_data[$key][$property] = $value;
+          }
+        }
+      }
+
+    // clear goups array
+    if (sizeof($matches))
+      {
+      $this->css_groups = array();
+      return TRUE;
+      }
+
+    return FALSE;
+    }
+
+
+  // split selector group
+  function _parse_selectors($selector)
+    {
+    // trim selector and remove multiple spaces
+    $selector = preg_replace('/\s+/', ' ', trim($selector));
+
+    if (strpos($selector, ','))
+      return preg_split('/[\t\s\n\r]*,[\t\s\n\r]*/mi', $selector);
+    else
+      return array($selector);
+    }
+
+
+  // compare identical styles and make groups
+  function _build_groups()
+    {
+    // clear group array
+    $this->css_groups = array();
+    $string_group_map = array();
+
+    // bulild css string for each selector and check if the same is already defines
+    foreach ($this->css_data as $selector => $prop_arr)
+      {
+      // make shure to compare props in the same order
+      ksort($prop_arr);
+      $compare_str = preg_replace('/[\s\t]+/', '', $this->_style2string($prop_arr, FALSE));
+
+      // add selector to extisting group
+      if (isset($string_group_map[$compare_str]))
+        {
+        $group_index = $string_group_map[$compare_str];
+        $this->css_groups[$group_index]['selectors'][] = $selector;
+        }
+
+      // create new group
+      else
+        {
+        $i = sizeof($this->css_groups);
+        $string_group_map[$compare_str] = $i;
+        $this->css_groups[$i] = array('selectors' => array($selector),
+                                      'properties' => $this->css_data[$selector]);
+        }
+      }
+    }
+
+
+  // convert the prop array into a valid css definition
+  function _style2string($prop_arr, $multiline=TRUE)
+    {
+    $out = '';
+    $delm   = $multiline ? "\n" : '';
+    $spacer = $multiline ? ' ' : '';
+    $indent = $multiline ? $this->indent_chars : '';
+
+    if (is_array($prop_arr))
+      foreach ($prop_arr as $prop => $value)
+        if (strlen($value))
+          $out .= sprintf('%s%s:%s%s;%s',
+                          $indent,
+                          $prop,
+                          $spacer,
+                          $value,
+                          $delm);
+
+    return $out;
+    }
+
+
+  // copy all properties inherited from superior styles to a specific selector
+  function _get_inherited_styles($selector, $loop=FALSE)
+    {
+    $css_props = $this->css_data[$selector] ? $this->css_data[$selector] : array();
+
+    // get styles from tag selector
+    if (preg_match('/(([a-z0-9]*)(\.[^\s]+)?)$/i', $selector, $regs))
+      {
+      $sel = $regs[1];
+      $tagname = $regs[2];
+      $class = $regs[3];
+
+      if ($sel && is_array($this->css_data[$sel]))
+        $css_props = $this->_merge_styles($this->css_data[$sel], $css_props);
+
+      if ($class && is_array($this->css_data[$class]))
+        $css_props = $this->_merge_styles($this->css_data[$class], $css_props);
+
+      if ($tagname && is_array($this->css_data[$tagname]))
+        $css_props = $this->_merge_styles($this->css_data[$tagname], $css_props);
+      }
+
+    // analyse inheritance
+    if (strpos($selector, ' '))
+      {
+      $a_hier = split(' ', $selector);
+      if (sizeof($a_hier)>1)
+        {
+        array_pop($a_hier);
+        $base_selector = join(' ', $a_hier);
+
+        // call this method recursively
+        $new_props = $this->_get_inherited_styles($base_selector, TRUE);
+        $css_props = $this->_merge_styles($new_props, $css_props);
+        }
+      }
+
+    // get body style
+    if (!$loop && is_array($this->css_data['body']))
+      $css_props = $this->_merge_styles($this->css_data['body'], $css_props);
+
+    return $css_props;
+    }
+
+
+  // merge two arrays with style properties together like a browser would do
+  function _merge_styles($one, $two)
+    {
+    // these properties are additive
+    foreach (array('text-decoration') as $prop)
+      if ($one[$prop] && $two[$prop])
+        {
+        // if value contains 'none' it's ignored
+        if (strstr($one[$prop], 'none'))
+          continue;
+        else if (strstr($two[$prop], 'none'))
+          unset($two[$prop]);
+
+        $a_values_one = split(' ', $one[$prop]);
+        $a_values_two = split(' ', $two[$prop]);
+        $two[$prop] = join(' ', array_unique(array_merge($a_values_one, $a_values_two)));
+        }
+
+    return array_merge($one, $two);
+    }
+
+
+  // resolve file path
+  function _get_file_path($file)
+    {
+    if (!$this->base_path && $GLOBALS['CSS_PATH'])
+      $this->set_basepath($GLOBALS['CSS_PATH']);
+
+    $base = ($file{0}=='/' || $file{0}=='.' || substr($file, 0, 7)=='http://') ? '' :
+            ($this->base_path ? $this->base_path.'/' : '');
+    return $base.$file;
+    }
+
+  }
+
+
+
+class base_form_element
+  {
+  var $uppertags = FALSE;
+  var $upperattribs = FALSE;
+  var $upperprops = FALSE;
+  var $newline = FALSE;
+  
+  var $attrib = array();
+
+
+  // create string with attributes
+  function create_attrib_string($tagname='')
+    {
+    if (!sizeof($this->attrib))
+      return '';
+
+    if ($this->name!='')
+      $this->attrib['name'] = $this->name;
+
+    $attrib_arr = array();
+    foreach ($this->attrib as $key => $value)
+      {
+      // don't output some internally used attributes
+      if (in_array($key, array('form', 'quicksearch')))
+        continue;
+
+      // skip if size if not numeric
+      if (($key=='size' && !is_numeric($value)))
+        continue;
+        
+      // skip empty eventhandlers
+      if ((strpos($key,'on')===0 && $value==''))
+        continue;
+
+      // encode textarea content
+      if ($key=='value')
+        $value = rep_specialchars_output($value, 'html', 'replace', FALSE);
+
+      // attributes with no value
+      if (in_array($key, array('checked', 'multiple', 'disabled', 'selected')))
+        {
+        if ($value)
+          $attrib_arr[] = $key;
+        }
+      // don't convert size of value attribute
+      else if ($key=='value')
+        $attrib_arr[] = sprintf('%s="%s"', $this->_conv_case($key, 'attrib'), $value, 'value');
+        
+      // regular tag attributes
+      else
+        $attrib_arr[] = sprintf('%s="%s"', $this->_conv_case($key, 'attrib'), $this->_conv_case($value, 'value'));
+      }
+
+    return sizeof($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
+    }
+    
+    
+  // convert tags and attributes to upper-/lowercase
+  // $type can either be "tag" or "attrib"
+  function _conv_case($str, $type='attrib')
+    {
+    if ($type == 'tag')
+      return $this->uppertags ? strtoupper($str) : strtolower($str);
+    else if ($type == 'attrib')
+      return $this->upperattribs ? strtoupper($str) : strtolower($str);
+    else if ($type == 'value')
+      return $this->upperprops ? strtoupper($str) : strtolower($str);
+    }    
+  }
+
+
+class input_field extends base_form_element
+  {
+  var $type = 'text';
+  
+  // PHP 5 constructor
+  function __construct($attrib=NULL)
+    {
+    if (is_array($attrib))
+      $this->attrib = $attrib;
+
+    if ($attrib['type'])
+      $this->type = $attrib['type'];    
+
+    if ($attrib['newline'])
+      $this->newline = TRUE;    
+    }
+
+  // PHP 4 compatibility
+  function input_field($attrib=array())
+    {
+    $this->__construct($attrib);
+    }  
+
+  // compose input tag
+  function show($value=NULL, $attrib=NULL)
+    {
+    // overwrite object attributes
+    if (is_array($attrib))
+      $this->attrib = array_merge($this->attrib, $attrib);
+
+    // set value attribute
+    if ($value!==NULL)
+      $this->attrib['value'] = $value;
+
+    $this->attrib['type'] = $this->type;
+
+    // return final tag
+    return sprintf('<%s%s />%s',
+                   $this->_conv_case('input', 'tag'),
+                   $this->create_attrib_string(),
+                   ($this->newline ? "\n" : ""));    
+    }  
+  }
+
+
+class textfield extends input_field
+  {
+  var $type = 'text';
+  }
+
+class passwordfield extends input_field
+  {
+  var $type = 'password';
+  }
+
+class radiobutton extends input_field
+  {
+  var $type = 'radio';
+  }
+
+class checkbox extends input_field
+  {
+  var $type = 'checkbox';
+
+
+  function show($value='', $attrib=NULL)
+    {
+    // overwrite object attributes
+    if (is_array($attrib))
+      $this->attrib = array_merge($this->attrib, $attrib);    
+
+    $this->attrib['type'] = $this->type;
+
+    if ($value && (string)$value==(string)$this->attrib['value'])
+      $this->attrib['checked'] = TRUE;
+    else
+      $this->attrib['checked'] = FALSE;
+
+    // return final tag
+    return sprintf('<%s%s />%s',
+                   $this->_conv_case('input', 'tag'),
+                   $this->create_attrib_string(),
+                   ($this->newline ? "\n" : ""));    
+    }
+  }
+
+
+class textarea extends base_form_element
+  {
+  // PHP 5 constructor
+  function __construct($attrib=array())
+    {
+    $this->attrib = $attrib;
+
+    if ($attrib['newline'])
+      $this->newline = TRUE;    
+    }
+
+  // PHP 4 compatibility
+  function textarea($attrib=array())
+    {
+    $this->__construct($attrib);
+    }
+    
+  function show($value='', $attrib=NULL)
+    {
+    // overwrite object attributes
+    if (is_array($attrib))
+      $this->attrib = array_merge($this->attrib, $attrib);
+    
+    // take value attribute as content
+    if ($value=='')
+      $value = $this->attrib['value'];
+    
+    // make shure we don't print the value attribute
+    if (isset($this->attrib['value']))
+      unset($this->attrib['value']);
+
+    if (strlen($value))
+      $value = rep_specialchars_output($value, 'html', 'replace', FALSE);
+    
+    // return final tag
+    return sprintf('<%s%s>%s</%s>%s',
+                   $this->_conv_case('textarea', 'tag'),
+                   $this->create_attrib_string(),
+                   $value,
+                   $this->_conv_case('textarea', 'tag'),
+                   ($this->newline ? "\n" : ""));       
+    }
+  }
+
+
+class hiddenfield extends base_form_element
+  {
+  var $fields_arr = array();
+  var $newline = TRUE;
+
+  // PHP 5 constructor
+  function __construct($attrib=NULL)
+    {
+    if (is_array($attrib))
+      $this->add($attrib);
+    }
+
+  // PHP 4 compatibility
+  function hiddenfield($attrib=NULL)
+    {
+    $this->__construct($attrib);
+    }
+
+  // add a hidden field to this instance
+  function add($attrib)
+    {
+    $this->fields_arr[] = $attrib;
+    }
+
+
+  function show()
+    {
+    $out = '';
+    foreach ($this->fields_arr as $attrib)
+      {
+      $this->attrib = $attrib;
+      $this->attrib['type'] = 'hidden';
+      
+      $out .= sprintf('<%s%s />%s',
+                   $this->_conv_case('input', 'tag'),
+                   $this->create_attrib_string(),
+                   ($this->newline ? "\n" : ""));   
+      }
+
+    return $out;
+    }
+  }
+
+
+class select extends base_form_element
+  {
+  var $options = array();
+
+  /*
+  syntax:
+  -------
+  // create instance. arguments are used to set attributes of select-tag
+  $select = new select(array('name' => 'fieldname'));
+
+  // add one option
+  $select->add('Switzerland', 'CH');
+
+  // add multiple options
+  $select->add(array('Switzerland', 'Germany'),
+               array('CH', 'DE'));
+
+  // add 10 blank options with 50 chars
+  // used to fill with javascript (necessary for 4.x browsers)
+  $select->add_blank(10, 50);
+
+  // generate pulldown with selection 'Switzerland'  and return html-code
+  // as second argument the same attributes available to instanciate can be used
+  print $select->show('CH');
+  */
+
+  // PHP 5 constructor
+  function __construct($attrib=NULL)
+    {
+    if (is_array($attrib))
+      $this->attrib = $attrib;
+
+    if ($attrib['newline'])
+      $this->newline = TRUE;
+    }
+
+  // PHP 4 compatibility
+  function select($attrib=NULL)
+    {
+    $this->__construct($attrib);
+    }
+
+
+  function add($names, $values=NULL)
+    {
+    if (is_array($names))
+      {
+      foreach ($names as $i => $text)
+        $this->options[] = array('text' => $text, 'value' => (string)$values[$i]);
+      }
+    else
+      {
+      $this->options[] = array('text' => $names, 'value' => (string)$values);
+      }
+    }
+
+    
+  function add_blank($nr, $width=0)
+    {
+    $text = $width ? str_repeat('&nbsp;', $width) : '';
+    
+    for ($i=0; $i < $nr; $i++)
+      $this->options[] = array('text' => $text);
+    }
+
+  
+  function show($select=array(), $attrib=NULL)
+    {
+    $options_str = "\n";
+    $value_str = $this->_conv_case(' value="%s"', 'attrib');
+    
+    if (!is_array($select))
+      $select = array((string)$select);
+    
+    foreach ($this->options as $option)
+      {
+      $selected = ((strlen($option['value']) && in_array($option['value'], $select, TRUE)) ||
+                   (in_array($option['text'], $select, TRUE))) ? $this->_conv_case(' selected', 'attrib') : '';
+                  
+      $options_str .= sprintf("<%s%s%s>%s</%s>\n",
+                             $this->_conv_case('option', 'tag'),
+                             strlen($option['value']) ? sprintf($value_str, $option['value']) : '',
+                             $selected, 
+                             rep_specialchars_output($option['text'], 'html', 'replace', FALSE),
+                             $this->_conv_case('option', 'tag'));
+      }
+                             
+    // return final tag
+    return sprintf('<%s%s>%s</%s>%s',
+                   $this->_conv_case('select', 'tag'),
+                   $this->create_attrib_string(),
+                   $options_str,
+                   $this->_conv_case('select', 'tag'),
+                   ($this->newline ? "\n" : ""));    
+    }
+  }
+
+
+
+
+// ********* rcube schared functions *********
+
+
+// provide details about the client's browser
+function rcube_browser()
+  {
+  $HTTP_USER_AGENT = $_SERVER['HTTP_USER_AGENT'];
+
+  $bw['ver'] = 0;
+  $bw['win'] = stristr($HTTP_USER_AGENT, 'win');
+  $bw['mac'] = stristr($HTTP_USER_AGENT, 'mac');
+  $bw['linux'] = stristr($HTTP_USER_AGENT, 'linux');
+  $bw['unix']  = stristr($HTTP_USER_AGENT, 'unix');
+
+  $bw['ns4'] = stristr($HTTP_USER_AGENT, 'mozilla/4') && !stristr($HTTP_USER_AGENT, 'msie');
+  $bw['ns']  = ($bw['ns4'] || stristr($HTTP_USER_AGENT, 'netscape'));
+  $bw['ie']  = stristr($HTTP_USER_AGENT, 'msie');
+  $bw['mz']  = stristr($HTTP_USER_AGENT, 'mozilla/5');
+  $bw['opera'] = stristr($HTTP_USER_AGENT, 'opera');
+  $bw['safari'] = stristr($HTTP_USER_AGENT, 'safari');
+
+  if($bw['ns'])
+    {
+    $test = eregi("mozilla\/([0-9\.]+)", $HTTP_USER_AGENT, $regs);
+    $bw['ver'] = $test ? (float)$regs[1] : 0;
+    }
+  if($bw['mz'])
+    {
+    $test = ereg("rv:([0-9\.]+)", $HTTP_USER_AGENT, $regs);
+    $bw['ver'] = $test ? (float)$regs[1] : 0;
+    }
+  if($bw['ie'])
+    {
+    $test = eregi("msie ([0-9\.]+)", $HTTP_USER_AGENT, $regs);
+    $bw['ver'] = $test ? (float)$regs[1] : 0;
+    }
+  if($bw['opera'])
+    {
+    $test = eregi("opera ([0-9\.]+)", $HTTP_USER_AGENT, $regs);
+    $bw['ver'] = $test ? (float)$regs[1] : 0;
+    }
+
+  if(eregi(" ([a-z]{2})-([a-z]{2})", $HTTP_USER_AGENT, $regs))
+    $bw['lang'] =  $regs[1];
+  else
+    $bw['lang'] =  'en';
+
+  $bw['dom'] = ($bw['mz'] || $bw['safari'] || ($bw['ie'] && $bw['ver']>=5) || ($bw['opera'] && $bw['ver']>=7));
+  $bw['pngalpha'] = $bw['mz'] || $bw['safari'] || ($bw['ie'] && $bw['ver']>=5.5) ||
+                    ($bw['ie'] && $bw['ver']>=5 && $bw['mac']) || ($bw['opera'] && $bw['ver']>=7) ? TRUE : FALSE;
+
+  return $bw;
+  }
+
+
+// get text in the desired language from the language file
+function rcube_label($attrib)
+  {
+  global $sess_user_lang, $INSTALL_PATH, $OUTPUT;
+  static $sa_text_data, $s_language, $utf8_decode;
+
+  // extract attributes
+  if (is_string($attrib))
+    $attrib = array('name' => $attrib);
+    
+  $nr = is_numeric($attrib['nr']) ? $attrib['nr'] : 1;
+  $vars = isset($attrib['vars']) ? $attrib['vars'] : '';
+
+  $command_name = strlen($attrib['command']) ? $attrib['command'] : NULL;
+  $alias = $attrib['name'] ? $attrib['name'] : ($command_name && $command_label_map[$command_name] ? $command_label_map[$command_name] : '');
+
+
+  // load localized texts
+  if (!$sa_text_data || $s_language != $sess_user_lang)
+    {
+    $sa_text_data = array();
+    
+    // get english labels (these should be complete)
+    @include($INSTALL_PATH.'program/localization/en_US/labels.inc');
+    @include($INSTALL_PATH.'program/localization/en_US/messages.inc');
+
+    if (is_array($labels))
+      $sa_text_data = $labels;
+    if (is_array($messages))
+      $sa_text_data = array_merge($sa_text_data, $messages);
+    
+    // include user language files
+    if ($sess_user_lang!='en' && is_dir($INSTALL_PATH.'program/localization/'.$sess_user_lang))
+      {
+      include_once($INSTALL_PATH.'program/localization/'.$sess_user_lang.'/labels.inc');
+      include_once($INSTALL_PATH.'program/localization/'.$sess_user_lang.'/messages.inc');
+
+      if (is_array($labels))
+        $sa_text_data = array_merge($sa_text_data, $labels);
+      if (is_array($messages))
+        $sa_text_data = array_merge($sa_text_data, $messages);
+      }
+      
+    $s_language = $sess_user_lang;
+    }
+
+  // text does not exist
+  if (!($text_item = $sa_text_data[$alias]))
+    {
+    /*
+    raise_error(array('code' => 500,
+                      'type' => 'php',
+                      'line' => __LINE__,
+                      'file' => __FILE__,
+                      'message' => "Missing localized text for '$alias' in '$sess_user_lang'"), TRUE, FALSE);
+    */
+    return "[$alias]";
+    }
+
+  // make text item array 
+  $a_text_item = is_array($text_item) ? $text_item : array('single' => $text_item);
+
+  // decide which text to use
+  if ($nr==1)
+    $text = $a_text_item['single'];
+  else if ($nr>0)
+    $text = $a_text_item['multiple'];
+  else if ($nr==0)
+    {
+    if ($a_text_item['none'])
+      $text = $a_text_item['none'];
+    else if ($a_text_item['single'])
+      $text = $a_text_item['single'];
+    else if ($a_text_item['multiple'])
+      $text = $a_text_item['multiple'];
+    }
+
+  // default text is single
+  if ($text=='')
+    $text = $a_text_item['single'];
+
+  // replace vars in text
+  if (is_array($attrib['vars']))
+    {
+    foreach ($attrib['vars'] as $var_key=>$var_value)
+      $a_replace_vars[substr($var_key, 0, 1)=='$' ? substr($var_key, 1) : $var_key] = $var_value;
+    }
+
+  if ($a_replace_vars)
+    $text = preg_replace('/\${?([_a-z]{1}[_a-z0-9]*)}?/ei', '$a_replace_vars["\1"]', $text);
+
+  // remove variables in text which were not available in arg $vars and $nr
+  eval("\$text = <<<EOF
+$text
+EOF;
+");
+
+  // format output
+  if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
+    return ucfirst($text);
+  else if ($attrib['uppercase'])
+    return strtoupper($text);
+  else if ($attrib['lowercase'])
+    return strtolower($text);
+  else
+    return $text;
+
+  return $text;
+  }
+
+
+// send HTTP header for no-cacheing steps
+function send_nocacheing_headers()
+  {
+  if (headers_sent())
+    return;
+
+  header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
+  header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
+  header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
+  header("Pragma: no-cache");
+  }
+
+
+// send header with expire date 30 days in future
+function send_future_expire_header()
+  {
+  if (!headers_sent())
+    header("Expires: ".gmdate("D, d M Y H:i:s", mktime()+2600000)." GMT");
+  }
+
+
+// function to convert an array to a javascript array
+function array2js($arr, $type='')
+  {
+  if (!$type)
+    $type = 'mixed';
+
+  if (is_array($arr))
+    {
+    // no items in array
+    if (!sizeof($arr))
+      return 'new Array()';
+    else
+      {
+      $a_pairs = array();
+      $keys_arr = array_keys($arr);
+      $is_assoc = $have_numeric = 0;
+
+      for ($i=0; $i<sizeof($keys_arr); ++$i)
+        {
+        if(is_numeric($keys_arr[$i]))
+          $have_numeric = 1;
+        if (!is_numeric($keys_arr[$i]) || $keys_arr[$i]!=$i)
+          $is_assoc = 1;
+        if($is_assoc && $have_numeric)
+          break;
+        }
+
+      $previous_was_array = false;
+      while (list($key, $value) = each($arr))
+        {
+        // enclose key with quotes if it is not variable-name conform
+        if (!ereg("^[_a-zA-Z]{1}[_a-zA-Z0-9]*$", $key) /* || is_js_reserved_word($key) */)
+          $key = "'$key'";
+
+        if (!is_array($value))
+          {
+          $value = str_replace("\r\n", '\n', $value);
+          $value = str_replace("\n", '\n', $value);
+          }
+
+        $is_string = false;
+        if (!is_array($value))
+          {
+          if ($type=='string')
+            $is_string = true;
+          else if ((($type=='mixed' && is_numeric($value)) || $type=='int') && strlen($value)<16)   // js interprets numbers with digits >15 as ...e+... 
+            $is_string = FALSE;
+          else
+            $is_string = TRUE;
+          }
+
+        if ($is_string)
+          $value = "'".preg_replace("/(?<!\\\)'/", "\'", $value)."'";
+
+        $a_pairs[] = sprintf("%s%s",
+                             $is_assoc ? "$key:" : '',
+                             is_array($value) ? array2js($value, $type) : $value);
+        }
+
+      if ($a_pairs)
+        {
+        if ($is_assoc)
+          $return = '{'.implode(',', $a_pairs).'}';
+        else
+          $return = '['.implode(',', $a_pairs).']';
+        }
+
+      return $return;
+      }
+    }
+  else
+    return $arr;
+  }
+
+
+// similar function as in_array() ut case-insensitive
+function in_array_nocase($needle, $haystack)
+  {
+  foreach ($haystack as $value)
+    {
+    if (strtolower($needle)===strtolower($value))
+      return TRUE;
+    }
+    
+  return FALSE;
+  }
+
+
+
+// find out if the string content means TRUE or FALSE
+function get_boolean($str)
+  {
+  $str = strtolower($str);
+  if(in_array($str, array('false', '0', 'no', 'nein', ''), TRUE))
+    return FALSE;
+  else
+    return TRUE;
+  }
+
+
+function show_bytes($numbytes)
+  {
+  if ($numbytes > 1024)
+    return sprintf('%d KB', round($numbytes/1024));
+  else
+    return sprintf('%d B', $numbytes);
+  }
+
+
+// convert paths like ../xxx to an absolute path using a base url
+function make_absolute_url($path, $base_url)
+    {
+    $host_url = $base_url;
+    $abs_path = $path;
+
+    // cut base_url to the last directory
+    if (strpos($base_url, '/')>7)
+      {
+      $host_url = substr($base_url, 0, strpos($base_url, '/'));
+      $base_url = substr($base_url, 0, strrpos($base_url, '/'));
+      }
+
+    // $path is absolute
+    if ($path{0}=='/')
+      $abs_path = $host_url.$path;
+    else
+      {
+      // strip './' because its the same as ''
+      $path = preg_replace('/^\.\//', '', $path);
+
+      if(preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER))
+        foreach($matches as $a_match)
+          {
+          if (strrpos($base_url, '/'))
+            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
+          
+          $path = substr($path, 3);
+          }
+
+      $abs_path = $base_url.'/'.$path;
+      }
+      
+    return $abs_path;
+    }
+
+
+// replace the middle part of a string with ...
+// if it is longer than the allowed length
+function abbrevate_string($str, $maxlength, $place_holder='...')
+  {
+  $length = strlen($str);
+  $first_part_length = floor($maxlength/2) - strlen($place_holder);
+  
+  if ($length > $maxlength)
+    {
+    $second_starting_location = $length - $maxlength + $first_part_length + 1;
+    $str = substr($str, 0, $first_part_length) . $place_holder . substr($str, $second_starting_location, $length);
+    }
+
+  return $str;
+  }
+
+
+// make sure the string ends with a slash
+function slashify($str)
+  {
+  return unslashify($str).'/';
+  }
+
+
+// remove slash at the end of the string
+function unslashify($str)
+  {
+  return preg_replace('/\/$/', '', $str);
+  }
+  
+
+// delete all files within a folder
+function clear_directory($dir_path)
+  {
+  $dir = @opendir($dir_path);
+  if(!$dir) return FALSE;
+
+  while ($file = readdir($dir))
+    if (strlen($file)>2)
+      unlink("$dir_path/$file");
+
+  closedir($dir);
+  return TRUE;
+  }
+
+
+// create a unix timestamp with a specified offset from now
+function get_offset_time($offset_str, $factor=1)
+  {
+  if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs))
+    {
+    $amount = (int)$regs[1];
+    $unit = strtolower($regs[2]);
+    }
+  else
+    {
+    $amount = (int)$offset_str;
+    $unit = 's';
+    }
+    
+  $ts = mktime();
+  switch ($unit)
+    {
+    case 'w':
+      $amount *= 7;
+    case 'd':
+      $amount *= 24;
+    case 'h':
+      $amount *= 60;
+    case 'h':
+      $amount *= 60;
+    case 's':
+      $ts += $amount * $factor;
+    }
+
+  return $ts;
+  }
+
+
+?>
diff --git a/program/include/rcube_smtp.inc b/program/include/rcube_smtp.inc
new file mode 100644 (file)
index 0000000..5977de5
--- /dev/null
@@ -0,0 +1,352 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_smtp.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide SMTP functionality using socket connections                 |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_smtp.inc 266 2006-06-26 18:38:03Z thomasb $
+
+*/
+
+
+// include required PEAR classes
+require_once('Net/SMTP.php');
+
+
+// define headers delimiter
+define('SMTP_MIME_CRLF', "\r\n");
+
+$SMTP_CONN = null;
+
+/**
+ * Function for sending mail using SMTP.
+ *
+ * @param string Sender e-Mail address
+ *
+ * @param mixed  Either a comma-seperated list of recipients
+ *               (RFC822 compliant), or an array of recipients,
+ *               each RFC822 valid. This may contain recipients not
+ *               specified in the headers, for Bcc:, resending
+ *               messages, etc.
+ *
+ * @param mixed  The message headers to send with the mail
+ *               Either as an associative array or a finally
+ *               formatted string
+ *
+ * @param string The full text of the message body, including any Mime parts, etc.
+ *
+ * @return bool  Returns TRUE on success, or FALSE on error
+ * @access public
+ */
+function smtp_mail($from, $recipients, &$headers, &$body)
+  {
+  global $SMTP_CONN, $CONFIG, $SMTP_ERROR;
+  $smtp_timeout = null;
+  $smtp_host = $CONFIG['smtp_server'];
+  $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
+  $smtp_host_url = parse_url($CONFIG['smtp_server']);
+  
+  // overwrite port
+  if ($smtp_host_url['host'] && $smtp_host_url['port'])
+    {
+    $smtp_host = $smtp_host_url['host'];
+    $smtp_port = $smtp_host_url['port'];
+    }
+
+  // re-write smtp host
+  if ($smtp_host_url['host'] && $smtp_host_url['scheme'])
+    $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']);
+
+
+  // create Net_SMTP object and connect to server
+  if (!is_object($smtp_conn))
+    {
+    $helo_host = !empty($_SERVER['server_name']) ? $_SERVER['server_name'] : 'localhost';
+    $SMTP_CONN = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
+
+    // set debugging
+    if ($CONFIG['debug_level'] & 8)
+      $SMTP_CONN->setDebug(TRUE);
+
+
+    // try to connect to server and exit on failure
+    $result = $SMTP_CONN->connect($smtp_timeout);
+    if (PEAR::isError($result))
+      {
+      $SMTP_CONN = null;
+      $SMTP_ERROR .= "Connection failed: ".$result->getMessage()."\n";
+      return FALSE;
+      }
+      
+    // attempt to authenticate to the SMTP server
+    if ($CONFIG['smtp_user'] && $CONFIG['smtp_pass'])
+      {
+      if (strstr($CONFIG['smtp_user'], '%u'))
+               $smtp_user = str_replace('%u', $_SESSION['username'], $CONFIG['smtp_user']);
+      else
+               $smtp_user = $CONFIG['smtp_user'];
+
+         if (strstr($CONFIG['smtp_pass'], '%p'))
+               $smtp_pass = str_replace('%p', decrypt_passwd($_SESSION['password']), $CONFIG['smtp_pass']);
+      else
+               $smtp_pass = $CONFIG['smtp_pass'];
+
+         $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
+         $result = $SMTP_CONN->auth($smtp_user, $smtp_pass, $smtp_auth_type);
+         
+      if (PEAR::isError($result))
+        {
+        smtp_reset();
+        $SMTP_ERROR .= "Authentication failure: ".$result->getMessage()."\n";
+        return FALSE;
+        }
+      }
+    }
+
+
+  // prepare message headers as string
+  if (is_array($headers))
+    {
+    $headerElements = smtp_prepare_headers($headers);
+    if (!$headerElements)
+      {
+      smtp_reset();
+      return FALSE;
+      }
+
+    list($from, $text_headers) = $headerElements;
+    }
+  else if (is_string($headers))
+    $text_headers = $headers;
+  else
+    {
+    smtp_reset();
+    $SMTP_ERROR .= "Invalid message headers\n";
+    return FALSE;
+    }
+
+  // exit if no from address is given
+  if (!isset($from))
+    {
+    smtp_reset();
+    $SMTP_ERROR .= "No From address has been provided\n";
+    return FALSE;
+    }
+
+
+  // set From: address
+  if (PEAR::isError($SMTP_CONN->mailFrom($from)))
+    {
+    smtp_reset();
+    $SMTP_ERROR .= "Failed to set sender '$from'\n";
+    return FALSE;
+    }
+
+
+  // prepare list of recipients
+  $recipients = smtp_parse_rfc822($recipients);
+  if (PEAR::isError($recipients))
+    {
+    smtp_reset();
+    return FALSE;
+    }
+
+
+  // set mail recipients
+  foreach ($recipients as $recipient)
+    {
+    if (PEAR::isError($SMTP_CONN->rcptTo($recipient)))
+      {
+      smtp_reset();
+      $SMTP_ERROR .= "Failed to add recipient '$recipient'\n";
+      return FALSE;
+      }
+    }
+
+
+  // Send the message's headers and the body as SMTP data.
+  if (PEAR::isError($SMTP_CONN->data("$text_headers\r\n$body")))
+    {
+    smtp_reset();
+    $SMTP_ERROR .= "Failed to send data\n";
+    return FALSE;
+    }
+
+
+  return TRUE;
+  }
+
+
+
+/**
+ * Reset the global SMTP connection
+ * @access public
+ */
+function smtp_reset()
+  {
+  global $SMTP_CONN;
+
+  if (is_object($SMTP_CONN))
+    {
+    $SMTP_CONN->rset();
+    smtp_disconnect();
+    }
+  }
+
+
+
+/**
+ * Disconnect the global SMTP connection and destroy object
+ * @access public
+ */
+function smtp_disconnect()
+  {
+  global $SMTP_CONN;
+
+  if (is_object($SMTP_CONN))
+    {
+    $SMTP_CONN->disconnect();
+    $SMTP_CONN = null;
+    }
+  }
+
+
+/**
+ * Take an array of mail headers and return a string containing
+ * text usable in sending a message.
+ *
+ * @param array $headers The array of headers to prepare, in an associative
+ *              array, where the array key is the header name (ie,
+ *              'Subject'), and the array value is the header
+ *              value (ie, 'test'). The header produced from those
+ *              values would be 'Subject: test'.
+ *
+ * @return mixed Returns false if it encounters a bad address,
+ *               otherwise returns an array containing two
+ *               elements: Any From: address found in the headers,
+ *               and the plain text version of the headers.
+ * @access private
+ */
+function smtp_prepare_headers($headers)
+  {
+  $lines = array();
+  $from = null;
+
+  foreach ($headers as $key => $value)
+    {
+    if (strcasecmp($key, 'From') === 0)
+      {
+      $addresses = smtp_parse_rfc822($value);
+
+      if (is_array($addresses))
+        $from = $addresses[0];
+
+      // Reject envelope From: addresses with spaces.
+      if (strstr($from, ' '))
+        return FALSE;
+
+
+      $lines[] = $key . ': ' . $value;
+      }
+    else if (strcasecmp($key, 'Received') === 0)
+      {
+      $received = array();
+      if (is_array($value))
+        {
+        foreach ($value as $line)
+          $received[] = $key . ': ' . $line;
+        }
+      else
+        {
+        $received[] = $key . ': ' . $value;
+        }
+
+      // Put Received: headers at the top.  Spam detectors often
+      // flag messages with Received: headers after the Subject:
+      // as spam.
+      $lines = array_merge($received, $lines);
+      }
+
+    else
+      {
+      // If $value is an array (i.e., a list of addresses), convert
+      // it to a comma-delimited string of its elements (addresses).
+      if (is_array($value))
+        $value = implode(', ', $value);
+
+      $lines[] = $key . ': ' . $value;
+      }
+    }
+
+  return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF);
+  }
+
+
+
+/**
+ * Take a set of recipients and parse them, returning an array of
+ * bare addresses (forward paths) that can be passed to sendmail
+ * or an smtp server with the rcpt to: command.
+ *
+ * @param mixed Either a comma-seperated list of recipients
+ *              (RFC822 compliant), or an array of recipients,
+ *              each RFC822 valid.
+ *
+ * @return array An array of forward paths (bare addresses).
+ * @access private
+ */
+function smtp_parse_rfc822($recipients)
+  {
+  // if we're passed an array, assume addresses are valid and implode them before parsing.
+  if (is_array($recipients))
+    $recipients = implode(', ', $recipients);
+    
+  $addresses = array();
+  $recipients = smtp_explode_quoted_str(",", $recipients);
+  
+  reset($recipients);
+  while (list($k, $recipient) = each($recipients))
+    {
+       $a = explode(" ", $recipient);
+       while (list($k2, $word) = each($a))
+         {
+      if ((strpos($word, "@") > 0) && (strpos($word, "\"")===false))
+        {
+        $word = ereg_replace('^<|>$', '', trim($word));
+        if (in_array($word, $addresses)===false)
+          array_push($addresses, $word);
+        }
+      }
+    }
+  return $addresses;
+  }
+
+
+function smtp_explode_quoted_str($delimiter, $string)
+  {
+  $quotes=explode("\"", $string);
+  while ( list($key, $val) = each($quotes))
+    if (($key % 2) == 1) 
+      $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
+    $string=implode("\"", $quotes);
+       
+    $result=explode($delimiter, $string);
+    while (list($key, $val) = each($result))
+      $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
+
+  return $result;
+  }    
+
+
+?>
diff --git a/program/include/rcube_sqlite.inc b/program/include/rcube_sqlite.inc
new file mode 100644 (file)
index 0000000..44e4b81
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_sqlite.inc                                      |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide callback functions for sqlite that will emulate             |
+ |   sone MySQL functions                                                |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_sqlite.inc 88 2005-12-03 16:54:12Z roundcube $
+
+*/
+
+
+function rcube_sqlite_from_unixtime($timestamp)
+  {
+       $timestamp = trim($timestamp);
+       if (!preg_match("/^[0-9]+$/is", $timestamp))
+         $ret = strtotime($timestamp);
+       else
+         $ret = $timestamp;
+         
+       $ret = date("Y-m-d H:i:s", $ret);
+       rcube_sqlite_debug("FROM_UNIXTIME ($timestamp) = $ret");
+       return $ret;
+  }
+
+
+function rcube_sqlite_unix_timestamp($timestamp="")
+  {
+       $timestamp = trim($timestamp);
+       if (!$timestamp)
+         $ret = time();
+       else if (!preg_match("/^[0-9]+$/is", $timestamp))
+         $ret = strtotime($timestamp);
+       else
+         $ret = $timestamp;
+
+       rcube_sqlite_debug("UNIX_TIMESTAMP ($timestamp) = $ret");
+       return $ret;
+  }
+
+
+function rcube_sqlite_now()
+  {
+       rcube_sqlite_debug("NOW() = ".date("Y-m-d H:i:s"));
+       return date("Y-m-d H:i:s");
+  }
+
+
+function rcube_sqlite_md5($str)
+  {
+       return md5($str);
+  }
+
+
+function rcube_sqlite_debug($str)
+  {
+       //console($str);
+  }
+       
+?>
\ No newline at end of file
diff --git a/program/include/session.inc b/program/include/session.inc
new file mode 100644 (file)
index 0000000..822237c
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/session.inc                                           |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide database supported session management                       |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: session.inc 132 2006-02-05 15:38:51Z roundcube $
+
+*/
+
+
+function sess_open($save_path, $session_name)
+  {
+  return TRUE;
+  }
+
+
+
+function sess_close()
+  {
+  return TRUE;
+  }
+
+
+// read session data
+function sess_read($key)
+  {
+  global $DB, $SESS_CHANGED;
+  
+  $sql_result = $DB->query("SELECT vars, ip, ".$DB->unixtimestamp('changed')." AS changed
+                            FROM ".get_table_name('session')."
+                            WHERE  sess_id=?",
+                            $key);
+
+  if ($sql_arr = $DB->fetch_assoc($sql_result))
+    {
+    $SESS_CHANGED = mktime(); //$sql_arr['changed'];
+
+    if (strlen($sql_arr['vars']))
+      return $sql_arr['vars'];
+    }
+
+  return FALSE;
+  }
+  
+
+// save session data
+function sess_write($key, $vars)
+  {
+  global $DB;
+
+  $sql_result = $DB->query("SELECT 1
+                            FROM ".get_table_name('session')."
+                            WHERE  sess_id=?",
+                            $key);
+
+  if ($DB->num_rows($sql_result))
+    {
+    session_decode($vars);
+    $DB->query("UPDATE ".get_table_name('session')."
+                SET    vars=?,
+                       changed=now()
+                WHERE  sess_id=?",
+                $vars,
+                $key);
+    }
+  else
+    {
+    $DB->query("INSERT INTO ".get_table_name('session')."
+                (sess_id, vars, ip, created, changed)
+                VALUES (?, ?, ?, now(), now())",
+                $key,
+                $vars,
+                $_SERVER['REMOTE_ADDR']);
+                
+
+    }
+
+  return TRUE;
+  }
+
+
+// handler for session_destroy()
+function sess_destroy($key)
+  {
+  global $DB;
+  
+  // delete session entries in cache table
+  $DB->query("DELETE FROM ".get_table_name('cache')."
+              WHERE  session_id=?",
+              $key);
+              
+  $DB->query("DELETE FROM ".get_table_name('session')."
+              WHERE sess_id=?",
+              $key);
+
+  rcmail_clear_session_temp($key);
+  return TRUE;
+  }
+
+
+// garbage collecting function
+function sess_gc($maxlifetime)
+  {
+  global $DB;
+
+  // get all expired sessions  
+  $sql_result = $DB->query("SELECT sess_id
+                            FROM ".get_table_name('session')."
+                            WHERE ".$DB->unixtimestamp('now()')."-".$DB->unixtimestamp('changed')." > ?",
+                            $maxlifetime);
+                                   
+  $a_exp_sessions = array();
+  while ($sql_arr = $DB->fetch_assoc($sql_result))
+    $a_exp_sessions[] = $sql_arr['sess_id'];
+
+  
+  if (sizeof($a_exp_sessions))
+    {
+    // delete session cache records
+    $DB->query("DELETE FROM ".get_table_name('cache')."
+                WHERE  session_id IN ('".join("','", $a_exp_sessions)."')");
+                
+    // delete session records
+    $DB->query("DELETE FROM ".get_table_name('session')."
+                WHERE sess_id IN ('".join("','", $a_exp_sessions)."')");
+    }
+
+  // remove session specific temp dirs
+  foreach ($a_exp_sessions as $key)
+    rcmail_clear_session_temp($key);
+
+  // also run message cache GC
+  rcmail_message_cache_gc();
+
+  return TRUE;
+  }
+
+
+// set custom functions for PHP session management
+session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy', 'sess_gc');
+
+?>
diff --git a/program/js/app.js b/program/js/app.js
new file mode 100644 (file)
index 0000000..7622f1f
--- /dev/null
@@ -0,0 +1,3902 @@
+/*
+ +-----------------------------------------------------------------------+
+ | RoundCube Webmail Client Script                                       |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
+ |          Charles McNulty <charles@charlesmcnulty.com>                 |
+ +-----------------------------------------------------------------------+
+  $Id: app.js 431 2006-12-23 10:44:16Z thomasb $
+*/
+
+// Constants
+var CONTROL_KEY = 1;
+var SHIFT_KEY = 2;
+var CONTROL_SHIFT_KEY = 3;
+
+var rcube_webmail_client;
+
+function rcube_webmail()
+  {
+  this.env = new Object();
+  this.labels = new Object();
+  this.buttons = new Object();
+  this.gui_objects = new Object();
+  this.commands = new Object();
+  this.selection = new Array();
+  this.last_selected = 0;
+  this.in_message_list = false;
+
+  // create public reference to myself
+  rcube_webmail_client = this;
+  this.ref = 'rcube_webmail_client';
+  // webmail client settings
+  this.dblclick_time = 600;
+  this.message_time = 5000;
+  
+  this.mbox_expression = new RegExp('[^0-9a-z\-_]', 'gi');
+  
+  // mimetypes supported by the browser (default settings)
+  this.mimetypes = new Array('text/plain', 'text/html', 'text/xml',
+                             'image/jpeg', 'image/gif', 'image/png',
+                             'application/x-javascript', 'application/pdf',
+                             'application/x-shockwave-flash');
+
+  // default environment vars
+  this.env.keep_alive = 60;        // seconds
+  this.env.request_timeout = 180;  // seconds
+  this.env.draft_autosave = 300;   // seconds
+
+
+  // set environment variable
+  this.set_env = function(name, value)
+    {
+    this.env[name] = value;
+    };
+
+
+  // add a localized label to the client environment
+  this.add_label = function(key, value)
+    {
+    this.labels[key] = value;
+    };
+
+
+  // add a button to the button list
+  this.register_button = function(command, id, type, act, sel, over)
+    {
+    if (!this.buttons[command])
+      this.buttons[command] = new Array();
+      
+    var button_prop = {id:id, type:type};
+    if (act) button_prop.act = act;
+    if (sel) button_prop.sel = sel;
+    if (over) button_prop.over = over;
+
+    this.buttons[command][this.buttons[command].length] = button_prop;    
+    };
+
+
+  // register a specific gui object
+  this.gui_object = function(name, id)
+    {
+    this.gui_objects[name] = id;
+    };
+
+
+  // initialize webmail client
+  this.init = function()
+    {
+    this.task = this.env.task;
+    
+    // check browser
+    if (!bw.dom || !bw.xmlhttp_test())
+      {
+      location.href = this.env.comm_path+'&_action=error&_code=0x199';
+      return;
+      }
+    
+    // find all registered gui objects
+    for (var n in this.gui_objects)
+      this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
+      
+    // tell parent window that this frame is loaded
+    if (this.env.framed && parent.rcmail && parent.rcmail.set_busy)
+      parent.rcmail.set_busy(false);
+
+    // enable general commands
+    this.enable_command('logout', 'mail', 'addressbook', 'settings', true);
+    
+    switch (this.task)
+      {
+      case 'mail':
+        var msg_list_frame = this.gui_objects.mailcontframe;
+        var msg_list = this.gui_objects.messagelist;
+        if (msg_list)
+          {
+          msg_list_frame.onmousedown = function(e){return rcube_webmail_client.click_on_list(e);};
+          this.init_messagelist(msg_list);
+          this.enable_command('toggle_status', true);
+          }
+
+        // enable mail commands
+        this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', true);
+        
+        if (this.env.action=='show')
+          {
+          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 'viewsource', 'print', 'load-attachment', true);
+          if (this.env.next_uid)
+            this.enable_command('nextmessage', true);
+          if (this.env.prev_uid)
+            this.enable_command('previousmessage', true);
+          }
+
+        if (this.env.action=='show' && this.env.blockedobjects)
+          {
+          if (this.gui_objects.remoteobjectsmsg)
+            this.gui_objects.remoteobjectsmsg.style.display = 'block';
+          this.enable_command('load-images', true);
+          }  
+
+        if (this.env.action=='compose')
+          {
+          this.enable_command('add-attachment', 'send-attachment', 'remove-attachment', 'send', true);
+          if (this.env.spellcheck)
+            this.enable_command('spellcheck', true);
+          if (this.env.drafts_mailbox)
+            this.enable_command('savedraft', true);
+          }
+          
+        if (this.env.messagecount)
+          this.enable_command('select-all', 'select-none', 'sort', 'expunge', true);
+
+        if (this.env.messagecount && (this.env.mailbox==this.env.trash_mailbox || this.env.mailbox==this.env.junk_mailbox))
+          this.enable_command('purge', true);
+
+        this.set_page_buttons();
+
+        // focus this window
+        window.focus();
+
+        // init message compose form
+        if (this.env.action=='compose')
+          this.init_messageform();
+
+        // show printing dialog
+        if (this.env.action=='print')
+          window.print();
+          
+        // get unread count for each mailbox
+        if (this.gui_objects.mailboxlist)
+          this.http_request('getunread', '');
+
+        break;
+
+
+      case 'addressbook':
+        var contacts_list      = this.gui_objects.contactslist;
+        var ldap_contacts_list = this.gui_objects.ldapcontactslist;
+
+        if (contacts_list)
+          this.init_contactslist(contacts_list);
+      
+        if (ldap_contacts_list)
+          this.init_ldapsearchlist(ldap_contacts_list);
+
+        this.set_page_buttons();
+          
+        if (this.env.cid)
+          this.enable_command('show', 'edit', true);
+
+        if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
+          this.enable_command('save', true);
+      
+        this.enable_command('list', 'add', true);
+
+        this.enable_command('ldappublicsearch', this.env.ldappublicsearch);
+
+        break;
+
+
+      case 'settings':
+        this.enable_command('preferences', 'identities', 'save', 'folders', true);
+        
+        if (this.env.action=='identities' || this.env.action=='edit-identity' || this.env.action=='add-identity')
+          this.enable_command('edit', 'add', 'delete', true);
+
+        if (this.env.action=='edit-identity' || this.env.action=='add-identity')
+          this.enable_command('save', true);
+          
+        if (this.env.action=='folders')
+          this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true);
+          
+        var identities_list = this.gui_objects.identitieslist;
+        if (identities_list)
+          this.init_identitieslist(identities_list);
+
+        break;
+
+      case 'login':
+        var input_user = rcube_find_object('_user');
+        var input_pass = rcube_find_object('_pass');
+        if (input_user && input_user.value=='')
+          input_user.focus();
+        else if (input_pass)
+          input_pass.focus();
+          
+        this.enable_command('login', true);
+        break;
+      
+      default:
+        break;
+      }
+
+
+    // enable basic commands
+    this.enable_command('logout', true);
+
+    // disable browser's contextmenus
+    // document.oncontextmenu = function(){ return false; }
+
+    // load body click event
+    document.onmousedown = function(){ return rcube_webmail_client.reset_click(); };
+    document.onkeydown   = function(e){ return rcube_webmail_client.key_pressed(e, msg_list_frame); };
+    
+    // flag object as complete
+    this.loaded = true;
+
+    // show message
+    if (this.pending_message)
+      this.display_message(this.pending_message[0], this.pending_message[1]);
+
+    // start keep-alive interval
+    this.start_keepalive();
+    };
+
+
+  // start interval for keep-alive/recent_check signal
+  this.start_keepalive = function()
+    {
+    if (this.env.keep_alive && this.task=='mail' && this.gui_objects.messagelist)
+      this._int = setInterval(this.ref+'.check_for_recent()', this.env.keep_alive * 1000);
+    else if (this.env.keep_alive && this.task!='login')
+      this._int = setInterval(this.ref+'.send_keep_alive()', this.env.keep_alive * 1000);    
+    }
+
+
+  // reset last clicked if user clicks on anything other than the message table
+  this.reset_click = function()
+    {
+    var id;
+    this.in_message_list = false;
+       for (var n=0; n<this.selection.length; n++)
+         {
+      id = this.selection[n];
+      if (this.list_rows[id] && this.list_rows[id].obj)
+        {
+        this.set_classname(this.list_rows[id].obj, 'selected', false);
+               this.set_classname(this.list_rows[id].obj, 'unfocused', true);
+        }
+      }
+    };
+       
+  this.click_on_list = function(e)
+    {
+    if (!e)
+      e = window.event;
+
+    for (var n=0; n<this.selection.length; n++)
+      {
+      id = this.selection[n];
+      if (this.list_rows[id].obj)
+        {
+        this.set_classname(this.list_rows[id].obj, 'selected', true);
+               this.set_classname(this.list_rows[id].obj, 'unfocused', false);
+        }
+      }
+
+    var mbox_li;
+    if (mbox_li = this.get_mailbox_li()) 
+      this.set_classname(mbox_li, 'unfocused', true);
+    
+    this.in_message_list = true;
+    e.cancelBubble = true;
+    };
+
+  this.key_pressed = function(e, msg_list_frame) {
+    if (this.in_message_list != true) 
+      return true;
+    var keyCode = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? e.keyCode : 0;
+    var mod_key = this.get_modifier(e);
+    switch (keyCode) {
+      case 13:
+        this.command('show','',this);
+        break;
+      case 40:
+      case 38: 
+        return this.use_arrow_key(keyCode, mod_key, msg_list_frame);
+        break;
+      case 46:
+        return this.use_delete_key(keyCode, mod_key, msg_list_frame);
+        break;
+      default:
+        return true;
+    }
+    return true;
+  }
+
+  this.use_arrow_key = function(keyCode, mod_key, msg_list_frame) {
+    var scroll_to = 0;
+    if (keyCode == 40) { // down arrow key pressed
+      new_row = this.get_next_row();
+      if (!new_row) return false;
+      scroll_to = (Number(new_row.offsetTop) + Number(new_row.offsetHeight)) - Number(msg_list_frame.offsetHeight);
+    } else if (keyCode == 38) { // up arrow key pressed
+      new_row = this.get_prev_row();
+      if (!new_row) return false;
+      scroll_to = new_row.offsetTop;
+    } else {return true;}
+       
+    this.select_row(new_row.uid,mod_key,true);
+
+    if (((Number(new_row.offsetTop)) < (Number(msg_list_frame.scrollTop))) || 
+       ((Number(new_row.offsetTop) + Number(new_row.offsetHeight)) > (Number(msg_list_frame.scrollTop) + Number(msg_list_frame.offsetHeight)))) {
+      msg_list_frame.scrollTop = scroll_to;
+    }
+    return false;
+  };
+  
+  this.use_delete_key = function(keyCode, mod_key, msg_list_frame){
+    this.command('delete','',this);
+    return false;
+  }
+
+  // get all message rows from HTML table and init each row
+  this.init_messagelist = function(msg_list)
+    {
+    if (msg_list && msg_list.tBodies[0])
+      {
+                 
+      this.message_rows = new Array();
+
+      var row;
+      for(var r=0; r<msg_list.tBodies[0].childNodes.length; r++)
+        {
+        row = msg_list.tBodies[0].childNodes[r];
+        while (row && (row.nodeType != 1 || row.style.display == 'none')) {
+          row = row.nextSibling;
+          r++;
+        }
+        //row = msg_list.tBodies[0].rows[r];
+        if (row) this.init_message_row(row);
+        }
+      }
+      
+    // alias to common rows array
+    this.list_rows = this.message_rows;
+    };
+    
+    
+  // make references in internal array and set event handlers
+  this.init_message_row = function(row)
+    {
+    var uid, msg_icon;
+    
+    if (String(row.id).match(/rcmrow([0-9]+)/))
+      {
+      uid = RegExp.$1;
+      row.uid = uid;
+              
+      this.message_rows[uid] = {id:row.id, obj:row,
+                                classname:row.className,
+                                deleted:this.env.messages[uid] ? this.env.messages[uid].deleted : null,
+                                unread:this.env.messages[uid] ? this.env.messages[uid].unread : null,
+                                replied:this.env.messages[uid] ? this.env.messages[uid].replied : null};
+              
+      // set eventhandlers to table row
+      row.onmousedown = function(e){ return rcube_webmail_client.drag_row(e, this.uid); };
+      row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.uid); };
+
+      if (document.all)
+        row.onselectstart = function() { return false; };
+
+      // set eventhandler to message icon
+      if ((msg_icon = row.cells[0].childNodes[0]) && row.cells[0].childNodes[0].nodeName=='IMG')
+        {                
+        msg_icon.id = 'msgicn_'+uid;
+        msg_icon._row = row;
+        msg_icon.onmousedown = function(e) { rcube_webmail_client.command('toggle_status', this); };
+                
+        // get message icon and save original icon src
+        this.message_rows[uid].icon = msg_icon;
+        }
+      }
+    };
+
+
+  // init message compose form: set focus and eventhandlers
+  this.init_messageform = function()
+    {
+    if (!this.gui_objects.messageform)
+      return false;
+    
+    //this.messageform = this.gui_objects.messageform;
+    var input_from = rcube_find_object('_from');
+    var input_to = rcube_find_object('_to');
+    var input_cc = rcube_find_object('_cc');
+    var input_bcc = rcube_find_object('_bcc');
+    var input_replyto = rcube_find_object('_replyto');
+    var input_subject = rcube_find_object('_subject');
+    var input_message = rcube_find_object('_message');
+    
+    // init live search events
+    if (input_to)
+      this.init_address_input_events(input_to);
+    if (input_cc)
+      this.init_address_input_events(input_cc);
+    if (input_bcc)
+      this.init_address_input_events(input_bcc);
+      
+    // add signature according to selected identity
+    if (input_from && input_from.type=='select-one')
+      this.change_identity(input_from);
+
+    if (input_to && input_to.value=='')
+      input_to.focus();
+    else if (input_subject && input_subject.value=='')
+      input_subject.focus();
+    else if (input_message)
+      this.set_caret2start(input_message); // input_message.focus();
+    
+    // get summary of all field values
+    this.cmp_hash = this.compose_field_hash();
+    // start the auto-save timer
+    this.auto_save_start();
+    };
+
+  this.init_address_input_events = function(obj)
+    {
+    var handler = function(e){ return rcube_webmail_client.ksearch_keypress(e,this); };
+    var handler2 = function(e){ return rcube_webmail_client.ksearch_blur(e,this); };
+       
+    if (bw.safari)
+      {
+      obj.addEventListener('keydown', handler, false);
+      // obj.addEventListener('blur', handler2, false);
+      }
+    else if (bw.mz)
+      {
+      obj.addEventListener('keypress', handler, false);
+      obj.addEventListener('blur', handler2, false);
+      }
+    else if (bw.ie)
+      {
+      obj.onkeydown = handler;
+      //obj.attachEvent('onkeydown', handler);
+      // obj.attachEvent('onblur', handler2, false);
+      }
+       
+    obj.setAttribute('autocomplete', 'off');       
+    };
+
+
+
+  // get all contact rows from HTML table and init each row
+  this.init_contactslist = function(contacts_list)
+    {
+    if (contacts_list && contacts_list.tBodies[0])
+      {
+      this.contact_rows = new Array();
+
+      var row;
+      for(var r=0; r<contacts_list.tBodies[0].childNodes.length; r++)
+        {
+        row = contacts_list.tBodies[0].childNodes[r];
+        this.init_table_row(row, 'contact_rows');
+        }
+      }
+
+    // alias to common rows array
+    this.list_rows = this.contact_rows;
+    
+    if (this.env.cid)
+      this.highlight_row(this.env.cid);
+    };
+
+
+  // get all contact rows from HTML table and init each row
+  this.init_ldapsearchlist = function(ldap_contacts_list)
+    {
+    if (ldap_contacts_list && ldap_contacts_list.tBodies[0])
+      {
+      this.ldap_contact_rows = new Array();
+
+      var row;
+      for(var r=0; r<ldap_contacts_list.tBodies[0].childNodes.length; r++)
+        {
+        row = ldap_contacts_list.tBodies[0].childNodes[r];
+        this.init_table_row(row, 'ldap_contact_rows');
+        }
+      }
+
+    // alias to common rows array
+    this.list_rows = this.ldap_contact_rows;
+    };
+
+
+  // make references in internal array and set event handlers
+  this.init_table_row = function(row, array_name)
+    {
+    var cid;
+    
+    if (String(row.id).match(/rcmrow([0-9]+)/))
+      {
+      cid = RegExp.$1;
+      row.cid = cid;
+
+      this[array_name][cid] = {id:row.id,
+                               obj:row,
+                               classname:row.className};
+
+      // set eventhandlers to table row
+      row.onmousedown = function(e) { rcube_webmail_client.in_selection_before=this.cid; return false; };  // fake for drag handler
+      row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.cid); };
+      }
+    };
+
+
+  // get all contact rows from HTML table and init each row
+  this.init_identitieslist = function(identities_list)
+    {
+    if (identities_list && identities_list.tBodies[0])
+      {
+      this.identity_rows = new Array();
+
+      var row;
+      for(var r=0; r<identities_list.tBodies[0].childNodes.length; r++)
+        {
+        row = identities_list.tBodies[0].childNodes[r];
+        this.init_table_row(row, 'identity_rows');
+        }
+      }
+
+    // alias to common rows array
+    this.list_rows = this.identity_rows;
+    
+    if (this.env.iid)
+      this.highlight_row(this.env.iid);    
+    };
+    
+
+
+  /*********************************************************/
+  /*********       client command interface        *********/
+  /*********************************************************/
+
+
+  // execute a specific command on the web client
+  this.command = function(command, props, obj)
+    {
+    if (obj && obj.blur)
+      obj.blur();
+
+    if (this.busy)
+      return false;
+
+    // command not supported or allowed
+    if (!this.commands[command])
+      {
+      // pass command to parent window
+      if (this.env.framed && parent.rcmail && parent.rcmail.command)
+        parent.rcmail.command(command, props);
+
+      return false;
+      }
+      
+      
+   // check input before leaving compose step
+   if (this.task=='mail' && this.env.action=='compose' && (command=='list' || command=='mail' || command=='addressbook' || command=='settings'))
+     {
+     if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
+        return false;
+     }
+
+
+    // process command
+    switch (command)
+      {
+      case 'login':
+        if (this.gui_objects.loginform)
+          this.gui_objects.loginform.submit();
+        break;
+
+      case 'logout':
+        location.href = this.env.comm_path+'&_action=logout';
+        break;      
+
+      // commands to switch task
+      case 'mail':
+      case 'addressbook':
+      case 'settings':
+        this.switch_task(command);
+        break;
+
+
+      // misc list commands
+      case 'list':
+        if (this.task=='mail')
+          {
+          if (this.env.search_request<0 || (this.env.search_request && props != this.env.mailbox))
+            this.reset_qsearch();
+
+          // Reset message list header, unless returning from compose/read/etc
+          // don't know what this is good for (thomasb, 2006/07/25)
+          //if (this.env.mailbox != props && this.message_rows)
+          //  this.clear_message_list_header();
+
+          this.list_mailbox(props);
+          }
+        else if (this.task=='addressbook')
+          this.list_contacts();
+        break;
+
+
+      case 'sort':
+        // get the type of sorting
+        var a_sort = props.split('_');
+        var sort_col = a_sort[0];
+        var sort_order = a_sort[1] ? a_sort[1].toUpperCase() : null;
+        var header;
+        
+        // no sort order specified: toggle
+        if (sort_order==null)
+          {
+          if (this.env.sort_col==sort_col)
+            sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
+          else
+            sort_order = this.env.sort_order;
+          }
+
+        if (this.env.sort_col==sort_col && this.env.sort_order==sort_order)
+          break;
+
+        // set table header class
+        if (header = document.getElementById('rcmHead'+this.env.sort_col))
+          this.set_classname(header, 'sorted'+(this.env.sort_order.toUpperCase()), false);
+        if (header = document.getElementById('rcmHead'+sort_col))
+          this.set_classname(header, 'sorted'+sort_order, true);
+
+        // save new sort properties
+        this.env.sort_col = sort_col;
+        this.env.sort_order = sort_order;
+
+        // reload message list
+        this.list_mailbox('', '', sort_col+'_'+sort_order);
+        break;
+
+      case 'nextpage':
+        this.list_page('next');
+        break;
+
+      case 'previouspage':
+        this.list_page('prev');
+        break;
+
+      case 'expunge':
+        if (this.env.messagecount)
+          this.expunge_mailbox(this.env.mailbox);
+        break;
+
+      case 'purge':
+      case 'empty-mailbox':
+        if (this.env.messagecount)
+          this.purge_mailbox(this.env.mailbox);
+        break;
+
+
+      // common commands used in multiple tasks
+      case 'show':
+        if (this.task=='mail')
+          {
+          var uid = this.get_single_uid();
+          if (uid && (!this.env.uid || uid != this.env.uid))
+            {
+            if (this.env.mailbox==this.env.drafts_mailbox)
+              {
+              this.set_busy(true);
+              location.href = this.env.comm_path+'&_action=compose&_draft_uid='+uid+'&_mbox='+escape(this.env.mailbox);
+              }
+            else
+              this.show_message(uid);
+            }
+          }
+        else if (this.task=='addressbook')
+          {
+          var cid = props ? props : this.get_single_cid();
+          if (cid && !(this.env.action=='show' && cid==this.env.cid))
+            this.load_contact(cid, 'show');
+          }
+        break;
+
+      case 'add':
+        if (this.task=='addressbook')
+          if (!window.frames[this.env.contentframe].rcmail)
+            this.load_contact(0, 'add');
+          else
+            {
+            if (window.frames[this.env.contentframe].rcmail.selection.length)
+              this.add_ldap_contacts();
+            else
+              this.load_contact(0, 'add');
+            }
+        else if (this.task=='settings')
+          {
+          this.clear_selection();
+          this.load_identity(0, 'add-identity');
+          }
+        break;
+
+      case 'edit':
+        var cid;
+        if (this.task=='addressbook' && (cid = this.get_single_cid()))
+          this.load_contact(cid, 'edit');
+        else if (this.task=='settings' && props)
+          this.load_identity(props, 'edit-identity');
+        break;
+
+      case 'save-identity':
+      case 'save':
+        if (this.gui_objects.editform)
+          {
+          var input_pagesize = rcube_find_object('_pagesize');
+          var input_name  = rcube_find_object('_name');
+          var input_email = rcube_find_object('_email');
+
+          // user prefs
+          if (input_pagesize && isNaN(input_pagesize.value))
+            {
+            alert(this.get_label('nopagesizewarning'));
+            input_pagesize.focus();
+            break;
+            }
+          // contacts/identities
+          else
+            {
+            if (input_name && input_name.value == '')
+              {
+              alert(this.get_label('nonamewarning'));
+              input_name.focus();
+              break;
+              }
+            else if (input_email && !rcube_check_email(input_email.value))
+              {
+              alert(this.get_label('noemailwarning'));
+              input_email.focus();
+              break;
+              }
+            }
+
+          this.gui_objects.editform.submit();
+          }
+        break;
+
+      case 'delete':
+        // mail task
+        if (this.task=='mail')
+          this.delete_messages();
+        // addressbook task
+        else if (this.task=='addressbook')
+          this.delete_contacts();
+        // user settings task
+        else if (this.task=='settings')
+          this.delete_identity();
+        break;
+
+
+      // mail task commands
+      case 'move':
+      case 'moveto':
+        this.move_messages(props);
+        break;
+        
+      case 'toggle_status':
+        if (props && !props._row)
+          break;
+        
+        var uid;
+        var flag = 'read';
+        
+        if (props._row.uid)
+          {
+          uid = props._row.uid;
+          this.dont_select = true;
+          // toggle read/unread
+          if (this.message_rows[uid].deleted) {
+               flag = 'undelete';
+          } else if (!this.message_rows[uid].unread)
+            flag = 'unread';
+          }
+          
+        this.mark_message(flag, uid);
+        break;
+        
+      case 'load-images':
+        if (this.env.uid)
+          this.show_message(this.env.uid, true);
+        break;
+
+      case 'load-attachment':
+        var url = this.env.comm_path+'&_action=get&_mbox='+this.env.mailbox+'&_uid='+this.env.uid+'&_part='+props.part;
+        
+        // open attachment in frame if it's of a supported mimetype
+        if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0)
+          {
+          this.attachment_win = window.open(url+'&_frame=1', 'rcubemailattachment');
+          if (this.attachment_win)
+            {
+            setTimeout(this.ref+'.attachment_win.focus()', 10);
+            break;
+            }
+          }
+
+        location.href = url;
+        break;
+        
+      case 'select-all':
+        this.select_all(props);
+        break;
+
+      case 'select-none':
+        this.clear_selection();
+        break;
+
+      case 'nextmessage':
+        if (this.env.next_uid)
+          this.show_message(this.env.next_uid);
+          //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.next_uid+'&_mbox='+this.env.mailbox;
+        break;
+
+      case 'previousmessage':
+        if (this.env.prev_uid)
+          this.show_message(this.env.prev_uid);
+          //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.prev_uid+'&_mbox='+this.env.mailbox;
+        break;
+      
+      case 'checkmail':
+        this.check_for_recent();
+        break;
+      
+      case 'compose':
+        var url = this.env.comm_path+'&_action=compose';
+       
+        if (this.task=='mail' && this.env.mailbox==this.env.drafts_mailbox)
+          {
+          var uid;
+          if (uid = this.get_single_uid())
+            url += '&_draft_uid='+uid+'&_mbox='+escape(this.env.mailbox);
+          } 
+        // modify url if we're in addressbook
+        else if (this.task=='addressbook')
+          {
+          url = this.get_task_url('mail', url);            
+          var a_cids = new Array();
+          
+          // use contact_id passed as command parameter
+          if (props)
+            a_cids[a_cids.length] = props;
+            
+          // get selected contacts
+          else
+            {
+            if (!window.frames[this.env.contentframe].rcmail.selection.length)
+              {
+              for (var n=0; n<this.selection.length; n++)
+                a_cids[a_cids.length] = this.selection[n];
+              }
+            else
+              {
+              var frameRcmail = window.frames[this.env.contentframe].rcmail;
+              // get the email address(es)
+              for (var n=0; n<frameRcmail.selection.length; n++)
+                a_cids[a_cids.length] = frameRcmail.ldap_contact_rows[frameRcmail.selection[n]].obj.cells[1].innerHTML;
+              }
+            }
+          if (a_cids.length)
+            url += '&_to='+a_cids.join(',');
+          else
+            break;
+            
+          }
+        else if (props)
+           url += '&_to='+encodeURIComponent(props);
+
+        // don't know if this is necessary...
+        url = url.replace(/&_framed=1/, "");
+
+        this.set_busy(true);
+
+        // need parent in case we are coming from the contact frame
+        if (this.env.framed)
+          parent.location.href = url;
+        else
+          location.href = url;
+        break;
+        
+      case 'spellcheck':
+        if (this.env.spellcheck && this.env.spellcheck.spellCheck)
+          this.env.spellcheck.spellCheck(this.env.spellcheck.check_link);
+        break;
+
+      case 'savedraft':
+        // Reset the auto-save timer
+        self.clearTimeout(this.save_timer);
+
+        if (!this.gui_objects.messageform)
+          break;
+
+        // if saving Drafts is disabled in main.inc.php
+        if (!this.env.drafts_mailbox)
+          break;
+
+        this.set_busy(true, 'savingmessage');
+        var form = this.gui_objects.messageform;
+        form.target = "savetarget";
+        form.submit();
+        break;
+
+      case 'send':
+        if (!this.gui_objects.messageform)
+          break;
+
+        if (!this.check_compose_input())
+          break;
+          
+        // Reset the auto-save timer
+        self.clearTimeout(this.save_timer);
+
+        // all checks passed, send message
+        this.set_busy(true, 'sendingmessage');
+        var form = this.gui_objects.messageform;
+        form.target = "savetarget";     
+        form._draft.value = '';
+        form.submit();
+        
+        // clear timeout (sending could take longer)
+        clearTimeout(this.request_timer);
+        break;
+
+      case 'add-attachment':
+        this.show_attachment_form(true);
+        
+      case 'send-attachment':
+        // Reset the auto-save timer
+        self.clearTimeout(this.save_timer);
+
+        this.upload_file(props)      
+        break;
+      
+      case 'remove-attachment':
+        this.remove_attachment(props);
+        break;
+
+      case 'reply-all':
+      case 'reply':
+        var uid;
+        if (uid = this.get_single_uid())
+          {
+          this.set_busy(true);
+          location.href = this.env.comm_path+'&_action=compose&_reply_uid='+uid+'&_mbox='+escape(this.env.mailbox)+(command=='reply-all' ? '&_all=1' : '');
+          }
+        break;      
+
+      case 'forward':
+        var uid;
+        if (uid = this.get_single_uid())
+          {
+          this.set_busy(true);
+          location.href = this.env.comm_path+'&_action=compose&_forward_uid='+uid+'&_mbox='+escape(this.env.mailbox);
+          }
+        break;
+        
+      case 'print':
+        var uid;
+        if (uid = this.get_single_uid())
+          {
+          this.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+escape(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
+          if (this.printwin)
+            setTimeout(this.ref+'.printwin.focus()', 20);
+          }
+        break;
+
+      case 'viewsource':
+        var uid;
+        if (uid = this.get_single_uid())
+          {          
+          this.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+this.env.uid+'&_mbox='+escape(this.env.mailbox));
+          if (this.sourcewin)
+            setTimeout(this.ref+'.sourcewin.focus()', 20);
+          }
+        break;
+
+      case 'add-contact':
+        this.add_contact(props);
+        break;
+      
+      // mail quicksearch
+      case 'search':
+        if (!props && this.gui_objects.qsearchbox)
+          props = this.gui_objects.qsearchbox.value;
+        if (props)
+          this.qsearch(escape(props), this.env.mailbox);
+        break;
+
+      // reset quicksearch        
+      case 'reset-search':
+        var s = this.env.search_request;
+        this.reset_qsearch();
+        
+        if (s)
+          this.list_mailbox(this.env.mailbox);
+        break;
+
+      // ldap search
+      case 'ldappublicsearch':
+        if (this.gui_objects.ldappublicsearchform) 
+          this.gui_objects.ldappublicsearchform.submit();
+        else 
+          this.ldappublicsearch(command);
+        break; 
+
+
+      // user settings commands
+      case 'preferences':
+        location.href = this.env.comm_path;
+        break;
+
+      case 'identities':
+        location.href = this.env.comm_path+'&_action=identities';
+        break;
+          
+      case 'delete-identity':
+        this.delete_identity();
+        
+      case 'folders':
+        location.href = this.env.comm_path+'&_action=folders';
+        break;
+
+      case 'subscribe':
+        this.subscribe_folder(props);
+        break;
+
+      case 'unsubscribe':
+        this.unsubscribe_folder(props);
+        break;
+        
+      case 'create-folder':
+        this.create_folder(props);
+        break;
+
+      case 'rename-folder':
+        this.rename_folder(props);
+        break;
+
+      case 'delete-folder':
+        if (confirm(this.get_label('deletefolderconfirm')))
+          this.delete_folder(props);
+        break;
+
+      }
+
+    return obj ? false : true;
+    };
+
+
+  // set command enabled or disabled
+  this.enable_command = function()
+    {
+    var args = arguments;
+    if(!args.length) return -1;
+
+    var command;
+    var enable = args[args.length-1];
+    
+    for(var n=0; n<args.length-1; n++)
+      {
+      command = args[n];
+      this.commands[command] = enable;
+      this.set_button(command, (enable ? 'act' : 'pas'));
+      }
+      return true;
+    };
+
+
+  // lock/unlock interface
+  this.set_busy = function(a, message)
+    {
+    if (a && message)
+      {
+      var msg = this.get_label(message);
+      if (msg==message)        
+        msg = 'Loading...';
+
+      this.display_message(msg, 'loading', true);
+      }
+    else if (!a && this.busy)
+      this.hide_message();
+
+    this.busy = a;
+    //document.body.style.cursor = a ? 'wait' : 'default';
+    
+    if (this.gui_objects.editform)
+      this.lock_form(this.gui_objects.editform, a);
+      
+    // clear pending timer
+    if (this.request_timer)
+      clearTimeout(this.request_timer);
+
+    // set timer for requests
+    if (a && this.env.request_timeout)
+      this.request_timer = setTimeout(this.ref+'.request_timed_out()', this.env.request_timeout * 1000);
+    };
+
+
+  // return a localized string
+  this.get_label = function(name)
+    {
+    if (this.labels[name])
+      return this.labels[name];
+    else
+      return name;
+    };
+
+
+  // switch to another application task
+  this.switch_task = function(task)
+    {
+    if (this.task===task && task!='mail')
+      return;
+
+    var url = this.get_task_url(task);
+    if (task=='mail')
+      url += '&_mbox=INBOX';
+
+    this.set_busy(true);
+    location.href = url;
+    };
+
+
+  this.get_task_url = function(task, url)
+    {
+    if (!url)
+      url = this.env.comm_path;
+
+    return url.replace(/_task=[a-z]+/, '_task='+task);
+    };
+    
+  
+  // called when a request timed out
+  this.request_timed_out = function()
+    {
+    this.set_busy(false);
+    this.display_message('Request timed out!', 'error');
+    };
+
+
+  /*********************************************************/
+  /*********        event handling methods         *********/
+  /*********************************************************/
+
+
+  // onmouseup handler for mailboxlist item
+  this.mbox_mouse_up = function(mbox)
+    {
+    if (this.drag_active)
+      {
+      this.unfocus_mailbox(mbox);
+      this.command('moveto', mbox);
+      }
+    else
+      this.command('list', mbox);
+  
+    return false;
+    };
+
+
+  // onmousedown-handler of message list row
+  this.drag_row = function(e, id)
+    {
+    this.in_selection_before = this.in_selection(id) ? id : false;
+
+    // don't do anything (another action processed before)
+    if (this.dont_select)
+      return false;
+
+    // selects currently unselected row
+    if (!this.in_selection_before && !this.list_rows[id].clicked)
+    {
+         var mod_key = this.get_modifier(e);
+         this.select_row(id,mod_key,false);
+    }
+    
+    if (this.selection.length)
+      {
+      this.drag_start = true;
+      document.onmousemove = function(e){ return rcube_webmail_client.drag_mouse_move(e); };
+      document.onmouseup = function(e){ return rcube_webmail_client.drag_mouse_up(e); };
+      }
+
+    return false;
+    };
+
+
+  // onmouseup-handler of message list row
+  this.click_row = function(e, id)
+    {
+    var mod_key = this.get_modifier(e);
+
+    // don't do anything (another action processed before)
+    if (this.dont_select)
+      {
+      this.dont_select = false;
+      return false;
+      }
+    
+    // unselects currently selected row    
+    if (!this.drag_active && this.in_selection_before==id && !this.list_rows[id].clicked)
+      this.select_row(id,mod_key,false);
+
+    this.drag_start = false;
+    this.in_selection_before = false;
+        
+    // row was double clicked
+    if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && this.in_selection(id))
+      {
+      if (this.env.mailbox==this.env.drafts_mailbox)
+        {
+        this.set_busy(true);
+        location.href = this.env.comm_path+'&_action=compose&_draft_uid='+id+'&_mbox='+escape(this.env.mailbox);
+        }
+      else
+        {
+        this.show_message(id);
+        }
+      return false;
+      }
+    else if (this.task=='addressbook')
+      {
+      if (this.contact_rows && this.selection.length==1)
+        {
+        this.load_contact(this.selection[0], 'show', true);
+        // change the text for the add contact button
+        var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
+        for (i = 0; i < links.length; i++)
+          {
+          var onclickstring = new String(links[i].onclick);
+          if (onclickstring.search('\"add\"') != -1)
+            links[i].title = this.env.newcontact;
+          }
+        }
+      else if (this.contact_rows && this.contact_rows[id].clicked)
+        {
+        this.load_contact(id, 'show');
+        return false;
+        }
+      else if (this.ldap_contact_rows && !this.ldap_contact_rows[id].clicked)
+        {
+        // clear selection
+        parent.rcmail.clear_selection();
+
+        // disable delete
+        parent.rcmail.set_button('delete', 'pas');
+
+        // change the text for the add contact button
+        var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
+        for (i = 0; i < links.length; i++)
+          {
+          var onclickstring = new String(links[i].onclick);
+          if (onclickstring.search('\"add\"') != -1)
+            links[i].title = this.env.addcontact;
+          }
+        }
+      // handle double click event
+      else if (this.ldap_contact_rows && this.selection.length==1 && this.ldap_contact_rows[id].clicked)
+        this.command('compose', this.ldap_contact_rows[id].obj.cells[1].innerHTML);
+      else if (this.env.contentframe)
+        {
+        var elm = document.getElementById(this.env.contentframe);
+        elm.style.visibility = 'hidden';
+        }
+      }
+    else if (this.task=='settings')
+      {
+      if (this.selection.length==1)
+        this.command('edit', this.selection[0]);
+      }
+
+    this.list_rows[id].clicked = true;
+    setTimeout(this.ref+'.list_rows['+id+'].clicked=false;', this.dblclick_time);
+      
+    return false;
+    };
+
+
+
+  /*********************************************************/
+  /*********     (message) list functionality      *********/
+  /*********************************************************/
+
+  // get next and previous rows that are not hidden
+  this.get_next_row = function(){
+       if (!this.list_rows) return false;
+    var last_selected_row = this.list_rows[this.last_selected];
+    var new_row = last_selected_row.obj.nextSibling;
+    while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) {
+      new_row = new_row.nextSibling;
+    }
+    return new_row;
+  }
+  
+  this.get_prev_row = function(){
+    if (!this.list_rows) return false;
+    var last_selected_row = this.list_rows[this.last_selected];
+    var new_row = last_selected_row.obj.previousSibling;
+    while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) {
+      new_row = new_row.previousSibling;
+    }
+    return new_row;
+  }
+  
+  // highlight/unhighlight a row
+  this.highlight_row = function(id, multiple)
+    {
+    var selected = false
+    
+    if (this.list_rows[id] && !multiple)
+      {
+      this.clear_selection();
+      this.selection[0] = id;
+      this.list_rows[id].obj.className += ' selected';
+      selected = true;
+      }
+    
+    else if (this.list_rows[id])
+      {
+      if (!this.in_selection(id))  // select row
+        {
+        this.selection[this.selection.length] = id;
+        this.set_classname(this.list_rows[id].obj, 'selected', true);
+        }
+      else  // unselect row
+        {
+        var p = find_in_array(id, this.selection);
+        var a_pre = this.selection.slice(0, p);
+        var a_post = this.selection.slice(p+1, this.selection.length);
+        this.selection = a_pre.concat(a_post);
+        this.set_classname(this.list_rows[id].obj, 'selected', false);
+        this.set_classname(this.list_rows[id].obj, 'unfocused', false);
+        }
+      selected = (this.selection.length==1);
+      }
+
+    // enable/disable commands for message
+    if (this.task=='mail')
+      {
+      if (this.env.mailbox==this.env.drafts_mailbox)
+       {
+       this.enable_command('show', selected);
+       this.enable_command('delete', 'moveto', this.selection.length>0 ? true : false);
+        }
+      else
+        {
+        this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', selected);
+        this.enable_command('delete', 'moveto', this.selection.length>0 ? true : false);
+        }
+      }
+    else if (this.task=='addressbook')
+      {
+      this.enable_command('edit', /*'print',*/ selected);
+      this.enable_command('delete', 'compose', this.selection.length>0 ? true : false);
+      }
+    };
+
+
+// selects or unselects the proper row depending on the modifier key pressed
+  this.select_row = function(id,mod_key,with_mouse)  { 
+       if (!mod_key) {
+      this.shift_start = id;
+         this.highlight_row(id, false);
+    } else {
+      switch (mod_key) {
+        case SHIFT_KEY: { 
+          this.shift_select(id,false); 
+          break; }
+        case CONTROL_KEY: { 
+          this.shift_start = id;
+          if (!with_mouse)
+            this.highlight_row(id, true); 
+          break; 
+          }
+        case CONTROL_SHIFT_KEY: { 
+          this.shift_select(id,true);
+          break;
+          }
+        default: {
+          this.highlight_row(id, false); 
+          break;
+          }
+      }
+       }
+       if (this.last_selected != 0 && this.list_rows[this.last_selected])
+         this.set_classname(this.list_rows[this.last_selected].obj, 'focused', false);
+
+    this.last_selected = id;
+    this.set_classname(this.list_rows[id].obj, 'focused', true);        
+  };
+
+  this.shift_select = function(id, control) {
+    var from_rowIndex = this.list_rows[this.shift_start].obj.rowIndex;
+    var to_rowIndex = this.list_rows[id].obj.rowIndex;
+        
+    var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex);
+    var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
+    
+       // iterate through the entire message list
+    for (var n in this.list_rows) {
+      if ((this.list_rows[n].obj.rowIndex >= i) && (this.list_rows[n].obj.rowIndex <= j)) {
+        if (!this.in_selection(n))
+          this.highlight_row(n, true);
+      } else {
+        if  (this.in_selection(n) && !control)
+          this.highlight_row(n, true);
+      }
+    }
+  };
+  
+
+  this.clear_selection = function()
+    {
+    for(var n=0; n<this.selection.length; n++)
+      if (this.list_rows[this.selection[n]]) {
+        this.set_classname(this.list_rows[this.selection[n]].obj, 'selected', false);
+        this.set_classname(this.list_rows[this.selection[n]].obj, 'unfocused', false);
+         }
+    this.selection = new Array();    
+    };
+
+
+  // check if given id is part of the current selection
+  this.in_selection = function(id)
+    {
+    for(var n in this.selection)
+      if (this.selection[n]==id)
+        return true;
+
+    return false;    
+    };
+
+
+  // select each row in list
+  this.select_all = function(filter)
+    {
+    if (!this.list_rows || !this.list_rows.length)
+      return false;
+      
+    // reset selection first
+    this.clear_selection();
+    
+    for (var n in this.list_rows)
+      {
+      if (!filter || this.list_rows[n][filter]==true)
+        {
+        this.last_selected = n;
+        this.highlight_row(n, true);
+        }
+      }
+
+    return true;  
+    };
+    
+
+  // when user doble-clicks on a row
+  this.show_message = function(id, safe)
+    {
+    var add_url = '';
+    var target = window;
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
+      {
+      target = window.frames[this.env.contentframe];
+      add_url = '&_framed=1';
+      }
+      
+    if (safe)
+      add_url = '&_safe=1';
+
+    if (id)
+      {
+      this.set_busy(true, 'loading');
+      target.location.href = this.env.comm_path+'&_action=show&_uid='+id+'&_mbox='+escape(this.env.mailbox)+add_url;
+      }
+    };
+
+
+
+  // list a specific page
+  this.list_page = function(page)
+    {
+    if (page=='next')
+      page = this.env.current_page+1;
+    if (page=='prev' && this.env.current_page>1)
+      page = this.env.current_page-1;
+      
+    if (page > 0 && page <= this.env.pagecount)
+      {
+      this.env.current_page = page;
+      
+      if (this.task=='mail')
+        this.list_mailbox(this.env.mailbox, page);
+      else if (this.task=='addressbook')
+        this.list_contacts(page);
+      }
+    };
+
+
+  // list messages of a specific mailbox
+  this.list_mailbox = function(mbox, page, sort)
+    {
+    this.last_selected = 0;
+    var add_url = '';
+    var target = window;
+
+    if (!mbox)
+      mbox = this.env.mailbox;
+
+    // add sort to url if set
+    if (sort)
+      add_url += '&_sort=' + sort;
+      
+    // set page=1 if changeing to another mailbox
+    if (!page && mbox != this.env.mailbox)
+      {
+      page = 1;
+      add_url += '&_refresh=1';
+      this.env.current_page = page;
+      this.clear_selection();
+      }
+    
+    // also send search request to get the right messages
+    if (this.env.search_request)
+      add_url += '&_search='+this.env.search_request;
+      
+    this.select_mailbox(mbox);
+
+    // load message list remotely
+    if (this.gui_objects.messagelist)
+      {
+      this.list_mailbox_remote(mbox, page, add_url);
+      return;
+      }
+    
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
+      {
+      target = window.frames[this.env.contentframe];
+      add_url += '&_framed=1';
+      }
+
+    // load message list to target frame/window
+    if (mbox)
+      {
+      this.set_busy(true, 'loading');
+      target.location.href = this.env.comm_path+'&_mbox='+escape(mbox)+(page ? '&_page='+page : '')+add_url;
+      }
+    };
+
+
+  // send remote request to load message list
+  this.list_mailbox_remote = function(mbox, page, add_url)
+    {
+    // clear message list first
+    this.clear_message_list();
+
+    // send request to server
+    var url = '_mbox='+escape(mbox)+(page ? '&_page='+page : '');
+    this.set_busy(true, 'loading');
+    this.http_request('list', url+add_url, true);
+    };
+
+
+  this.clear_message_list = function()
+    {
+    var table = this.gui_objects.messagelist;
+   
+    var tbody = document.createElement('TBODY');
+    table.insertBefore(tbody, table.tBodies[0]);
+    table.removeChild(table.tBodies[1]);
+    
+    this.message_rows = new Array();
+    this.list_rows = this.message_rows;
+    
+    };
+
+
+  this.clear_message_list_header = function()
+    {
+    var table;
+    if (table = this.gui_objects.messagelist)
+      {
+      if (table.colgroup)
+        table.removeChild(table.colgroup);
+      if (table.tHead)
+        table.removeChild(table.tHead);
+
+      var colgroup = document.createElement('COLGROUP');
+      var thead = document.createElement('THEAD');
+      table.insertBefore(colgroup, table.tBodies[0]);
+      table.insertBefore(thead, table.tBodies[0]);
+      }
+    };
+
+
+  this.expunge_mailbox = function(mbox)
+    {
+    var lock = false;
+    var add_url = '';
+    
+    // lock interface if it's the active mailbox
+    if (mbox == this.env.mailbox)
+       {
+       lock = true;
+       this.set_busy(true, 'loading');
+       add_url = '&_reload=1';
+       }
+
+    // send request to server
+    var url = '_mbox='+escape(mbox);
+    this.http_request('expunge', url+add_url, lock);
+    };
+
+
+  this.purge_mailbox = function(mbox)
+    {
+    var lock = false;
+    var add_url = '';
+    
+    if (!confirm(this.get_label('purgefolderconfirm')))
+      return false;
+    
+    // lock interface if it's the active mailbox
+    if (mbox == this.env.mailbox)
+       {
+       lock = true;
+       this.set_busy(true, 'loading');
+       add_url = '&_reload=1';
+       }
+
+    // send request to server
+    var url = '_mbox='+escape(mbox);
+    this.http_request('purge', url+add_url, lock);
+    return true;
+    };
+    
+  this.focus_mailbox = function(mbox)
+    {
+    var mbox_li;
+       if (this.drag_active && mbox != this.env.mailbox && (mbox_li = this.get_mailbox_li(mbox)))
+      this.set_classname(mbox_li, 'droptarget', true);
+    }
+    
+  this.unfocus_mailbox = function(mbox)
+    {
+    var mbox_li;
+       if (this.drag_active && (mbox_li = this.get_mailbox_li(mbox)))
+      this.set_classname(mbox_li, 'droptarget', false);
+    }
+  
+  // move selected messages to the specified mailbox
+  this.move_messages = function(mbox)
+    {
+    // exit if no mailbox specified or if selection is empty
+    if (!mbox || !(this.selection.length || this.env.uid) || mbox==this.env.mailbox)
+      return;
+    
+    var a_uids = new Array();
+
+    if (this.env.uid)
+      a_uids[a_uids.length] = this.env.uid;
+    else
+      {
+      var id;
+      for (var n=0; n<this.selection.length; n++)
+        {
+        id = this.selection[n];
+        a_uids[a_uids.length] = id;
+      
+        // 'remove' message row from list (just hide it)
+        if (this.message_rows[id].obj)
+          this.message_rows[id].obj.style.display = 'none';
+        }
+      next_row = this.get_next_row();
+      prev_row = this.get_prev_row();
+      new_row = (next_row) ? next_row : prev_row;
+      if (new_row) this.select_row(new_row.uid,false,false);
+      }
+      
+    var lock = false;
+
+    // show wait message
+    if (this.env.action=='show')
+      {
+      lock = true;
+      this.set_busy(true, 'movingmessage');
+      }
+    // send request to server
+    this.http_request('moveto', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_target_mbox='+escape(mbox)+'&_from='+(this.env.action ? this.env.action : ''), lock);
+    };
+
+  this.permanently_remove_messages = function() {
+    // exit if no mailbox specified or if selection is empty
+    if (!(this.selection.length || this.env.uid))
+      return;
+    
+    var a_uids = new Array();
+
+    if (this.env.uid)
+      a_uids[a_uids.length] = this.env.uid;
+    else
+      {
+      var id;
+      for (var n=0; n<this.selection.length; n++)
+        {
+        id = this.selection[n];
+        a_uids[a_uids.length] = id;
+      
+        // 'remove' message row from list (just hide it)
+        if (this.message_rows[id].obj)
+          this.message_rows[id].obj.style.display = 'none';
+        }
+      }
+      next_row = this.get_next_row();
+      prev_row = this.get_prev_row();
+      new_row = (next_row) ? next_row : prev_row;
+      if (new_row) this.select_row(new_row.uid,false,false);
+
+    // send request to server
+    this.http_request('delete', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_from='+(this.env.action ? this.env.action : ''));
+  }
+    
+    
+  // delete selected messages from the current mailbox
+  this.delete_messages = function()
+    {
+    // exit if no mailbox specified or if selection is empty
+    if (!(this.selection.length || this.env.uid))
+      return;
+    // if there is a trash mailbox defined and we're not currently in it:
+    if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase()!=String(this.env.trash_mailbox).toLowerCase())
+      this.move_messages(this.env.trash_mailbox);
+    // if there is a trash mailbox defined but we *are* in it:
+    else if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase())
+      this.permanently_remove_messages();
+    // if there isn't a defined trash mailbox and the config is set to flag for deletion
+    else if (!this.env.trash_mailbox && this.env.flag_for_deletion) {
+      flag = 'delete';
+      this.mark_message(flag);
+      if(this.env.action=="show"){
+        this.command('nextmessage','',this);
+      } else if (this.selection.length == 1) {
+        next_row = this.get_next_row();
+        prev_row = this.get_prev_row();
+        new_row = (next_row) ? next_row : prev_row;
+        if (new_row) this.select_row(new_row.uid,false,false);
+      }
+    // if there isn't a defined trash mailbox and the config is set NOT to flag for deletion
+    }else if (!this.env.trash_mailbox && !this.env.flag_for_deletion) {
+      this.permanently_remove_messages();
+    }
+    return;
+  };
+
+
+  // set a specific flag to one or more messages
+  this.mark_message = function(flag, uid)
+    {
+    var a_uids = new Array();
+    
+    if (uid)
+      a_uids[0] = uid;
+    else if (this.env.uid)
+      a_uids[0] = this.env.uid;
+    else
+      {
+      var id;
+      for (var n=0; n<this.selection.length; n++)
+        {
+        id = this.selection[n];
+        a_uids[a_uids.length] = id;
+        }
+      }
+      switch (flag) {
+        case 'read':
+        case 'unread':
+          this.toggle_read_status(flag,a_uids);
+          break;
+        case 'delete':
+        case 'undelete':
+          this.toggle_delete_status(a_uids);
+          break;
+      }
+    };
+
+  // set class to read/unread
+  this.toggle_read_status = function(flag, a_uids) {
+    // mark all message rows as read/unread
+    var icn_src;
+    for (var i=0; i<a_uids.length; i++)
+      {
+      uid = a_uids[i];
+      if (this.message_rows[uid])
+        {
+        this.message_rows[uid].unread = (flag=='unread' ? true : false);
+        
+        if (this.message_rows[uid].classname.indexOf('unread')<0 && this.message_rows[uid].unread)
+          {
+          this.message_rows[uid].classname += ' unread';
+          this.set_classname(this.message_rows[uid].obj, 'unread', true);
+
+          if (this.env.unreadicon)
+            icn_src = this.env.unreadicon;
+          }
+        else if (!this.message_rows[uid].unread)
+          {
+          this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*unread/, '');
+          this.set_classname(this.message_rows[uid].obj, 'unread', false);
+
+          if (this.message_rows[uid].replied && this.env.repliedicon)
+            icn_src = this.env.repliedicon;
+          else if (this.env.messageicon)
+            icn_src = this.env.messageicon;
+          }
+
+        if (this.message_rows[uid].icon && icn_src)
+          this.message_rows[uid].icon.src = icn_src;
+        }
+      }
+      this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
+  }
+  
+  // mark all message rows as deleted/undeleted
+  this.toggle_delete_status = function(a_uids) {
+    if (this.env.read_when_deleted) {
+      this.toggle_read_status('read',a_uids);
+    }
+    // if deleting message from "view message" don't bother with delete icon
+    if (this.env.action == "show")
+      return false;
+
+    if (a_uids.length==1){
+      if(this.message_rows[a_uids[0]].classname.indexOf('deleted') < 0 ){
+       this.flag_as_deleted(a_uids)
+      } else {
+       this.flag_as_undeleted(a_uids)
+      }
+      return true;
+    }
+    
+    var all_deleted = true;
+    
+    for (var i=0; i<a_uids.length; i++) {
+      uid = a_uids[i];
+      if (this.message_rows[uid]) {
+        if (this.message_rows[uid].classname.indexOf('deleted')<0) {
+          all_deleted = false;
+          break;
+        }
+      }
+    }
+    
+    if (all_deleted)
+      this.flag_as_undeleted(a_uids);
+    else
+      this.flag_as_deleted(a_uids);
+    
+    return true;
+  }
+
+  this.flag_as_undeleted = function(a_uids){
+    // if deleting message from "view message" don't bother with delete icon
+    if (this.env.action == "show")
+      return false;
+
+    var icn_src;
+      
+    for (var i=0; i<a_uids.length; i++) {
+      uid = a_uids[i];
+      if (this.message_rows[uid]) {
+        this.message_rows[uid].deleted = false;
+        
+        if (this.message_rows[uid].classname.indexOf('deleted') > 0) {
+          this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*deleted/, '');
+          this.set_classname(this.message_rows[uid].obj, 'deleted', false);
+        }
+        if (this.message_rows[uid].unread && this.env.unreadicon)
+          icn_src = this.env.unreadicon;
+        else if (this.message_rows[uid].replied && this.env.repliedicon)
+          icn_src = this.env.repliedicon;
+        else if (this.env.messageicon)
+          icn_src = this.env.messageicon;
+        if (this.message_rows[uid].icon && icn_src)
+          this.message_rows[uid].icon.src = icn_src;
+      }
+    }
+    this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag=undelete');
+    return true;
+  }
+  
+  this.flag_as_deleted = function(a_uids) {
+    // if deleting message from "view message" don't bother with delete icon
+    if (this.env.action == "show")
+      return false;
+
+    for (var i=0; i<a_uids.length; i++) {
+      uid = a_uids[i];
+      if (this.message_rows[uid]) {
+        this.message_rows[uid].deleted = true;
+        
+        if (this.message_rows[uid].classname.indexOf('deleted')<0) {
+          this.message_rows[uid].classname += ' deleted';
+          this.set_classname(this.message_rows[uid].obj, 'deleted', true);
+        }
+        if (this.message_rows[uid].icon && this.env.deletedicon)
+          this.message_rows[uid].icon.src = this.env.deletedicon;
+      }
+    }
+    this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag=delete');
+    return true;  
+  }
+
+
+  this.get_mailbox_li = function(mbox)
+    {
+    if (this.gui_objects.mailboxlist)
+      {
+      mbox = String((mbox ? mbox : this.env.mailbox)).toLowerCase().replace(this.mbox_expression, '');
+      return document.getElementById('rcmbx'+mbox);
+      }
+    
+    return null;
+    };
+    
+
+  /*********************************************************/
+  /*********        message compose methods        *********/
+  /*********************************************************/
+  
+  
+  // checks the input fields before sending a message
+  this.check_compose_input = function()
+    {
+    // check input fields
+    var input_to = rcube_find_object('_to');
+    var input_subject = rcube_find_object('_subject');
+    var input_message = rcube_find_object('_message');
+
+    // check for empty recipient
+    if (input_to && !rcube_check_email(input_to.value, true))
+      {
+      alert(this.get_label('norecipientwarning'));
+      input_to.focus();
+      return false;
+      }
+
+    // display localized warning for missing subject
+    if (input_subject && input_subject.value == '')
+      {
+      var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject'));
+
+      // user hit cancel, so don't send
+      if (!subject && subject !== '')
+        {
+        input_subject.focus();
+        return false;
+        }
+      else
+        {
+        input_subject.value = subject ? subject : this.get_label('nosubject');            
+        }
+      }
+
+    // check for empty body
+    if (input_message.value=='')
+      {
+      if (!confirm(this.get_label('nobodywarning')))
+        {
+        input_message.focus();
+        return false;
+        }
+      }
+
+    return true;
+    };
+
+
+  this.auto_save_start = function()
+    {
+    if (this.env.draft_autosave)
+      this.save_timer = self.setTimeout(this.ref+'.command("savedraft")', this.env.draft_autosave * 1000);
+    };
+
+
+  this.compose_field_hash = function()
+    {
+    // check input fields
+    var input_to = rcube_find_object('_to');
+    var input_cc = rcube_find_object('_to');
+    var input_bcc = rcube_find_object('_to');
+    var input_subject = rcube_find_object('_subject');
+    var input_message = rcube_find_object('_message');
+    
+    var str = '';
+    if (input_to && input_to.value)
+      str += input_to.value+':';
+    if (input_cc && input_cc.value)
+      str += input_cc.value+':';
+    if (input_bcc && input_bcc.value)
+      str += input_bcc.value+':';
+    if (input_subject && input_subject.value)
+      str += input_subject.value+':';
+    if (input_message && input_message.value)
+      str += input_message.value;
+
+    return str;
+    };
+    
+  
+  this.change_identity = function(obj)
+    {
+    if (!obj || !obj.options)
+      return false;
+
+    var id = obj.options[obj.selectedIndex].value;
+    var input_message = rcube_find_object('_message');
+    var message = input_message ? input_message.value : '';
+    var sig, p;
+
+    if (!this.env.identity)
+      this.env.identity = id
+
+    // remove the 'old' signature
+    if (this.env.identity && this.env.signatures && this.env.signatures[this.env.identity])
+      {
+      sig = this.env.signatures[this.env.identity];
+      if (sig.indexOf('--')!=0)
+        sig = '--\n'+sig;
+
+      p = message.lastIndexOf(sig);
+      if (p>=0)
+        message = message.substring(0, p-1) + message.substring(p+sig.length, message.length);
+      }
+
+    // add the new signature string
+    if (this.env.signatures && this.env.signatures[id])
+      {
+      sig = this.env.signatures[id];
+      if (sig.indexOf('--')!=0)
+        sig = '--\n'+sig;
+      message += '\n'+sig;
+      }
+
+    if (input_message)
+      input_message.value = message;
+      
+    this.env.identity = id;
+    return true;
+    };
+
+
+  this.show_attachment_form = function(a)
+    {
+    if (!this.gui_objects.uploadbox)
+      return false;
+      
+    var elm, list;
+    if (elm = this.gui_objects.uploadbox)
+      {
+      if (a &&  (list = this.gui_objects.attachmentlist))
+        {
+        var pos = rcube_get_object_pos(list);
+        var left = pos.x;
+        var top = pos.y + list.offsetHeight + 10;
+      
+        elm.style.top = top+'px';
+        elm.style.left = left+'px';
+        }
+      
+      elm.style.visibility = a ? 'visible' : 'hidden';
+      }
+      
+    // clear upload form
+    if (!a && this.gui_objects.attachmentform && this.gui_objects.attachmentform!=this.gui_objects.messageform)
+      this.gui_objects.attachmentform.reset();
+    
+    return true;  
+    };
+
+
+  // upload attachment file
+  this.upload_file = function(form)
+    {
+    
+    if (!form)
+      return false;
+      
+    // get file input fields
+    var send = false;
+    for (var n=0; n<form.elements.length; n++)
+      if (form.elements[n].type=='file' && form.elements[n].value)
+        {
+        send = true;
+        break;
+        }
+    
+    // create hidden iframe and post upload form
+    if (send)
+      {
+      var ts = new Date().getTime();
+      var frame_name = 'rcmupload'+ts;
+
+      // have to do it this way for IE
+      // otherwise the form will be posted to a new window
+      if(document.all && !window.opera)
+        {
+        var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
+        document.body.insertAdjacentHTML('BeforeEnd',html);
+        }
+      else  // for standards-compilant browsers
+        {
+        var frame = document.createElement('IFRAME');
+        frame.name = frame_name;
+        frame.width = 10;
+        frame.height = 10;
+        frame.style.visibility = 'hidden';
+        document.body.appendChild(frame);
+        }
+
+      form.target = frame_name;
+      form.action = this.env.comm_path+'&_action=upload';
+      form.setAttribute('enctype', 'multipart/form-data');
+      form.submit();
+      }
+    
+    // set reference to the form object
+    this.gui_objects.attachmentform = form;
+    return true;
+    };
+
+
+  // add file name to attachment list
+  // called from upload page
+  this.add2attachment_list = function(name, content)
+    {
+    if (!this.gui_objects.attachmentlist)
+      return false;
+
+    var li = document.createElement('LI');
+    li.id = name;
+    li.innerHTML = content;
+    this.gui_objects.attachmentlist.appendChild(li);
+    return true;
+    };
+
+  this.remove_from_attachment_list = function(name)
+    {
+    if (!this.gui_objects.attachmentlist)
+      return false;
+
+    var list = this.gui_objects.attachmentlist.getElementsByTagName("li");
+    for (i=0;i<list.length;i++)
+      if (list[i].id == name)
+        this.gui_objects.attachmentlist.removeChild(list[i]);
+    };
+
+  this.remove_attachment = function(name)
+    {
+    if (name)
+      this.http_request('remove-attachment', '_file='+escape(name));
+
+    return true;
+    };
+
+  // send remote request to add a new contact
+  this.add_contact = function(value)
+    {
+    if (value)
+      this.http_request('addcontact', '_address='+value);
+    
+    return true;
+    };
+
+  // send remote request to search mail
+  this.qsearch = function(value, mbox)
+    {
+    if (value && mbox)
+      {
+      this.clear_message_list();
+      this.set_busy(true, 'searching');
+      this.http_request('search', '_search='+value+'&_mbox='+mbox, true);
+      }
+    return true;
+    };
+
+  // reset quick-search form
+  this.reset_qsearch = function()
+    {
+    if (this.gui_objects.qsearchbox)
+      this.gui_objects.qsearchbox.value = '';
+      
+    this.env.search_request = null;
+    return true;
+    };
+
+
+  this.sent_successfully = function(msg)
+    {
+    this.list_mailbox();
+    this.display_message(msg, 'confirmation', true);
+    }
+
+
+  /*********************************************************/
+  /*********     keyboard live-search methods      *********/
+  /*********************************************************/
+
+
+  // handler for keyboard events on address-fields
+  this.ksearch_keypress = function(e, obj)
+    {
+    if (typeof(this.env.contacts)!='object' || !this.env.contacts.length)
+      return true;
+
+    if (this.ksearch_timer)
+      clearTimeout(this.ksearch_timer);
+
+    if (!e)
+      e = window.event;
+      
+    var highlight;
+    var key = e.keyCode ? e.keyCode : e.which;
+
+    switch (key)
+      {
+      case 38:  // key up
+      case 40:  // key down
+        if (!this.ksearch_pane)
+          break;
+          
+        var dir = key==38 ? 1 : 0;
+        var next;
+        
+        highlight = document.getElementById('rcmksearchSelected');
+        if (!highlight)
+          highlight = this.ksearch_pane.ul.firstChild;
+        
+        if (highlight && (next = dir ? highlight.previousSibling : highlight.nextSibling))
+          {
+          highlight.removeAttribute('id');
+          //highlight.removeAttribute('class');
+          this.set_classname(highlight, 'selected', false);
+          }
+
+        if (next)
+          {
+          next.setAttribute('id', 'rcmksearchSelected');
+          this.set_classname(next, 'selected', true);
+          this.ksearch_selected = next._rcm_id;
+          }
+
+        if (e.preventDefault)
+          e.preventDefault();
+        return false;
+
+      case 9:  // tab
+        if(e.shiftKey)
+          break;
+
+      case 13:  // enter     
+        if (this.ksearch_selected===null || !this.ksearch_input || !this.ksearch_value)
+          break;
+
+        // get cursor pos
+        var inp_value = this.ksearch_input.value.toLowerCase();
+        var cpos = this.get_caret_pos(this.ksearch_input);
+        var p = inp_value.lastIndexOf(this.ksearch_value, cpos);
+        
+        // replace search string with full address
+        var pre = this.ksearch_input.value.substring(0, p);
+        var end = this.ksearch_input.value.substring(p+this.ksearch_value.length, this.ksearch_input.value.length);
+        var insert = this.env.contacts[this.ksearch_selected]+', ';
+        this.ksearch_input.value = pre + insert + end;
+        
+        //this.ksearch_input.value = this.ksearch_input.value.substring(0, p)+insert;
+        
+        // set caret to insert pos
+        cpos = p+insert.length;
+        if (this.ksearch_input.setSelectionRange)
+          this.ksearch_input.setSelectionRange(cpos, cpos);
+        
+        // hide ksearch pane
+        this.ksearch_hide();
+      
+        if (e.preventDefault)
+          e.preventDefault();
+        return false;
+
+      case 27:  // escape
+        this.ksearch_hide();
+        break;
+
+      }
+
+    // start timer
+    this.ksearch_timer = setTimeout(this.ref+'.ksearch_get_results()', 200);      
+    this.ksearch_input = obj;
+    
+    return true;
+    };
+
+
+  // address search processor
+  this.ksearch_get_results = function()
+    {
+    var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
+    if (inp_value===null)
+      return;
+
+    // get string from current cursor pos to last comma
+    var cpos = this.get_caret_pos(this.ksearch_input);
+    var p = inp_value.lastIndexOf(',', cpos-1);
+    var q = inp_value.substring(p+1, cpos);
+
+    // trim query string
+    q = q.replace(/(^\s+|\s+$)/g, '').toLowerCase();
+
+    if (!q.length || q==this.ksearch_value)
+      {
+      if (!q.length && this.ksearch_pane && this.ksearch_pane.visible)
+        this.ksearch_pane.show(0);
+
+      return;
+      }
+
+    this.ksearch_value = q;
+    
+    // start searching the contact list
+    var a_results = new Array();
+    var a_result_ids = new Array();
+    var c=0;
+    for (var i=0; i<this.env.contacts.length; i++)
+      {
+      if (this.env.contacts[i].toLowerCase().indexOf(q)>=0)
+        {
+        a_results[c] = this.env.contacts[i];
+        a_result_ids[c++] = i;
+        
+        if (c==15)  // limit search results
+          break;
+        }
+      }
+
+    // display search results
+    if (c && a_results.length)
+      {
+      var p, ul, li;
+      
+      // create results pane if not present
+      if (!this.ksearch_pane)
+        {
+        ul = document.createElement('UL');
+        this.ksearch_pane = new rcube_layer('rcmKSearchpane', {vis:0, zindex:30000});
+        this.ksearch_pane.elm.appendChild(ul);
+        this.ksearch_pane.ul = ul;
+        }
+      else
+        ul = this.ksearch_pane.ul;
+
+      // remove all search results
+      ul.innerHTML = '';
+            
+      // add each result line to list
+      for (i=0; i<a_results.length; i++)
+        {
+        li = document.createElement('LI');
+        li.innerHTML = a_results[i].replace(/</, '&lt;').replace(/>/, '&gt;');
+        li._rcm_id = a_result_ids[i];
+        ul.appendChild(li);
+        }
+
+      // check if last selected item is still in result list
+      if (this.ksearch_selected!==null)
+        {
+        p = find_in_array(this.ksearch_selected, a_result_ids);
+        if (p>=0 && ul.childNodes)
+          {
+          ul.childNodes[p].setAttribute('id', 'rcmksearchSelected');
+          this.set_classname(ul.childNodes[p], 'selected', true);
+          }
+        else
+          this.ksearch_selected = null;
+        }
+      
+      // if no item selected, select the first one
+      if (this.ksearch_selected===null)
+        {
+        ul.firstChild.setAttribute('id', 'rcmksearchSelected');
+        this.set_classname(ul.firstChild, 'selected', true);
+        this.ksearch_selected = a_result_ids[0];
+        }
+
+      // resize the containing layer to fit the list
+      //this.ksearch_pane.resize(ul.offsetWidth, ul.offsetHeight);
+    
+      // move the results pane right under the input box and make it visible
+      var pos = rcube_get_object_pos(this.ksearch_input);
+      this.ksearch_pane.move(pos.x, pos.y+this.ksearch_input.offsetHeight);
+      this.ksearch_pane.show(1); 
+      }
+    // hide results pane
+    else
+      this.ksearch_hide();
+    };
+
+
+  this.ksearch_blur = function(e, obj)
+    {
+    if (this.ksearch_timer)
+      clearTimeout(this.ksearch_timer);
+
+    this.ksearch_value = '';      
+    this.ksearch_input = null;
+    
+    this.ksearch_hide();
+    };
+
+
+  this.ksearch_hide = function()
+    {
+    this.ksearch_selected = null;
+    
+    if (this.ksearch_pane)
+      this.ksearch_pane.show(0);    
+    };
+
+
+
+  /*********************************************************/
+  /*********         address book methods          *********/
+  /*********************************************************/
+
+
+  this.list_contacts = function(page)
+    {
+    var add_url = '';
+    var target = window;
+    
+    if (page && this.current_page==page)
+      return false;
+
+    // load contacts remotely
+    if (this.gui_objects.contactslist)
+      {
+      this.list_contacts_remote(page);
+      return;
+      }
+
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
+      {
+      target = window.frames[this.env.contentframe];
+      add_url = '&_framed=1';
+      }
+
+    this.set_busy(true, 'loading');
+    location.href = this.env.comm_path+(page ? '&_page='+page : '')+add_url;
+    };
+
+
+  // send remote request to load contacts list
+  this.list_contacts_remote = function(page)
+    {
+    // clear list
+    var table = this.gui_objects.contactslist;
+    var tbody = document.createElement('TBODY');
+    table.insertBefore(tbody, table.tBodies[0]);
+    table.tBodies[1].style.display = 'none';
+    
+    this.contact_rows = new Array();
+    this.list_rows = this.contact_rows;
+
+    // send request to server
+    var url = page ? '&_page='+page : '';
+    this.set_busy(true, 'loading');
+    this.http_request('list', url, true);
+    };
+
+
+  // load contact record
+  this.load_contact = function(cid, action, framed)
+    {
+    var add_url = '';
+    var target = window;
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
+      {
+      add_url = '&_framed=1';
+      target = window.frames[this.env.contentframe];
+      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
+      }
+    else if (framed)
+      return false;
+      
+    //if (this.env.framed && add_url=='')
+    
+    //  add_url = '&_framed=1';
+    
+    if (action && (cid || action=='add'))
+      {
+      this.set_busy(true);
+      target.location.href = this.env.comm_path+'&_action='+action+'&_cid='+cid+add_url;
+      }
+    return true;
+    };
+
+
+  this.delete_contacts = function()
+    {
+    // exit if no mailbox specified or if selection is empty
+    if (!(this.selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
+      return;
+      
+    var a_cids = new Array();
+
+    if (this.env.cid)
+      a_cids[a_cids.length] = this.env.cid;
+    else
+      {
+      var id;
+      for (var n=0; n<this.selection.length; n++)
+        {
+        id = this.selection[n];
+        a_cids[a_cids.length] = id;
+      
+        // 'remove' row from list (just hide it)
+        if (this.contact_rows[id].obj)
+          this.contact_rows[id].obj.style.display = 'none';
+        }
+
+      // hide content frame if we delete the currently displayed contact
+      if (this.selection.length==1 && this.env.contentframe)
+        {
+        var elm = document.getElementById(this.env.contentframe);
+        elm.style.visibility = 'hidden';
+        }
+      }
+
+    // send request to server
+    this.http_request('delete', '_cid='+a_cids.join(',')+'&_from='+(this.env.action ? this.env.action : ''));
+    return true;
+    };
+
+
+  // update a contact record in the list
+  this.update_contact_row = function(cid, cols_arr)
+    {
+    if (!this.contact_rows[cid] || !this.contact_rows[cid].obj)
+      return false;
+      
+    var row = this.contact_rows[cid].obj;
+    for (var c=0; c<cols_arr.length; c++){
+      if (row.cells[c])
+        row.cells[c].innerHTML = cols_arr[c];
+    }
+    return true;
+    };
+  
+  
+  // load ldap search form
+  this.ldappublicsearch = function(action)
+    {
+    var add_url = '';
+    var target = window;
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
+      {
+      add_url = '&_framed=1';
+      target = window.frames[this.env.contentframe];
+      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
+      }
+    else
+      return false; 
+
+
+    if (action == 'ldappublicsearch')
+      target.location.href = this.env.comm_path+'&_action='+action+add_url;
+      
+    return true;
+    };
+  // add ldap contacts to address book
+  this.add_ldap_contacts = function()
+    {
+    if (window.frames[this.env.contentframe].rcmail)
+      {
+      var frame = window.frames[this.env.contentframe];
+
+      // build the url
+      var url    = '&_framed=1';
+      var emails = '&_emails=';
+      var names  = '&_names=';
+      var end    = '';
+      for (var n=0; n<frame.rcmail.selection.length; n++)
+        {
+        end = n < frame.rcmail.selection.length - 1 ? ',' : '';
+        emails += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[1].innerHTML + end;
+        names  += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[0].innerHTML + end;
+        }
+       
+      frame.location.href = this.env.comm_path + '&_action=save&_framed=1' + emails + names;
+      }
+    return false;
+    }
+  
+
+
+  /*********************************************************/
+  /*********        user settings methods          *********/
+  /*********************************************************/
+
+
+  // load contact record
+  this.load_identity = function(id, action)
+    {
+    if (action=='edit-identity' && (!id || id==this.env.iid))
+      return false;
+
+    var add_url = '';
+    var target = window;
+    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
+      {
+      add_url = '&_framed=1';
+      target = window.frames[this.env.contentframe];
+      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
+      }
+
+    if (action && (id || action=='add-identity'))
+      {
+      this.set_busy(true);
+      target.location.href = this.env.comm_path+'&_action='+action+'&_iid='+id+add_url;
+      }
+    return true;
+    };
+
+
+
+  this.delete_identity = function(id)
+    {
+    // exit if no mailbox specified or if selection is empty
+    if (!(this.selection.length || this.env.iid))
+      return;
+    
+    if (!id)
+      id = this.env.iid ? this.env.iid : this.selection[0];
+
+/*
+    // 'remove' row from list (just hide it)
+    if (this.identity_rows && this.identity_rows[id].obj)
+      {
+      this.clear_selection();
+      this.identity_rows[id].obj.style.display = 'none';
+      }
+*/
+
+    // if (this.env.framed && id)
+      this.set_busy(true);
+      location.href = this.env.comm_path+'&_action=delete-identity&_iid='+id;     
+    // else if (id)
+    //  this.http_request('delete-identity', '_iid='+id);
+    return true;
+    };
+
+
+  // tell server to create and subscribe a new mailbox
+  this.create_folder = function(name)
+    {
+       if (this.edit_folder)
+         this.reset_folder_rename();
+
+    var form;
+    if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
+      name = form.elements['_folder_name'].value;
+
+    if (name)
+      this.http_request('create-folder', '_name='+escape(name), true);
+    else if (form.elements['_folder_name'])
+      form.elements['_folder_name'].focus();
+    };
+
+
+  // entry point for folder renaming
+  this.rename_folder = function(props)
+    {
+    var form, oldname, newname;
+    
+    // rename a specific mailbox
+    if (props)
+      this.edit_foldername(props);
+
+    // use a dropdown and input field (old behavior)
+    else if ((form = this.gui_objects.editform) && form.elements['_folder_oldname'] && form.elements['_folder_newname'])
+      {
+      oldname = form.elements['_folder_oldname'].value;
+      newname = form.elements['_folder_newname'].value;
+      }
+
+    if (oldname && newname)
+      this.http_request('rename-folder', '_folder_oldname='+escape(oldname)+'&_folder_newname='+escape(newname));
+    };
+
+
+  // start editing the mailbox name.
+  // this will replace the name string with an input field
+  this.edit_foldername = function(folder)
+    {
+    var temp, row, form;
+    var id = this.get_folder_row_id(folder);
+
+    // reset current renaming
+       if (temp = this.edit_folder)
+         {
+         this.reset_folder_rename();
+         if (temp == id)
+           return;
+         }
+
+    if (id && (row = document.getElementById(id)))
+      {
+      this.name_input = document.createElement('INPUT');
+      this.name_input.value = this.env.subscriptionrows[id];
+      this.name_input.style.width = '100%';
+      this.name_input.onkeypress = function(e){ rcmail.name_input_keypress(e); };
+      
+      row.cells[0].replaceChild(this.name_input, row.cells[0].firstChild);
+      this.edit_folder = id;
+      this.name_input.select();
+      
+      if (form = this.gui_objects.editform)
+        form.onsubmit = function(){ return false; };
+      }
+    };
+
+
+  // remove the input field and write the current mailbox name to the table cell
+  this.reset_folder_rename = function()
+    {
+    var cell = this.name_input ? this.name_input.parentNode : null;
+    if (cell && this.edit_folder)
+      cell.innerHTML = this.env.subscriptionrows[this.edit_folder];
+      
+    this.edit_folder = null;
+    };
+
+
+  // handler for keyboard events on the input field
+  this.name_input_keypress = function(e)
+    {
+    var key = document.all ? event.keyCode : document.getElementById ? e.keyCode : 0;
+
+    // enter
+    if (key==13)
+      {
+      var newname = this.name_input ? this.name_input.value : null;
+      if (this.edit_folder && newname)
+        this.http_request('rename-folder', '_folder_oldname='+escape(this.env.subscriptionrows[this.edit_folder])+'&_folder_newname='+escape(newname));        
+      }
+    // escape
+    else if (key==27)
+      this.reset_folder_rename();
+    };
+
+
+  // delete a specific mailbox with all its messages
+  this.delete_folder = function(folder)
+    {
+       if (this.edit_folder)
+         this.reset_folder_rename();
+    
+    if (folder)
+      this.http_request('delete-folder', '_mboxes='+escape(folder));
+    };
+
+
+  // add a new folder to the subscription list by cloning a folder row
+  this.add_folder_row = function(name, replace)
+    {
+    name = name.replace('\\',"");
+    if (!this.gui_objects.subscriptionlist)
+      return false;
+
+    for (var refid in this.env.subscriptionrows)
+      if (this.env.subscriptionrows[refid]!=null)
+        break;
+
+    var refrow, form;
+    var tbody = this.gui_objects.subscriptionlist.tBodies[0];
+    var id = replace && replace.id ? replace.id : tbody.childNodes.length+1;
+
+    if (!id || !(refrow = document.getElementById(refid)))
+      {
+      // Refresh page if we don't have a table row to clone
+      location.href = this.env.comm_path+'&_action=folders';
+      }
+    else
+      {
+      // clone a table row if there are existing rows
+      var row = this.clone_table_row(refrow);
+      row.id = 'rcmrow'+id;
+      if (replace)
+        tbody.replaceChild(row, replace);
+      else
+        tbody.appendChild(row);
+      }
+
+    // add to folder/row-ID map
+    this.env.subscriptionrows[row.id] = name;
+
+    // set folder name
+    row.cells[0].innerHTML = name;
+    if (row.cells[1] && row.cells[1].firstChild.tagName=='INPUT')
+      {
+      row.cells[1].firstChild.value = name;
+      row.cells[1].firstChild.checked = true;
+      }
+       
+    if (row.cells[2] && row.cells[2].firstChild.tagName=='A')
+      row.cells[2].firstChild.onclick = new Function(this.ref+".command('rename-folder','"+name.replace('\'','\\\'')+"')");
+    if (row.cells[3] && row.cells[3].firstChild.tagName=='A')
+      row.cells[3].firstChild.onclick = new Function(this.ref+".command('delete-folder','"+name.replace('\'','\\\'')+"')");
+
+    // add new folder to rename-folder list and clear input field
+    if (!replace && (form = this.gui_objects.editform))
+      {
+      if (form.elements['_folder_oldname'])
+        form.elements['_folder_oldname'].options[form.elements['_folder_oldname'].options.length] = new Option(name,name);
+      if (form.elements['_folder_name'])
+        form.elements['_folder_name'].value = ''; 
+      }
+
+    };
+
+
+  // replace an existing table row with a new folder line
+  this.replace_folder_row = function(newfolder, oldfolder)
+    {
+    var id = this.get_folder_row_id(oldfolder);
+    var row = document.getElementById(id);
+    
+    // replace an existing table row (if found)
+    this.add_folder_row(newfolder, row);
+    this.env.subscriptionrows[id] = null;
+    
+    // rename folder in rename-folder dropdown
+    var form, elm;
+    if ((form = this.gui_objects.editform) && (elm = form.elements['_folder_oldname']))
+      {
+      for (var i=0;i<elm.options.length;i++)
+        {
+        if (elm.options[i].value == oldfolder)
+          {
+          elm.options[i].text = newfolder;
+          elm.options[i].value = newfolder;
+          break;
+          }
+        }
+
+      form.elements['_folder_newname'].value = '';
+      }
+    };
+    
+
+  // remove the table row of a specific mailbox from the table
+  // (the row will not be removed, just hidden)
+  this.remove_folder_row = function(folder)
+    {
+    var row;
+    var id = this.get_folder_row_id(folder);
+    if (id && (row = document.getElementById(id)))
+      row.style.display = 'none';    
+
+    // remove folder from rename-folder list
+    var form;
+    if ((form = this.gui_objects.editform) && form.elements['_folder_oldname'])
+      {
+      for (var i=0;i<form.elements['_folder_oldname'].options.length;i++)
+        {
+        if (form.elements['_folder_oldname'].options[i].value == folder) 
+          {
+          form.elements['_folder_oldname'].options[i] = null;
+          break;
+          }
+        }
+      }
+    
+    if (form && form.elements['_folder_newname'])
+      form.elements['_folder_newname'].value = '';
+    };
+
+
+  this.subscribe_folder = function(folder)
+    {
+    var form;
+    if ((form = this.gui_objects.editform) && form.elements['_unsubscribed'])
+      this.change_subscription('_unsubscribed', '_subscribed', 'subscribe');
+    else if (folder)
+      this.http_request('subscribe', '_mboxes='+escape(folder));
+    };
+
+
+  this.unsubscribe_folder = function(folder)
+    {
+    var form;
+    if ((form = this.gui_objects.editform) && form.elements['_subscribed'])
+      this.change_subscription('_subscribed', '_unsubscribed', 'unsubscribe');
+    else if (folder)
+      this.http_request('unsubscribe', '_mboxes='+escape(folder));
+    };
+    
+
+  this.change_subscription = function(from, to, action)
+    {
+    var form;
+    if (form = this.gui_objects.editform)
+      {
+      var a_folders = new Array();
+      var list_from = form.elements[from];
+
+      for (var i=0; list_from && i<list_from.options.length; i++)
+        {
+        if (list_from.options[i] && list_from.options[i].selected)
+          {
+          a_folders[a_folders.length] = list_from.options[i].value;
+          list_from[i] = null;
+          i--;
+          }
+        }
+
+      // yes, we have some folders selected
+      if (a_folders.length)
+        {
+        var list_to = form.elements[to];
+        var index;
+        
+        for (var n=0; n<a_folders.length; n++)
+          {
+          index = list_to.options.length;
+          list_to[index] = new Option(a_folders[n]);
+          }
+          
+        this.http_request(action, '_mboxes='+escape(a_folders.join(',')));
+        }
+      }
+      
+    };
+
+  // helper method to find a specific mailbox row ID
+  this.get_folder_row_id = function(folder)
+    {
+    for (var id in this.env.subscriptionrows)
+      if (this.env.subscriptionrows[id]==folder)
+        break;
+        
+    return id;
+    };
+
+  // duplicate a specific table row
+  this.clone_table_row = function(row)
+    {
+    var cell, td;
+    var new_row = document.createElement('TR');
+    for(var n=0; n<row.childNodes.length; n++)
+      {
+      cell = row.childNodes[n];
+      td = document.createElement('TD');
+
+      if (cell.className)
+        td.className = cell.className;
+      if (cell.align)
+        td.setAttribute('align', cell.align);
+        
+      td.innerHTML = cell.innerHTML;
+      new_row.appendChild(td);
+      }
+    
+    return new_row;
+    };
+
+
+  /*********************************************************/
+  /*********           GUI functionality           *********/
+  /*********************************************************/
+
+
+  // eable/disable buttons for page shifting
+  this.set_page_buttons = function()
+    {
+    this.enable_command('nextpage', (this.env.pagecount > this.env.current_page));
+    this.enable_command('previouspage', (this.env.current_page > 1));
+    }
+
+
+  // set button to a specific state
+  this.set_button = function(command, state)
+    {
+    var a_buttons = this.buttons[command];
+    var button, obj;
+
+    if(!a_buttons || !a_buttons.length)
+      return;
+
+    for(var n=0; n<a_buttons.length; n++)
+      {
+      button = a_buttons[n];
+      obj = document.getElementById(button.id);
+
+      // get default/passive setting of the button
+      if (obj && button.type=='image' && !button.status)
+        button.pas = obj._original_src ? obj._original_src : obj.src;
+      else if (obj && !button.status)
+        button.pas = String(obj.className);
+
+      // set image according to button state
+      if (obj && button.type=='image' && button[state])
+        {
+        button.status = state;        
+        obj.src = button[state];
+        }
+      // set class name according to button state
+      else if (obj && typeof(button[state])!='undefined')
+        {
+        button.status = state;        
+        obj.className = button[state];        
+        }
+      // disable/enable input buttons
+      if (obj && button.type=='input')
+        {
+        button.status = state;
+        obj.disabled = !state;
+        }
+      }
+    };
+
+
+  // mouse over button
+  this.button_over = function(command, id)
+    {
+    var a_buttons = this.buttons[command];
+    var button, img;
+
+    if(!a_buttons || !a_buttons.length)
+      return;
+
+    for(var n=0; n<a_buttons.length; n++)
+      {
+      button = a_buttons[n];
+      if(button.id==id && button.status=='act')
+        {
+        img = document.getElementById(button.id);
+        if (img && button.over)
+          img.src = button.over;
+        }
+      }
+    };
+
+  // mouse down on button
+  this.button_sel = function(command, id)
+    {
+    var a_buttons = this.buttons[command];
+    var button, img;
+
+    if(!a_buttons || !a_buttons.length)
+      return;
+
+    for(var n=0; n<a_buttons.length; n++)
+      {
+      button = a_buttons[n];
+      if(button.id==id && button.status=='act')
+        {
+        img = document.getElementById(button.id);
+        if (img && button.sel)
+          img.src = button.sel;
+        }
+      }
+    };
+
+  // mouse out of button
+  this.button_out = function(command, id)
+    {
+    var a_buttons = this.buttons[command];
+    var button, img;
+
+    if(!a_buttons || !a_buttons.length)
+      return;
+
+    for(var n=0; n<a_buttons.length; n++)
+      {
+      button = a_buttons[n];
+      if(button.id==id && button.status=='act')
+        {
+        img = document.getElementById(button.id);
+        if (img && button.act)
+          img.src = button.act;
+        }
+      }
+    };
+
+
+  // set/unset a specific class name
+  this.set_classname = function(obj, classname, set)
+    {
+    var reg = new RegExp('\s*'+classname, 'i');
+    if (!set && obj.className.match(reg))
+      obj.className = obj.className.replace(reg, '');
+    else if (set && !obj.className.match(reg))
+      obj.className += ' '+classname;
+    };
+
+
+  // display a specific alttext
+  this.alttext = function(text)
+    {
+    
+    };
+
+
+  // display a system message
+  this.display_message = function(msg, type, hold)
+    {
+    this.set_busy(false);
+    if (!this.loaded)  // save message in order to display after page loaded
+      {
+      this.pending_message = new Array(msg, type);
+      return true;
+      }
+  
+    if (!this.gui_objects.message)
+      return false;
+
+    if (this.message_timer)
+      clearTimeout(this.message_timer);
+    
+    var cont = msg;
+    if (type)
+      cont = '<div class="'+type+'">'+cont+'</div>';
+
+    this.gui_objects.message._rcube = this;
+    this.gui_objects.message.innerHTML = cont;
+    this.gui_objects.message.style.display = 'block';
+    if (type!='loading')
+      this.gui_objects.message.onmousedown = function(){ this._rcube.hide_message(); return true; };
+    
+    if (!hold)
+      this.message_timer = setTimeout(this.ref+'.hide_message()', this.message_time);
+    };
+
+
+  // make a message row disapear
+  this.hide_message = function()
+    {
+    if (this.gui_objects.message)
+      {
+      this.gui_objects.message.style.display = 'none';
+      this.gui_objects.message.onmousedown = null;
+      }
+    };
+
+
+  // mark a mailbox as selected and set environment variable
+  this.select_mailbox = function(mbox)
+    {
+    if (this.gui_objects.mailboxlist )
+      {
+      var item, reg, text_obj;      
+      var current_li = this.get_mailbox_li();
+      var mbox_li = this.get_mailbox_li(mbox);
+      
+      if (current_li)
+        {
+        this.set_classname(current_li, 'selected', false);
+        this.set_classname(current_li, 'unfocused', false);
+        }
+
+      if (mbox_li || this.env.mailbox == mbox)
+        {
+        this.set_classname(mbox_li, 'unfocused', false);
+        this.set_classname(mbox_li, 'selected', true);
+        }
+      }
+      
+    // also update mailbox name in window title
+    if (document.title)
+      {
+      var doc_title = String(document.title);
+      var reg = new RegExp(this.env.mailbox.toLowerCase(), 'i');
+      if (this.env.mailbox && doc_title.match(reg))
+        document.title = doc_title.replace(reg, mbox).replace(/^\([0-9]+\)\s+/i, '');
+      }
+    
+    this.env.mailbox = mbox;
+    };
+
+
+  // for reordering column array, Konqueror workaround
+  this.set_message_coltypes = function(coltypes) 
+  { 
+    this.coltypes = coltypes;
+    
+    // set correct list titles
+    var cell, col;
+    var thead = this.gui_objects.messagelist ? this.gui_objects.messagelist.tHead : null;
+    for (var n=0; thead && n<this.coltypes.length; n++) 
+      {
+      col = this.coltypes[n];
+      if ((cell = thead.rows[0].cells[n+1]) && (col=='from' || col=='to'))
+        {
+        // if we have links for sorting, it's a bit more complicated...
+        if (cell.firstChild && cell.firstChild.tagName=='A')
+          {
+          cell.firstChild.innerHTML = this.get_label(this.coltypes[n]);
+          cell.firstChild.onclick = function(){ return rcmail.command('sort', this.__col, this); };
+          cell.firstChild.__col = col;
+          }
+        else
+          cell.innerHTML = this.get_label(this.coltypes[n]);
+
+        cell.id = 'rcmHead'+col;
+        }
+      }
+
+  };
+
+  // create a table row in the message list
+  this.add_message_row = function(uid, cols, flags, attachment, attop)
+    {
+    if (!this.gui_objects.messagelist || !this.gui_objects.messagelist.tBodies[0])
+      return false;
+    
+    var tbody = this.gui_objects.messagelist.tBodies[0];
+    var rowcount = tbody.rows.length;
+    var even = rowcount%2;
+    
+    this.env.messages[uid] = {deleted:flags.deleted?1:0,
+                              replied:flags.replied?1:0,
+                              unread:flags.unread?1:0};
+    
+    var row = document.createElement('TR');
+    row.id = 'rcmrow'+uid;
+    row.className = 'message '+(even ? 'even' : 'odd')+(flags.unread ? ' unread' : '')+(flags.deleted ? ' deleted' : '');
+    
+    if (this.in_selection(uid))
+      row.className += ' selected';
+
+    var icon = flags.deleted && this.env.deletedicon ? this.env.deletedicon:
+               (flags.unread && this.env.unreadicon ? this.env.unreadicon :
+               (flags.replied && this.env.repliedicon ? this.env.repliedicon : this.env.messageicon));
+
+    var col = document.createElement('TD');
+    col.className = 'icon';
+    col.innerHTML = icon ? '<img src="'+icon+'" alt="" border="0" />' : '';
+    row.appendChild(col);
+
+    // add each submitted col
+    for (var n = 0; n < this.coltypes.length; n++) 
+      { 
+      var c = this.coltypes[n];
+      col = document.createElement('TD');
+      col.className = String(c).toLowerCase();
+      col.innerHTML = cols[c];
+      row.appendChild(col);
+      }
+
+    col = document.createElement('TD');
+    col.className = 'icon';
+    col.innerHTML = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" border="0" />' : '';
+    row.appendChild(col);
+    
+    if (attop && tbody.rows.length)
+      tbody.insertBefore(row, tbody.firstChild);
+    else
+      tbody.appendChild(row);
+      
+    this.init_message_row(row);
+    };
+
+
+  // replace content of row count display
+  this.set_rowcount = function(text)
+    {
+    if (this.gui_objects.countdisplay)
+      this.gui_objects.countdisplay.innerHTML = text;
+
+    // update page navigation buttons
+    this.set_page_buttons();
+    };
+
+  // replace content of quota display
+   this.set_quota = function(text)
+     {
+     if (this.gui_objects.quotadisplay)
+       this.gui_objects.quotadisplay.innerHTML = text;
+     };
+                            
+
+  // update the mailboxlist
+  this.set_unread_count = function(mbox, count, set_title)
+    {
+    if (!this.gui_objects.mailboxlist)
+      return false;
+
+    if (mbox==this.env.mailbox)
+      set_title = true;
+
+    var reg, text_obj;
+    var item = this.get_mailbox_li(mbox);
+    mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
+
+    if (item && item.className && item.className.indexOf('mailbox '+mbox)>=0)
+      {
+      // set new text
+      text_obj = item.firstChild;
+      reg = /\s+\([0-9]+\)$/i;
+
+      if (count && text_obj.innerHTML.match(reg))
+        text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+count+')');
+      else if (count)
+        text_obj.innerHTML += ' ('+count+')';
+      else
+        text_obj.innerHTML = text_obj.innerHTML.replace(reg, '');
+
+      // set the right classes
+      this.set_classname(item, 'unread', count>0 ? true : false);
+      }
+
+    // set unread count to window title
+    reg = /^\([0-9]+\)\s+/i;
+    if (set_title && document.title)
+      {
+      var doc_title = String(document.title);
+
+      if (count && doc_title.match(reg))
+        document.title = doc_title.replace(reg, '('+count+') ');
+      else if (count)
+        document.title = '('+count+') '+doc_title;
+      else
+        document.title = doc_title.replace(reg, '');
+      }
+    };
+
+
+  // add row to contacts list
+  this.add_contact_row = function(cid, cols)
+    {
+    if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
+      return false;
+    
+    var tbody = this.gui_objects.contactslist.tBodies[0];
+    var rowcount = tbody.rows.length;
+    var even = rowcount%2;
+    
+    var row = document.createElement('TR');
+    row.id = 'rcmrow'+cid;
+    row.className = 'contact '+(even ? 'even' : 'odd');
+    
+    if (this.in_selection(cid))
+      row.className += ' selected';
+
+    // add each submitted col
+    for (var c in cols)
+      {
+      col = document.createElement('TD');
+      col.className = String(c).toLowerCase();
+      col.innerHTML = cols[c];
+      row.appendChild(col);
+      }
+    
+    tbody.appendChild(row);
+    this.init_table_row(row, 'contact_rows');
+    };
+
+
+
+  /********************************************************/
+  /*********          drag & drop methods         *********/
+  /********************************************************/
+
+
+  this.drag_mouse_move = function(e)
+    {
+    if (this.drag_start)
+      {
+      if (!this.draglayer)
+        this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
+      
+      // get subjects of selectedd messages
+      var names = '';
+      var c, subject, obj;
+      for(var n=0; n<this.selection.length; n++)
+        {
+        if (n>12)  // only show 12 lines
+          {
+          names += '...';
+          break;
+          }
+
+        if (this.message_rows[this.selection[n]].obj)
+          {
+          obj = this.message_rows[this.selection[n]].obj;
+          subject = '';
+
+          for(c=0; c<obj.childNodes.length; c++)
+            if (!subject && obj.childNodes[c].nodeName=='TD' && obj.childNodes[c].firstChild && obj.childNodes[c].firstChild.nodeType==3)
+              {
+              subject = obj.childNodes[c].firstChild.data;
+              names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
+              }
+          }
+        }
+        
+      this.draglayer.write(names);
+      this.draglayer.show(1);
+      }
+
+    var pos = this.get_mouse_pos(e);
+    this.draglayer.move(pos.x+20, pos.y-5);
+    
+    this.drag_start = false;
+    this.drag_active = true;
+    
+    return false;
+    };
+
+
+  this.drag_mouse_up = function()
+    {
+    document.onmousemove = null;
+    
+    if (this.draglayer && this.draglayer.visible)
+      this.draglayer.show(0);
+      
+    this.drag_active = false;
+    
+    return false;
+    };
+
+
+
+  /********************************************************/
+  /*********        remote request methods        *********/
+  /********************************************************/
+
+
+  this.http_sockets = new Array();
+  
+  // find a non-busy socket or create a new one
+  this.get_request_obj = function()
+    {
+    for (var n=0; n<this.http_sockets.length; n++)
+      {
+      if (!this.http_sockets[n].busy)
+        return this.http_sockets[n];
+      }
+    
+    // create a new XMLHTTP object
+    var i = this.http_sockets.length;
+    this.http_sockets[i] = new rcube_http_request();
+
+    return this.http_sockets[i];
+    };
+  
+
+  // send a http request to the server
+  this.http_request = function(action, querystring, lock)
+    {
+    var request_obj = this.get_request_obj();
+    querystring += '&_remote=1';
+    
+    // add timestamp to request url to avoid cacheing problems in Safari
+    if (bw.safari)
+      querystring += '&_ts='+(new Date().getTime());
+
+    // send request
+    if (request_obj)
+      {
+      // prompt('request', this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
+      console('HTTP request: '+this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
+
+      if (lock)
+        this.set_busy(true);
+
+      request_obj.__lock = lock ? true : false;
+      request_obj.__action = action;
+      request_obj.onerror = function(o){ rcube_webmail_client.http_error(o); };
+      request_obj.oncomplete = function(o){ rcube_webmail_client.http_response(o); };
+      request_obj.GET(this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
+      }
+    };
+
+
+  // handle HTTP response
+  this.http_response = function(request_obj)
+    {
+    var ctype = request_obj.get_header('Content-Type');
+    if (ctype){
+      ctype = String(ctype).toLowerCase();
+      var ctype_array=ctype.split(";");
+      ctype = ctype_array[0];
+    }
+
+    this.set_busy(false);
+
+  console(request_obj.get_text());
+
+    // if we get javascript code from server -> execute it
+    if (request_obj.get_text() && (ctype=='text/javascript' || ctype=='application/x-javascript'))
+      eval(request_obj.get_text());
+
+    // process the response data according to the sent action
+    switch (request_obj.__action)
+      {
+      case 'delete':
+      case 'moveto':
+        if (this.env.action=='show')
+          this.command('list');
+        break;
+
+      case 'list':
+        if (this.env.messagecount)
+          this.enable_command('purge', (this.env.mailbox==this.env.trash_mailbox));
+
+      case 'expunge':
+        this.enable_command('select-all', 'select-none', 'expunge', this.env.messagecount ? true : false);
+        break;      
+      }
+
+    request_obj.reset();
+    };
+
+
+  // handle HTTP request errors
+  this.http_error = function(request_obj)
+    {
+    //alert('Error sending request: '+request_obj.url);
+
+    if (request_obj.__lock)
+      this.set_busy(false);
+
+    request_obj.reset();
+    request_obj.__lock = false;
+    };
+
+
+  // use an image to send a keep-alive siganl to the server
+  this.send_keep_alive = function()
+    {
+    var d = new Date();
+    this.http_request('keep-alive', '_t='+d.getTime());
+    };
+
+    
+  // send periodic request to check for recent messages
+  this.check_for_recent = function()
+    {
+    if (this.busy)
+      {
+      this.send_keep_alive();
+      return;
+      }
+
+    this.set_busy(true, 'checkingmail');
+    var d = new Date();
+    this.http_request('check-recent', '_t='+d.getTime());
+    };
+
+
+  /********************************************************/
+  /*********            helper methods            *********/
+  /********************************************************/
+  
+  // check if we're in show mode or if we have a unique selection
+  // and return the message uid
+  this.get_single_uid = function()
+    {
+    return this.env.uid ? this.env.uid : (this.selection.length==1 ? this.selection[0] : null);
+    };
+
+  // same as above but for contacts
+  this.get_single_cid = function()
+    {
+    return this.env.cid ? this.env.cid : (this.selection.length==1 ? this.selection[0] : null);
+    };
+
+
+/* deprecated methods
+
+  // check if Shift-key is pressed on event
+  this.check_shiftkey = function(e)
+    {
+    if(!e && window.event)
+      e = window.event;
+
+    if(bw.linux && bw.ns4 && e.modifiers)
+      return true;
+    else if((bw.ns4 && e.modifiers & Event.SHIFT_MASK) || (e && e.shiftKey))
+      return true;
+    else
+      return false;
+    }
+
+  // check if Shift-key is pressed on event
+  this.check_ctrlkey = function(e)
+    {
+    if(!e && window.event)
+      e = window.event;
+
+    if(bw.linux && bw.ns4 && e.modifiers)
+      return true;
+   else if (bw.mac)
+       return this.check_shiftkey(e);
+    else if((bw.ns4 && e.modifiers & Event.CTRL_MASK) || (e && e.ctrlKey))
+      return true;
+    else
+      return false;
+    }
+*/
+
+  // returns modifier key (constants defined at top of file)
+  this.get_modifier = function(e)
+    {
+    var opcode = 0;
+    e = e || window.event;
+
+    if (bw.mac && e)
+      {
+      opcode += (e.metaKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
+      return opcode;    
+      }
+    if (e)
+      {
+      opcode += (e.ctrlKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
+      return opcode;
+      }
+    if (e.cancelBubble)
+      {
+      e.cancelBubble = true;
+      e.returnValue = false;
+      }
+    else if (e.preventDefault)
+      e.preventDefault();
+  }
+
+
+  this.get_mouse_pos = function(e)
+    {
+    if(!e) e = window.event;
+    var mX = (e.pageX) ? e.pageX : e.clientX;
+    var mY = (e.pageY) ? e.pageY : e.clientY;
+
+    if(document.body && document.all)
+      {
+      mX += document.body.scrollLeft;
+      mY += document.body.scrollTop;
+      }
+
+    return { x:mX, y:mY };
+    };
+    
+  
+  this.get_caret_pos = function(obj)
+    {
+    if (typeof(obj.selectionEnd)!='undefined')
+      return obj.selectionEnd;
+
+    else if (document.selection && document.selection.createRange)
+      {
+      var range = document.selection.createRange();
+      if (range.parentElement()!=obj)
+        return 0;
+
+      var gm = range.duplicate();
+      if (obj.tagName=='TEXTAREA')
+        gm.moveToElementText(obj);
+      else
+        gm.expand('textedit');
+      
+      gm.setEndPoint('EndToStart', range);
+      var p = gm.text.length;
+
+      return p<=obj.value.length ? p : -1;
+      }
+
+    else
+      return obj.value.length;
+    };
+
+
+  this.set_caret2start = function(obj)
+    {
+    if (obj.createTextRange)
+      {
+      var range = obj.createTextRange();
+      range.collapse(true);
+      range.select();
+      }
+    else if (obj.setSelectionRange)
+      obj.setSelectionRange(0,0);
+
+    obj.focus();
+    };
+
+
+  // set all fields of a form disabled
+  this.lock_form = function(form, lock)
+    {
+    if (!form || !form.elements)
+      return;
+    
+    var type;
+    for (var n=0; n<form.elements.length; n++)
+      {
+      type = form.elements[n];
+      if (type=='hidden')
+        continue;
+        
+      form.elements[n].disabled = lock;
+      }
+    };
+    
+  }  // end object rcube_webmail
+
+
+
+// class for HTTP requests
+function rcube_http_request()
+  {
+  this.url = '';
+  this.busy = false;
+  this.xmlhttp = null;
+
+
+  // reset object properties
+  this.reset = function()
+    {
+    // set unassigned event handlers
+    this.onloading = function(){ };
+    this.onloaded = function(){ };
+    this.oninteractive = function(){ };
+    this.oncomplete = function(){ };
+    this.onabort = function(){ };
+    this.onerror = function(){ };
+    
+    this.url = '';
+    this.busy = false;
+    this.xmlhttp = null;
+    }
+
+
+  // create HTMLHTTP object
+  this.build = function()
+    {
+    if (window.XMLHttpRequest)
+      this.xmlhttp = new XMLHttpRequest();
+    else if (window.ActiveXObject)
+      this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+    else
+      {
+      
+      }
+    }
+
+  // sedn GET request
+  this.GET = function(url)
+    {
+    this.build();
+
+    if (!this.xmlhttp)
+      {
+      this.onerror(this);
+      return false;
+      }
+
+    var ref = this;
+    this.url = url;
+    this.busy = true;
+
+    this.xmlhttp.onreadystatechange = function(){ ref.xmlhttp_onreadystatechange(); };
+    this.xmlhttp.open('GET', url);
+    this.xmlhttp.send(null);
+    };
+
+
+  this.POST = function(url, a_param)
+    {
+    // not implemented yet
+    };
+
+
+  // handle onreadystatechange event
+  this.xmlhttp_onreadystatechange = function()
+    {
+    if(this.xmlhttp.readyState == 1)
+      this.onloading(this);
+
+    else if(this.xmlhttp.readyState == 2)
+      this.onloaded(this);
+
+    else if(this.xmlhttp.readyState == 3)
+      this.oninteractive(this);
+
+    else if(this.xmlhttp.readyState == 4)
+      {
+      try {
+        if (this.xmlhttp.status == 0)
+          this.onabort(this);
+        else if(this.xmlhttp.status == 200)
+          this.oncomplete(this);
+        else
+          this.onerror(this);
+
+        this.busy = false;
+        }
+      catch(err)
+        {
+        this.onerror(this);
+        this.busy = false;
+        }
+      }
+    }
+
+  // getter method for HTTP headers
+  this.get_header = function(name)
+    {
+    return this.xmlhttp.getResponseHeader(name);
+    };
+
+  this.get_text = function()
+    {
+    return this.xmlhttp.responseText;
+    };
+
+  this.get_xml = function()
+    {
+    return this.xmlhttp.responseXML;
+    };
+
+  this.reset();
+  
+  }  // end class rcube_http_request
+
+
+
+function console(str)
+  {
+  if (document.debugform && document.debugform.console)
+    document.debugform.console.value += str+'\n--------------------------------------\n';
+  }
+
+
+// set onload handler
+window.onload = function(e)
+  {
+  if (window.rcube_webmail_client)
+    rcube_webmail_client.init();
+  };
diff --git a/program/js/common.js b/program/js/common.js
new file mode 100644 (file)
index 0000000..2058635
--- /dev/null
@@ -0,0 +1,441 @@
+/*
+ +-----------------------------------------------------------------------+
+ | RoundCube common js library                                           |
+ |                                                                       |
+ | This file is part of the RoundCube web development suite              |
+ | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+ $Id: common.js 289 2006-08-02 03:42:08Z cmcnulty $
+*/
+
+
+// default browsercheck
+function roundcube_browser()
+  {
+  this.ver = parseFloat(navigator.appVersion);
+  this.appver = navigator.appVersion;
+  this.agent = navigator.userAgent;
+  this.name = navigator.appName;
+  this.vendor = navigator.vendor ? navigator.vendor : '';
+  this.vendver = navigator.vendorSub ? parseFloat(navigator.vendorSub) : 0;
+  this.product = navigator.product ? navigator.product : '';
+  this.platform = String(navigator.platform).toLowerCase();
+  this.lang = (navigator.language) ? navigator.language.substring(0,2) :
+              (navigator.browserLanguage) ? navigator.browserLanguage.substring(0,2) :
+              (navigator.systemLanguage) ? navigator.systemLanguage.substring(0,2) : 'en';
+
+  this.win = (this.platform.indexOf('win')>=0) ? true : false;
+  this.mac = (this.platform.indexOf('mac')>=0) ? true : false;
+  this.linux = (this.platform.indexOf('linux')>=0) ? true : false;
+  this.unix = (this.platform.indexOf('unix')>=0) ? true : false;
+
+  this.dom = document.getElementById ? true : false;
+  this.dom2 = (document.addEventListener && document.removeEventListener);
+
+  this.ie = (document.all) ? true : false;
+  this.ie4 = (this.ie && !this.dom);
+  this.ie5 = (this.dom && this.appver.indexOf('MSIE 5')>0);
+  this.ie6 = (this.dom && this.appver.indexOf('MSIE 6')>0);
+
+  this.mz = (this.dom && this.ver>=5);  // (this.dom && this.product=='Gecko')
+  this.ns = ((this.ver<5 && this.name=='Netscape') || (this.ver>=5 && this.vendor.indexOf('Netscape')>=0));
+  this.ns4 = (this.ns && parseInt(this.ver)==4);
+  this.ns6 = (this.ns && parseInt(this.vendver)==6);  // (this.mz && this.ns) ? true : false;
+  this.ns7 = (this.ns && parseInt(this.vendver)==7);  // this.agent.indexOf('Netscape/7')>0);
+  this.safari = (this.agent.toLowerCase().indexOf('safari')>0 || this.agent.toLowerCase().indexOf('applewebkit')>0);
+  this.konq   = (this.agent.toLowerCase().indexOf('konqueror')>0);
+
+  this.opera = (window.opera) ? true : false;
+  this.opera5 = (this.opera5 && this.agent.indexOf('Opera 5')>0) ? true : false;
+  this.opera6 = (this.opera && this.agent.indexOf('Opera 6')>0) ? true : false;
+  this.opera7 = (this.opera && this.agent.indexOf('Opera 7')>0) ? true : false;
+
+  if(this.opera && window.RegExp)
+    this.vendver = (/opera(\s|\/)([0-9\.]+)/i.test(navigator.userAgent)) ? parseFloat(RegExp.$2) : -1;
+  else if(!this.vendver && this.safari)
+    this.vendver = (/(safari|applewebkit)\/([0-9]+)/i.test(this.agent)) ? parseInt(RegExp.$2) : 0;
+  else if((!this.vendver && this.mz) || this.agent.indexOf('Camino')>0)
+    this.vendver = (/rv:([0-9\.]+)/.test(this.agent)) ? parseFloat(RegExp.$1) : 0;
+  else if(this.ie && window.RegExp)
+    this.vendver = (/msie\s+([0-9\.]+)/i.test(this.agent)) ? parseFloat(RegExp.$1) : 0;
+  else if(this.konq && window.RegExp)
+    this.vendver = (/khtml\/([0-9\.]+)/i.test(this.agent)) ? parseFloat(RegExp.$1) : 0;
+
+
+  // get real language out of safari's user agent
+  if(this.safari && (/;\s+([a-z]{2})-[a-z]{2}\)/i.test(this.agent)))
+    this.lang = RegExp.$1;
+
+  this.dhtml = ((this.ie4 && this.win) || this.ie5 || this.ie6 || this.ns4 || this.mz);
+  this.layers = this.ns4;  // (document.layers);
+  this.div = (this.ie4 || this.dom);
+  this.vml = (this.win && this.ie && this.dom && !this.opera);
+  this.linkborder = (this.ie || this.mz);
+  this.rollover = (this.ver>=4 || (this.ns && this.ver>=3));  // (document.images) ? true : false;
+  this.pngalpha = (this.mz || (this.opera && this.vendver>=6) || (this.ie && this.mac && this.vendver>=5) ||
+                   (this.ie && this.win && this.vendver>=5.5) || this.safari);
+  this.opacity = (this.mz || (this.ie && this.vendver>=5.5 && !this.opera) || (this.safari && this.vendver>=100));
+  this.cookies = navigator.cookieEnabled;
+  
+  // test for XMLHTTP support
+  this.xmlhttp_test = function()
+    {
+    var activeX_test = new Function("try{var o=new ActiveXObject('Microsoft.XMLHTTP');return true;}catch(err){return false;}");
+    this.xmlhttp = (window.XMLHttpRequest || (window.ActiveXObject && activeX_test())) ? true : false;
+    return this.xmlhttp;
+    }
+  }
+
+
+
+
+var rcube_layer_objects = new Array();
+
+function rcube_layer(id, attributes)
+  {
+  this.name = id;
+  
+  // create a new layer in the current document
+  this.create = function(arg)
+    {
+    var l = (arg.x) ? arg.x : 0;
+    var t = (arg.y) ? arg.y : 0;
+    var w = arg.width;
+    var h = arg.height;
+    var z = arg.zindex;
+    var vis = arg.vis;
+    var parent = arg.parent;
+    var obj;
+
+    obj = document.createElement('DIV');
+    with(obj)
+      {
+      id = this.name;
+      with(style)
+        {
+        position = 'absolute';
+        visibility = (vis) ? (vis==2) ? 'inherit' : 'visible' : 'hidden';
+        left = l+'px';
+        top = t+'px';
+        if(w) width = w+'px';
+        if(h) height = h+'px';
+        if(z) zIndex = z;
+        }
+      }
+      
+    if(parent) parent.appendChild(obj);
+    else document.body.appendChild(obj);
+
+    this.elm = obj;
+    };
+
+
+  // create new layer
+  if(attributes!=null)
+    {
+    this.create(attributes);
+    this.name = this.elm.id;
+    }
+  else  // just refer to the object
+    this.elm = document.getElementById(id);
+
+
+  if(!this.elm)
+    return false;
+
+
+  // ********* layer object properties *********
+
+  this.css = this.elm.style;
+  this.event = this.elm;
+  this.width = this.elm.offsetWidth;
+  this.height = this.elm.offsetHeight;
+  this.x = parseInt(this.elm.offsetLeft);
+  this.y = parseInt(this.elm.offsetTop);
+  this.visible = (this.css.visibility=='visible' || this.css.visibility=='show' || this.css.visibility=='inherit') ? true : false;
+
+  this.id = rcube_layer_objects.length;
+  this.obj = 'rcube_layer_objects['+this.id+']';
+  rcube_layer_objects[this.id] = this;
+
+
+  // ********* layer object methods *********
+
+
+  // move the layer to a specific position
+  this.move = function(x, y)
+    {
+    this.x = x;
+    this.y = y;
+    this.css.left = Math.round(this.x)+'px';
+    this.css.top = Math.round(this.y)+'px';
+    }
+
+
+  // move the layer for a specific step
+  this.shift = function(x,y)
+    {
+    x = Math.round(x*100)/100;
+    y = Math.round(y*100)/100;
+    this.move(this.x+x, this.y+y);
+    }
+
+
+  // change the layers width and height
+  this.resize = function(w,h)
+    {
+    this.css.width  = w+'px';
+    this.css.height = h+'px';
+    this.width = w;
+    this.height = h;
+    }
+
+
+  // cut the layer (top,width,height,left)
+  this.clip = function(t,w,h,l)
+    {
+    this.css.clip='rect('+t+' '+w+' '+h+' '+l+')';
+    this.clip_height = h;
+    this.clip_width = w;
+    }
+
+
+  // show or hide the layer
+  this.show = function(a)
+    {
+    if(a==1)
+      {
+      this.css.visibility = 'visible';
+      this.visible = true;
+      }
+    else if(a==2)
+      {
+      this.css.visibility = 'inherit';
+      this.visible = true;
+      }
+    else
+      {
+      this.css.visibility = 'hidden';
+      this.visible = false;
+      }
+    }
+
+
+  // write new content into a Layer
+  this.write = function(cont)
+    {
+    this.elm.innerHTML = cont;
+    }
+
+
+  // set the given color to the layer background
+  this.set_bgcolor = function(c)
+    {
+    if(!c || c=='#')
+      c = 'transparent';
+
+    this.css.backgroundColor = c;
+    }
+
+
+  // set the opacity of a layer to the given ammount (in %)
+  this.set_opacity = function(v)
+    {
+    if(!bw.opacity)
+      return;
+
+    var op = v<=1 ? Math.round(v*100) : parseInt(v);
+
+    if(bw.ie)
+      this.css.filter = 'alpha(opacity:'+op+')';
+    else if(bw.safari)
+      {
+      this.css.opacity = op/100;
+      this.css.KhtmlOpacity = op/100;
+      }
+    else if(bw.mz)
+      this.css.MozOpacity = op/100;
+    }
+  }
+
+// check if input is a valid email address
+// By Cal Henderson <cal@iamcal.com>
+// http://code.iamcal.com/php/rfc822/
+function rcube_check_email(input, inline)
+  {
+  if (input && window.RegExp)
+    {
+    var no_ws_ctl    = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]";
+    var alpha        = "[\\x41-\\x5a\\x61-\\x7a]";
+    var digit        = "[\\x30-\\x39]";
+    var cr        = "\\x0d";
+    var lf        = "\\x0a";
+    var crlf        = "(" + cr + lf + ")";
+
+    var obs_char    = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]";
+    var obs_text    = "("+lf+"*"+cr+"*("+obs_char+lf+"*"+cr+"*)*)";
+    var text        = "([\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|"+obs_text+")";
+    var obs_qp        = "(\\x5c[\\x00-\\x7f])";
+    var quoted_pair    = "(\\x5c"+text+"|"+obs_qp+")";
+
+    var wsp        = "[\\x20\\x09]";
+    var obs_fws    = "("+wsp+"+("+crlf+wsp+"+)*)";
+    var fws        = "((("+wsp+"*"+crlf+")?"+wsp+"+)|"+obs_fws+")";
+    var ctext        = "("+no_ws_ctl+"|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])";
+    var ccontent    = "("+ctext+"|"+quoted_pair+")";
+    var comment    = "(\\x28("+fws+"?"+ccontent+")*"+fws+"?\\x29)";
+    var cfws        = "(("+fws+"?"+comment+")*("+fws+"?"+comment+"|"+fws+"))";
+    var cfws        = fws+"*";
+
+    var atext        = "("+alpha+"|"+digit+"|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2e\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])";
+    var atom        = "("+cfws+"?"+atext+"+"+cfws+"?)";
+
+    var qtext        = "("+no_ws_ctl+"|[\\x21\\x23-\\x5b\\x5d-\\x7e])";
+    var qcontent    = "("+qtext+"|"+quoted_pair+")";
+    var quoted_string    = "("+cfws+"?\\x22("+fws+"?"+qcontent+")*"+fws+"?\\x22"+cfws+"?)";
+    var word        = "("+atom+"|"+quoted_string+")";
+
+    var obs_local_part    = "("+word+"(\\x2e"+word+")*)";
+    var obs_domain    = "("+atom+"(\\x2e"+atom+")*)";
+
+    var dot_atom_text    = "("+atext+"+(\\x2e"+atext+"+)*)";
+    var dot_atom    = "("+cfws+"?"+dot_atom_text+cfws+"?)";
+
+    var dtext        = "("+no_ws_ctl+"|[\\x21-\\x5a\\x5e-\\x7e])";
+    var dcontent    = "("+dtext+"|"+quoted_pair+")";
+    var domain_literal    = "("+cfws+"?\\x5b("+fws+"?"+dcontent+")*"+fws+"?\\x5d"+cfws+"?)";
+
+    var local_part    = "("+dot_atom+"|"+quoted_string+"|"+obs_local_part+")";
+    var domain        = "("+dot_atom+"|"+domain_literal+"|"+obs_domain+")";
+    var addr_spec    = "("+local_part+"\\x40"+domain+")";
+
+    var reg1 = inline ? new RegExp(addr_spec, 'i') : new RegExp('^'+addr_spec+'$', 'i');
+    return reg1.test(input) ? true : false;
+    }
+  return false;
+  }
+  
+
+// find a value in a specific array and returns the index
+function find_in_array()
+  {
+  var args = find_in_array.arguments;
+  if(!args.length) return -1;
+
+  var haystack = typeof(args[0])=='object' ? args[0] : args.length>1 && typeof(args[1])=='object' ? args[1] : new Array();
+  var needle = typeof(args[0])!='object' ? args[0] : args.length>1 && typeof(args[1])!='object' ? args[1] : '';
+  var nocase = args.length==3 ? args[2] : false;
+
+  if(!haystack.length) return -1;
+
+  for(var i=0; i<haystack.length; i++)
+    if(nocase && haystack[i].toLowerCase()==needle.toLowerCase())
+      return i;
+    else if(haystack[i]==needle)
+      return i;
+
+  return -1;
+  }
+
+
+// get any type of html objects by id/name
+function rcube_find_object(id, d)
+  {
+  var n, f, obj, e;
+  if(!d) d = document;
+
+  if(d.getElementsByName && (e = d.getElementsByName(id)))
+    obj = e[0];
+  if(!obj && d.getElementById)
+    obj = d.getElementById(id);
+  if(!obj && d.all)
+    obj = d.all[id];
+
+  if(!obj && d.images.length)
+    obj = d.images[id];
+
+  if(!obj && d.forms.length)
+    for(f=0; f<d.forms.length; f++)
+      {
+      if(d.forms[f].name == id)
+        obj = d.forms[f];
+      else if(d.forms[f].elements[id])
+        obj = d.forms[f].elements[id];
+      }
+
+  if(!obj && d.layers)
+    {
+    if(d.layers[id]) obj = d.layers[id];
+    for(n=0; !obj && n<d.layers.length; n++)
+      obj = nex_get_object(id, d.layers[n].document);
+    }
+
+  return obj;
+  }
+
+
+// return the absolute position of an object within the document
+function rcube_get_object_pos(obj)
+  {
+  if(typeof(obj)=='string')
+    obj = nex_get_object(obj);
+
+  if(!obj) return {x:0, y:0};
+
+  var iX = (bw.layers) ? obj.x : obj.offsetLeft;
+  var iY = (bw.layers) ? obj.y : obj.offsetTop;
+
+  if(bw.ie || bw.mz)
+    {
+    var elm = obj.offsetParent;
+    while(elm && elm!=null)
+      {
+      iX += elm.offsetLeft;
+      iY += elm.offsetTop;
+      elm = elm.offsetParent;
+      }
+    }
+
+  if(bw.mac && bw.ie5) iX += document.body.leftMargin;
+  if(bw.mac && bw.ie5) iY += document.body.topMargin;
+
+  return {x:iX, y:iY};
+  }
+  
+
+// cookie functions by GoogieSpell
+function setCookie(name, value, expires, path, domain, secure)
+  {
+  var curCookie = name + "=" + escape(value) +
+      (expires ? "; expires=" + expires.toGMTString() : "") +
+      (path ? "; path=" + path : "") +
+      (domain ? "; domain=" + domain : "") +
+      (secure ? "; secure" : "");
+  document.cookie = curCookie;
+  }
+
+function getCookie(name)
+  {
+  var dc = document.cookie;
+  var prefix = name + "=";
+  var begin = dc.indexOf("; " + prefix);
+  if (begin == -1)
+    {
+    begin = dc.indexOf(prefix);
+    if (begin != 0) return null;
+    }
+  else
+    begin += 2;  
+  var end = document.cookie.indexOf(";", begin);
+  if (end == -1)
+    end = dc.length;
+  return unescape(dc.substring(begin + prefix.length, end));
+  }
+
+
+var bw = new roundcube_browser();
diff --git a/program/js/googiespell.js b/program/js/googiespell.js
new file mode 100755 (executable)
index 0000000..f318e19
--- /dev/null
@@ -0,0 +1,1308 @@
+/*
+Last Modified: 28/04/06 16:28:09
+
+  AmiJs library
+    A very small library with DOM and Ajax functions.
+    For a much larger script look on http://www.mochikit.com/
+  AUTHOR
+    4mir Salihefendic (http://amix.dk) - amix@amix.dk
+  LICENSE
+    Copyright (c) 2006 Amir Salihefendic. All rights reserved.
+    Copyright (c) 2005 Bob Ippolito. All rights reserved.
+    http://www.opensource.org/licenses/mit-license.php
+  VERSION
+    2.1
+  SITE
+    http://amix.dk/amijs
+**/
+
+var AJS = {
+////
+// Accessor functions
+////
+  /**
+   * @returns The element with the id
+   */
+  getElement: function(id) {
+    if(typeof(id) == "string") 
+      return document.getElementById(id);
+    else
+      return id;
+  },
+
+  /**
+   * @returns The elements with the ids
+   */
+  getElements: function(/*id1, id2, id3*/) {
+    var elements = new Array();
+      for (var i = 0; i < arguments.length; i++) {
+        var element = this.getElement(arguments[i]);
+        elements.push(element);
+      }
+      return elements;
+  },
+
+  /**
+   * @returns The GET query argument
+   */
+  getQueryArgument: function(var_name) {
+    var query = window.location.search.substring(1);
+    var vars = query.split("&");
+    for (var i=0;i<vars.length;i++) {
+      var pair = vars[i].split("=");
+      if (pair[0] == var_name) {
+        return pair[1];
+      }
+    }
+    return null;
+  },
+
+  /**
+   * @returns If the browser is Internet Explorer
+   */
+  isIe: function() {
+    return (navigator.userAgent.toLowerCase().indexOf("msie") != -1 && navigator.userAgent.toLowerCase().indexOf("opera") == -1);
+  },
+
+  /**
+   * @returns The document body   
+   */
+  getBody: function() {
+    return this.getElementsByTagAndClassName('body')[0] 
+  },
+
+  /**
+   * @returns All the elements that have a specific tag name or class name
+   */
+  getElementsByTagAndClassName: function(tag_name, class_name, /*optional*/ parent) {
+    var class_elements = new Array();
+    if(!this.isDefined(parent))
+      parent = document;
+    if(!this.isDefined(tag_name))
+      tag_name = '*';
+
+    var els = parent.getElementsByTagName(tag_name);
+    var els_len = els.length;
+    var pattern = new RegExp("(^|\\s)" + class_name + "(\\s|$)");
+
+    for (i = 0, j = 0; i < els_len; i++) {
+      if ( pattern.test(els[i].className) || class_name == null ) {
+        class_elements[j] = els[i];
+        j++;
+      }
+    }
+    return class_elements;
+  },
+
+
+////
+// DOM manipulation
+////
+  /**
+   * Appends some nodes to a node
+   */
+  appendChildNodes: function(node/*, nodes...*/) {
+    if(arguments.length >= 2) {
+      for(var i=1; i < arguments.length; i++) {
+        var n = arguments[i];
+        if(typeof(n) == "string")
+          n = document.createTextNode(n);
+        if(this.isDefined(n))
+          node.appendChild(n);
+      }
+    }
+    return node;
+  },
+
+  /**
+   * Replaces a nodes children with another node(s)
+   */
+  replaceChildNodes: function(node/*, nodes...*/) {
+    var child;
+    while ((child = node.firstChild)) {
+      node.removeChild(child);
+    }
+    if (arguments.length < 2) {
+      return node;
+    } else {
+      return this.appendChildNodes.apply(this, arguments);
+    }
+  },
+
+  /**
+   * Insert a node after another node
+   */
+  insertAfter: function(node, referenceNode) {
+    referenceNode.parentNode.insertBefore(node, referenceNode.nextSibling);
+  },
+  
+  /**
+   * Insert a node before another node
+   */
+  insertBefore: function(node, referenceNode) {
+    referenceNode.parentNode.insertBefore(node, referenceNode);
+  },
+  
+  /**
+   * Shows the element
+   */
+  showElement: function(elm) {
+    elm.style.display = '';
+  },
+  
+  /**
+   * Hides the element
+   */
+  hideElement: function(elm) {
+    elm.style.display = 'none';
+  },
+
+  isElementHidden: function(elm) {
+    return elm.style.visibility == "hidden";
+  },
+  
+  /**
+   * Swaps one element with another. To delete use swapDOM(elm, null)
+   */
+  swapDOM: function(dest, src) {
+    dest = this.getElement(dest);
+    var parent = dest.parentNode;
+    if (src) {
+      src = this.getElement(src);
+      parent.replaceChild(src, dest);
+    } else {
+      parent.removeChild(dest);
+    }
+    return src;
+  },
+
+  /**
+   * Removes an element from the world
+   */
+  removeElement: function(elm) {
+    this.swapDOM(elm, null);
+  },
+
+  /**
+   * @returns Is an object a dictionary?
+   */
+  isDict: function(o) {
+    var str_repr = String(o);
+    return str_repr.indexOf(" Object") != -1;
+  },
+  
+  /**
+   * Creates a DOM element
+   * @param {String} name The elements DOM name
+   * @param {Dict} attrs Attributes sent to the function
+   */
+  createDOM: function(name, attrs) {
+    var i=0;
+    elm = document.createElement(name);
+
+    if(this.isDict(attrs[i])) {
+      for(k in attrs[0]) {
+        if(k == "style")
+          elm.style.cssText = attrs[0][k];
+        else if(k == "class")
+          elm.className = attrs[0][k];
+        else
+          elm.setAttribute(k, attrs[0][k]);
+      }
+      i++;
+    }
+
+    if(attrs[0] == null)
+      i = 1;
+
+    for(i; i < attrs.length; i++) {
+      var n = attrs[i];
+      if(this.isDefined(n)) {
+        if(typeof(n) == "string")
+          n = document.createTextNode(n);
+        elm.appendChild(n);
+      }
+    }
+    return elm;
+  },
+
+  UL: function() { return this.createDOM.apply(this, ["ul", arguments]); },
+  LI: function() { return this.createDOM.apply(this, ["li", arguments]); },
+  TD: function() { return this.createDOM.apply(this, ["td", arguments]); },
+  TR: function() { return this.createDOM.apply(this, ["tr", arguments]); },
+  TH: function() { return this.createDOM.apply(this, ["th", arguments]); },
+  TBODY: function() { return this.createDOM.apply(this, ["tbody", arguments]); },
+  TABLE: function() { return this.createDOM.apply(this, ["table", arguments]); },
+  INPUT: function() { return this.createDOM.apply(this, ["input", arguments]); },
+  SPAN: function() { return this.createDOM.apply(this, ["span", arguments]); },
+  B: function() { return this.createDOM.apply(this, ["b", arguments]); },
+  A: function() { return this.createDOM.apply(this, ["a", arguments]); },
+  DIV: function() { return this.createDOM.apply(this, ["div", arguments]); },
+  IMG: function() { return this.createDOM.apply(this, ["img", arguments]); },
+  BUTTON: function() { return this.createDOM.apply(this, ["button", arguments]); },
+  H1: function() { return this.createDOM.apply(this, ["h1", arguments]); },
+  H2: function() { return this.createDOM.apply(this, ["h2", arguments]); },
+  H3: function() { return this.createDOM.apply(this, ["h3", arguments]); },
+  BR: function() { return this.createDOM.apply(this, ["br", arguments]); },
+  TEXTAREA: function() { return this.createDOM.apply(this, ["textarea", arguments]); },
+  FORM: function() { return this.createDOM.apply(this, ["form", arguments]); },
+  P: function() { return this.createDOM.apply(this, ["p", arguments]); },
+  SELECT: function() { return this.createDOM.apply(this, ["select", arguments]); },
+  OPTION: function() { return this.createDOM.apply(this, ["option", arguments]); },
+  TN: function(text) { return document.createTextNode(text); },
+  IFRAME: function() { return this.createDOM.apply(this, ["iframe", arguments]); },
+  SCRIPT: function() { return this.createDOM.apply(this, ["script", arguments]); },
+
+////
+// Ajax functions
+////
+  /**
+   * @returns A new XMLHttpRequest object 
+   */
+  getXMLHttpRequest: function() {
+    var try_these = [
+      function () { return new XMLHttpRequest(); },
+      function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
+      function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
+      function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
+      function () { throw "Browser does not support XMLHttpRequest"; }
+    ];
+    for (var i = 0; i < try_these.length; i++) {
+      var func = try_these[i];
+      try {
+        return func();
+      } catch (e) {
+      }
+    }
+  },
+  
+  /**
+   * Use this function to do a simple HTTP Request
+   */
+  doSimpleXMLHttpRequest: function(url) {
+    var req = this.getXMLHttpRequest();
+    req.open("GET", url, true);
+    return this.sendXMLHttpRequest(req);
+  },
+
+  getRequest: function(url, data) {
+    var req = this.getXMLHttpRequest();
+    req.open("POST", url, true);
+    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+    return this.sendXMLHttpRequest(req);
+  },
+
+  /**
+   * Send a XMLHttpRequest
+   */
+  sendXMLHttpRequest: function(req, data) {
+    var d = new AJSDeferred(req);
+
+    var onreadystatechange = function () {
+      if (req.readyState == 4) {
+        try {
+          status = req.status;
+        }
+        catch(e) {};
+        if(status == 200 || status == 304 || req.responseText == null) {
+          d.callback(req, data);
+        }
+        else {
+          d.errback();
+        }
+      }
+    }
+    req.onreadystatechange = onreadystatechange;
+    return d;
+  },
+  
+  /**
+   * Represent an object as a string
+   */
+  reprString: function(o) {
+    return ('"' + o.replace(/(["\\])/g, '\\$1') + '"'
+    ).replace(/[\f]/g, "\\f"
+    ).replace(/[\b]/g, "\\b"
+    ).replace(/[\n]/g, "\\n"
+    ).replace(/[\t]/g, "\\t"
+    ).replace(/[\r]/g, "\\r");
+  },
+  
+  /**
+   * Serialize an object to JSON notation
+   */
+  serializeJSON: function(o) {
+    var objtype = typeof(o);
+    if (objtype == "undefined") {
+      return "undefined";
+    } else if (objtype == "number" || objtype == "boolean") {
+      return o + "";
+    } else if (o === null) {
+      return "null";
+    }
+    if (objtype == "string") {
+      return this.reprString(o);
+    }
+    var me = arguments.callee;
+    var newObj;
+    if (typeof(o.__json__) == "function") {
+      newObj = o.__json__();
+      if (o !== newObj) {
+        return me(newObj);
+      }
+    }
+    if (typeof(o.json) == "function") {
+      newObj = o.json();
+      if (o !== newObj) {
+        return me(newObj);
+      }
+    }
+    if (objtype != "function" && typeof(o.length) == "number") {
+      var res = [];
+      for (var i = 0; i < o.length; i++) {
+        var val = me(o[i]);
+        if (typeof(val) != "string") {
+          val = "undefined";
+        }
+        res.push(val);
+      }
+      return "[" + res.join(",") + "]";
+    }
+    res = [];
+    for (var k in o) {
+      var useKey;
+      if (typeof(k) == "number") {
+        useKey = '"' + k + '"';
+      } else if (typeof(k) == "string") {
+        useKey = this.reprString(k);
+      } else {
+        // skip non-string or number keys
+        continue;
+      }
+      val = me(o[k]);
+      if (typeof(val) != "string") {
+        // skip non-serializable values
+        continue;
+      }
+      res.push(useKey + ":" + val);
+    }
+    return "{" + res.join(",") + "}";
+  },
+
+  /**
+   * Send and recive JSON using GET
+   */
+  loadJSONDoc: function(url) {
+    var d = this.getRequest(url);
+    var eval_req = function(req) {
+      var text = req.responseText;
+      return eval('(' + text + ')');
+    };
+    d.addCallback(eval_req);
+    return d;
+  },
+  
+  
+////
+// Misc.
+////
+  /**
+   * Alert the objects key attrs 
+   */
+  keys: function(obj) {
+    var rval = [];
+    for (var prop in obj) {
+      rval.push(prop);
+    }
+    return rval;
+  },
+
+  urlencode: function(str) {
+    return encodeURIComponent(str.toString());
+  },
+
+  /**
+   * @returns True if the object is defined, otherwise false
+   */
+  isDefined: function(o) {
+    return (o != "undefined" && o != null)
+  },
+  
+  /**
+   * @returns True if an object is a array, false otherwise
+   */
+  isArray: function(obj) {
+    try { return (typeof(obj.length) == "undefined") ? false : true; }
+    catch(e)
+    { return false; }
+  },
+
+  isObject: function(obj) {
+    return (obj && typeof obj == 'object');
+  },
+
+  /**
+   * Export DOM elements to the global namespace
+   */
+  exportDOMElements: function() {
+    UL = this.UL;
+    LI = this.LI;
+    TD = this.TD;
+    TR = this.TR;
+    TH = this.TH;
+    TBODY = this.TBODY;
+    TABLE = this.TABLE;
+    INPUT = this.INPUT;
+    SPAN = this.SPAN;
+    B = this.B;
+    A = this.A;
+    DIV = this.DIV;
+    IMG = this.IMG;
+    BUTTON = this.BUTTON;
+    H1 = this.H1;
+    H2 = this.H2;
+    H3 = this.H3;
+    BR = this.BR;
+    TEXTAREA = this.TEXTAREA;
+    FORM = this.FORM;
+    P = this.P;
+    SELECT = this.SELECT;
+    OPTION = this.OPTION;
+    TN = this.TN;
+    IFRAME = this.IFRAME;
+    SCRIPT = this.SCRIPT;
+  },
+
+  /**
+   * Export AmiJS functions to the global namespace
+   */
+  exportToGlobalScope: function() {
+    getElement = this.getElement;
+    getQueryArgument = this.getQueryArgument;
+    isIe = this.isIe;
+    $ = this.getElement;
+    getElements = this.getElements;
+    getBody = this.getBody;
+    getElementsByTagAndClassName = this.getElementsByTagAndClassName;
+    appendChildNodes = this.appendChildNodes;
+    ACN = appendChildNodes;
+    replaceChildNodes = this.replaceChildNodes;
+    RCN = replaceChildNodes;
+    insertAfter = this.insertAfter;
+    insertBefore = this.insertBefore;
+    showElement = this.showElement;
+    hideElement = this.hideElement;
+    isElementHidden = this.isElementHidden;
+    swapDOM = this.swapDOM;
+    removeElement = this.removeElement;
+    isDict = this.isDict;
+    createDOM = this.createDOM;
+    this.exportDOMElements();
+    getXMLHttpRequest = this.getXMLHttpRequest;
+    doSimpleXMLHttpRequest = this.doSimpleXMLHttpRequest;
+    getRequest = this.getRequest;
+    sendXMLHttpRequest = this.sendXMLHttpRequest;
+    reprString = this.reprString;
+    serializeJSON = this.serializeJSON;
+    loadJSONDoc = this.loadJSONDoc;
+    keys = this.keys;
+    isDefined = this.isDefined;
+    isArray = this.isArray;
+  }
+}
+
+
+
+AJSDeferred = function(req) {
+  this.callbacks = [];
+  this.req = req;
+
+  this.callback = function (res) {
+    while (this.callbacks.length > 0) {
+      var fn = this.callbacks.pop();
+      res = fn(res);
+    }
+  };
+
+  this.errback = function(e){
+    alert("Error encountered:\n" + e);
+  };
+
+  this.addErrback = function(fn) {
+    this.errback = fn;
+  };
+
+  this.addCallback = function(fn) {
+    this.callbacks.unshift(fn);
+  };
+
+  this.addCallbacks = function(fn1, fn2) {
+    this.addCallback(fn1);
+    this.addErrback(fn2);
+  };
+
+  this.sendReq = function(data) {
+    if(AJS.isObject(data)) {
+      var post_data = [];
+      for(k in data) {
+        post_data.push(k + "=" + AJS.urlencode(data[k]));
+      }
+      post_data = post_data.join("&");
+      this.req.send(post_data);
+    }
+    else if(AJS.isDefined(data))
+      this.req.send(data);
+    else {
+      this.req.send("");
+    }
+  };
+};
+AJSDeferred.prototype = new AJSDeferred();
+
+
+
+
+
+
+/****
+Last Modified: 28/04/06 15:26:06
+
+ GoogieSpell
+   Google spell checker for your own web-apps :)
+   Copyright Amir Salihefendic 2006
+ LICENSE
+  GPL (see gpl.txt for more information)
+  This basically means that you can't use this script with/in proprietary software!
+  There is another license that permits you to use this script with proprietary software. Check out:... for more info.
+  AUTHOR
+   4mir Salihefendic (http://amix.dk) - amix@amix.dk
+ VERSION
+        3.22
+****/
+var GOOGIE_CUR_LANG = "en";
+
+function GoogieSpell(img_dir, server_url) {
+  var cookie_value;
+  var lang;
+  cookie_value = getCookie('language');
+
+  if(cookie_value != null)
+    GOOGIE_CUR_LANG = cookie_value;
+
+  this.img_dir = img_dir;
+  this.server_url = server_url;
+
+  this.lang_to_word = {"da": "Dansk", "de": "Deutsch", "en": "English",
+                       "es": "Espa&#241;ol", "fr": "Fran&#231;ais", "it": "Italiano", 
+                       "nl": "Nederlands", "pl": "Polski", "pt": "Portugu&#234;s",
+                       "fi": "Suomi", "sv": "Svenska"};
+  this.langlist_codes = AJS.keys(this.lang_to_word);
+
+  this.show_change_lang_pic = true;
+
+  this.lang_state_observer = null;
+
+  this.spelling_state_observer = null;
+
+  this.request = null;
+  this.error_window = null;
+  this.language_window = null;
+  this.edit_layer = null;
+  this.orginal_text = null;
+  this.results = null;
+  this.text_area = null;
+  this.gselm = null;
+  this.ta_scroll_top = 0;
+  this.el_scroll_top = 0;
+
+  this.lang_chck_spell = "Check spelling";
+  this.lang_rsm_edt = "Resume editing";
+  this.lang_close = "Close";
+  this.lang_no_error_found = "No spelling errors found";
+  this.lang_revert = "Revert to";
+  this.show_spell_img = false;  // modified by roundcube
+}
+
+GoogieSpell.prototype.setStateChanged = function(current_state) {
+  if(this.spelling_state_observer != null)
+    this.spelling_state_observer(current_state);
+}
+
+GoogieSpell.item_onmouseover = function(e) {
+  var elm = GoogieSpell.getEventElm(e);
+  if(elm.className != "googie_list_close" && elm.className != "googie_list_revert")
+    elm.className = "googie_list_onhover";
+  else
+    elm.parentNode.className = "googie_list_onhover";
+}
+
+GoogieSpell.item_onmouseout = function(e) {
+  var elm = GoogieSpell.getEventElm(e);
+  if(elm.className != "googie_list_close" && elm.className != "googie_list_revert")
+    elm.className = "googie_list_onout";
+  else
+    elm.parentNode.className = "googie_list_onout";
+}
+
+GoogieSpell.prototype.getGoogleUrl = function() {
+  return this.server_url + GOOGIE_CUR_LANG;
+}
+
+GoogieSpell.prototype.spellCheck = function(elm, name) {
+  this.ta_scroll_top = this.text_area.scrollTop;
+
+  this.appendIndicator(elm);
+
+  try {
+    this.hideLangWindow();
+  }
+  catch(e) {}
+  
+  this.gselm = elm;
+
+  this.createEditLayer(this.text_area.offsetWidth, this.text_area.offsetHeight);
+
+  this.createErrorWindow();
+  AJS.getBody().appendChild(this.error_window);
+
+  try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); } 
+  catch (e) { }
+
+  this.gselm.onclick = null;
+
+  this.orginal_text = this.text_area.value;
+  var me = this;
+
+  //Create request
+  var d = AJS.getRequest(this.getGoogleUrl());
+  var reqdone = function(req) {
+    var r_text = req.responseText;
+    if(r_text.match(/<c.*>/) != null) {
+      var results = GoogieSpell.parseResult(r_text);
+      //Before parsing be sure that errors were found
+      me.results = results;
+      me.showErrorsInIframe(results);
+      me.resumeEditingState();
+    }
+    else {
+      me.flashNoSpellingErrorState();
+    }
+    me.removeIndicator();
+  };
+
+  var reqfailed = function(req) {
+    alert("An error was encountered on the server. Please try again later.");
+    AJS.removeElement(me.gselm);
+    me.checkSpellingState();
+    me.removeIndicator();
+  };
+  
+  d.addCallback(reqdone);
+  d.addErrback(reqfailed);
+
+  var req_text = GoogieSpell.escapeSepcial(this.orginal_text);
+  d.sendReq(GoogieSpell.createXMLReq(req_text));
+}
+
+GoogieSpell.escapeSepcial = function(val) {
+  return val.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+}
+
+GoogieSpell.createXMLReq = function (text) {
+  return '<?xml version="1.0" encoding="utf-8" ?><spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1"><text>' + text + '</text></spellrequest>';
+}
+
+//Retunrs an array
+//result[item] -> ['attrs']
+//                ['suggestions']
+GoogieSpell.parseResult = function(r_text) {
+  var re_split_attr_c = /\w="\d+"/g;
+  var re_split_text = /\t/g;
+
+  var matched_c = r_text.match(/<c[^>]*>[^<]*<\/c>/g);
+  var results = new Array();
+  
+  for(var i=0; i < matched_c.length; i++) {
+    var item = new Array();
+
+    //Get attributes
+    item['attrs'] = new Array();
+    var split_c = matched_c[i].match(re_split_attr_c);
+    for(var j=0; j < split_c.length; j++) {
+      var c_attr = split_c[j].split(/=/);
+      item['attrs'][c_attr[0]] = parseInt(c_attr[1].replace('"', ''));
+    }
+
+    //Get suggestions
+    item['suggestions'] = new Array();
+    var only_text = matched_c[i].replace(/<[^>]*>/g, "");
+    var split_t = only_text.split(re_split_text);
+    for(var k=0; k < split_t.length; k++) {
+    if(split_t[k] != "")
+      item['suggestions'].push(split_t[k]);
+    }
+    results.push(item);
+  }
+  return results;
+}
+
+/****
+ Error window (the drop-down window)
+****/
+GoogieSpell.prototype.createErrorWindow = function() {
+  this.error_window = AJS.DIV();
+  this.error_window.className = "googie_window";
+}
+
+GoogieSpell.prototype.hideErrorWindow = function() {
+  this.error_window.style.visibility = "hidden";
+}
+
+GoogieSpell.prototype.updateOrginalText = function(offset, old_value, new_value, id) {
+  var part_1 = this.orginal_text.substring(0, offset);
+  var part_2 = this.orginal_text.substring(offset+old_value.length);
+  this.orginal_text = part_1 + new_value + part_2;
+  var add_2_offset = new_value.length - old_value.length;
+  for(var j=0; j < this.results.length; j++) {
+    //Don't edit the offset of the current item
+    if(j != id && j > id){
+      this.results[j]['attrs']['o'] += add_2_offset;
+    }
+  }
+}
+
+GoogieSpell.prototype.saveOldValue = function (id, old_value) {
+  this.results[id]['is_changed'] = true;
+  this.results[id]['old_value'] = old_value;
+}
+
+GoogieSpell.prototype.showErrorWindow = function(elm, id) {
+  var me = this;
+
+  var abs_pos = GoogieSpell.absolutePosition(elm);
+  abs_pos.y -= this.edit_layer.scrollTop;
+  this.error_window.style.visibility = "visible";
+  this.error_window.style.top = (abs_pos.y+20) + "px";
+  this.error_window.style.left = (abs_pos.x) + "px";
+  this.error_window.innerHTML = "";
+
+  //Build up the result list
+  var table = AJS.TABLE({'class': 'googie_list'});
+  var list = AJS.TBODY();
+
+  var suggestions = this.results[id]['suggestions'];
+  var offset = this.results[id]['attrs']['o'];
+  var len = this.results[id]['attrs']['l'];
+
+  if(suggestions.length == 0) {
+    var row = AJS.TR();
+    var item = AJS.TD();
+    var dummy = AJS.SPAN();
+    item.appendChild(AJS.TN("No suggestions :("));
+    row.appendChild(item);
+    list.appendChild(row);
+  }
+
+  for(i=0; i < suggestions.length; i++) {
+    var row = AJS.TR();
+    var item = AJS.TD();
+    var dummy = AJS.SPAN();
+    dummy.innerHTML = suggestions[i];
+    item.appendChild(AJS.TN(dummy.innerHTML));
+    
+    item.onclick = function(e) {
+      var l_elm = GoogieSpell.getEventElm(e);
+      var old_value = elm.innerHTML;
+      var new_value = l_elm.innerHTML;
+
+      elm.style.color = "green";
+      elm.innerHTML = l_elm.innerHTML;
+      me.hideErrorWindow();
+
+      me.updateOrginalText(offset, old_value, new_value, id);
+
+      //Update to the new length
+      me.results[id]['attrs']['l'] = new_value.length;
+      me.saveOldValue(id, old_value);
+    };
+    item.onmouseover = GoogieSpell.item_onmouseover;
+    item.onmouseout = GoogieSpell.item_onmouseout;
+    row.appendChild(item);
+    list.appendChild(row);
+  }
+  
+  //The element is changed, append the revert
+  if(this.results[id]['is_changed']) {
+    var old_value = this.results[id]['old_value'];
+    var offset = this.results[id]['attrs']['o'];
+    var revert_row = AJS.TR();
+    var revert = AJS.TD();
+
+    revert.onmouseover = GoogieSpell.item_onmouseover;
+    revert.onmouseout = GoogieSpell.item_onmouseout;
+    var rev_span = AJS.SPAN({'class': 'googie_list_revert'});
+    rev_span.innerHTML = this.lang_revert + " " + old_value;
+    revert.appendChild(rev_span);
+
+    revert.onclick = function(e) { 
+      me.updateOrginalText(offset, elm.innerHTML, old_value, id);
+      elm.style.color = "#b91414";
+      elm.innerHTML = old_value;
+      me.hideErrorWindow();
+    };
+
+    revert_row.appendChild(revert);
+    list.appendChild(revert_row);
+  }
+
+  //Append the edit box
+  var edit_row = AJS.TR();
+  var edit = AJS.TD();
+
+  var edit_input = AJS.INPUT({'style': 'width: 120px; margin:0; padding:0'});
+
+  var onsub = function () {
+    if(edit_input.value != "") {
+      me.saveOldValue(id, elm.innerHTML);
+      me.updateOrginalText(offset, elm.innerHTML, edit_input.value, id);
+      elm.style.color = "green"
+      elm.innerHTML = edit_input.value;
+      
+      me.hideErrorWindow();
+      return false;
+    }
+  };
+  
+  var ok_pic = AJS.IMG({'src': this.img_dir + "ok.gif", 'style': 'width: 32px; height: 16px; margin-left: 2px; margin-right: 2px;'});
+  var edit_form = AJS.FORM({'style': 'margin: 0; padding: 0'}, edit_input, ok_pic);
+  ok_pic.onclick = onsub;
+  edit_form.onsubmit = onsub;
+  
+  edit.appendChild(edit_form);
+  edit_row.appendChild(edit);
+  list.appendChild(edit_row);
+
+  //Close button
+  var close_row = AJS.TR();
+  var close = AJS.TD();
+
+  close.onmouseover = GoogieSpell.item_onmouseover;
+  close.onmouseout = GoogieSpell.item_onmouseout;
+
+  var spn_close = AJS.SPAN({'class': 'googie_list_close'});
+  spn_close.innerHTML = this.lang_close;
+  close.appendChild(spn_close);
+  close.onclick = function() { me.hideErrorWindow()};
+  close_row.appendChild(close);
+  list.appendChild(close_row);
+
+  table.appendChild(list);
+  this.error_window.appendChild(table);
+}
+
+
+/****
+  Edit layer (the layer where the suggestions are stored)
+****/
+GoogieSpell.prototype.createEditLayer = function(width, height) {
+  this.edit_layer = AJS.DIV({'class': 'googie_edit_layer'});
+  
+  //Set the style so it looks like edit areas
+  this.edit_layer.className = this.text_area.className;
+  this.edit_layer.style.border = "1px solid #999";
+  this.edit_layer.style.overflow = "auto";
+  this.edit_layer.style.backgroundColor = "#F1EDFE";
+  this.edit_layer.style.padding = "3px";
+
+  this.edit_layer.style.width = (width-8) + "px";
+  this.edit_layer.style.height = height + "px";
+}
+
+GoogieSpell.prototype.resumeEditing = function(e, me) {
+  this.setStateChanged("check_spelling");
+  me.switch_lan_pic.style.display = "inline";
+
+  this.el_scroll_top = me.edit_layer.scrollTop;
+
+  var elm = GoogieSpell.getEventElm(e);
+  AJS.replaceChildNodes(elm, this.createSpellDiv());
+
+  elm.onclick = function(e) {
+    me.spellCheck(elm, me.text_area.id);
+  };
+  me.hideErrorWindow();
+
+  //Remove the EDIT_LAYER
+  me.edit_layer.parentNode.removeChild(me.edit_layer);
+
+  me.text_area.value = me.orginal_text;
+  AJS.showElement(me.text_area);
+  me.gselm.className = "googie_no_style";
+
+  me.text_area.scrollTop = this.el_scroll_top;
+
+  elm.onmouseout = null;
+}
+
+GoogieSpell.prototype.createErrorLink = function(text, id) {
+  var elm = AJS.SPAN({'class': 'googie_link'});
+  var me = this;
+  elm.onclick = function () {
+    me.showErrorWindow(elm, id);
+  };
+  elm.innerHTML = text;
+  return elm;
+}
+
+GoogieSpell.createPart = function(txt_part) {
+  if(txt_part == " ")
+    return AJS.TN(" ");
+  var result = AJS.SPAN();
+
+  var is_first = true;
+  var is_safari = (navigator.userAgent.toLowerCase().indexOf("safari") != -1);
+
+  var part = AJS.SPAN();
+  txt_part = GoogieSpell.escapeSepcial(txt_part);
+  txt_part = txt_part.replace(/\n/g, "<br>");
+  txt_part = txt_part.replace(/  /g, " &nbsp;");
+  txt_part = txt_part.replace(/^ /g, "&nbsp;");
+  txt_part = txt_part.replace(/ $/g, "&nbsp;");
+  
+  part.innerHTML = txt_part;
+
+  return part;
+}
+
+GoogieSpell.prototype.showErrorsInIframe = function(results) {
+  var output = AJS.DIV();
+  output.style.textAlign = "left";
+  var pointer = 0;
+  for(var i=0; i < results.length; i++) {
+    var offset = results[i]['attrs']['o'];
+    var len = results[i]['attrs']['l'];
+    
+    var part_1_text = this.orginal_text.substring(pointer, offset);
+    var part_1 = GoogieSpell.createPart(part_1_text);
+    output.appendChild(part_1);
+    pointer += offset - pointer;
+    
+    //If the last child was an error, then insert some space
+    output.appendChild(this.createErrorLink(this.orginal_text.substr(offset, len), i));
+    pointer += len;
+  }
+  //Insert the rest of the orginal text
+  var part_2_text = this.orginal_text.substr(pointer, this.orginal_text.length);
+
+  var part_2 = GoogieSpell.createPart(part_2_text);
+  output.appendChild(part_2);
+
+  this.edit_layer.appendChild(output);
+
+  //Hide text area
+  AJS.hideElement(this.text_area);
+  this.text_area.parentNode.insertBefore(this.edit_layer, this.text_area.nextSibling);
+  this.edit_layer.scrollTop = this.ta_scroll_top;
+}
+
+GoogieSpell.Position = function(x, y) {
+  this.x = x;
+  this.y = y;
+}      
+
+//Get the absolute position of menu_slide
+GoogieSpell.absolutePosition = function(element) {
+  //Create a new object that has elements y and x pos...
+  var posObj = new GoogieSpell.Position(element.offsetLeft, element.offsetTop);
+
+  //Check if the element has an offsetParent - if it has .. loop until it has not
+  if(element.offsetParent) {
+    var temp_pos =     GoogieSpell.absolutePosition(element.offsetParent);
+    posObj.x += temp_pos.x;
+    posObj.y += temp_pos.y;
+  }
+  return posObj;
+}
+
+GoogieSpell.getEventElm = function(e) {
+       var targ;
+       if (!e) var e = window.event;
+       if (e.target) targ = e.target;
+       else if (e.srcElement) targ = e.srcElement;
+       if (targ.nodeType == 3) // defeat Safari bug
+               targ = targ.parentNode;
+  return targ;
+}
+
+GoogieSpell.prototype.removeIndicator = function(elm) {
+  // modified by roundcube
+  if (window.rcube_webmail_client)
+    rcube_webmail_client.set_busy(false);
+  //AJS.removeElement(this.indicator);
+}
+
+GoogieSpell.prototype.appendIndicator = function(elm) {
+  // modified by roundcube
+  if (window.rcube_webmail_client)
+    rcube_webmail_client.set_busy(true, 'checking');
+/*
+  var img = AJS.IMG({'src': this.img_dir + 'indicator.gif', 'style': 'margin-right: 5px;'});
+  img.style.width = "16px";
+  img.style.height = "16px";
+  this.indicator = img;
+  img.style.textDecoration = "none";
+  AJS.insertBefore(img, elm);
+  */
+}
+
+/****
+ Choose language
+****/
+GoogieSpell.prototype.createLangWindow = function() {
+  this.language_window = AJS.DIV({'class': 'googie_window'});
+  this.language_window.style.width = "130px";
+
+  //Build up the result list
+  var table = AJS.TABLE({'class': 'googie_list'});
+  var list = AJS.TBODY();
+
+  this.lang_elms = new Array();
+
+  for(i=0; i < this.langlist_codes.length; i++) {
+    var row = AJS.TR();
+    var item = AJS.TD();
+    item.googieId = this.langlist_codes[i];
+    this.lang_elms.push(item);
+    var lang_span = AJS.SPAN();
+    lang_span.innerHTML = this.lang_to_word[this.langlist_codes[i]];
+    item.appendChild(AJS.TN(lang_span.innerHTML));
+
+    var me = this;
+    
+    item.onclick = function(e) {
+      var elm = GoogieSpell.getEventElm(e);
+      me.deHighlightCurSel();
+
+      me.setCurrentLanguage(elm.googieId);
+
+      if(me.lang_state_observer != null) {
+        me.lang_state_observer();
+      }
+
+      me.highlightCurSel();
+      me.hideLangWindow();
+    };
+
+    item.onmouseover = function(e) { 
+      var i_it = GoogieSpell.getEventElm(e);
+      if(i_it.className != "googie_list_selected")
+        i_it.className = "googie_list_onhover";
+    };
+    item.onmouseout = function(e) { 
+      var i_it = GoogieSpell.getEventElm(e);
+      if(i_it.className != "googie_list_selected")
+        i_it.className = "googie_list_onout"; 
+    };
+
+    row.appendChild(item);
+    list.appendChild(row);
+  }
+
+  this.highlightCurSel();
+
+  //Close button
+  var close_row = AJS.TR();
+  var close = AJS.TD();
+  close.onmouseover = GoogieSpell.item_onmouseover;
+  close.onmouseout = GoogieSpell.item_onmouseout;
+  var spn_close = AJS.SPAN({'class': 'googie_list_close'});
+  spn_close.innerHTML = this.lang_close;
+  close.appendChild(spn_close);
+  var me = this;
+  close.onclick = function(e) {
+    me.hideLangWindow(); GoogieSpell.item_onmouseout(e);
+  };
+  close_row.appendChild(close);
+  list.appendChild(close_row);
+
+  table.appendChild(list);
+  this.language_window.appendChild(table);
+}
+
+GoogieSpell.prototype.setCurrentLanguage = function(lan_code) {
+  GOOGIE_CUR_LANG = lan_code;
+
+  //Set cookie
+  var now = new Date();
+  now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
+  setCookie('language', lan_code, now);
+}
+
+GoogieSpell.prototype.hideLangWindow = function() {
+  this.language_window.style.visibility = "hidden";
+  this.switch_lan_pic.className = "googie_lang_3d_on";
+}
+
+GoogieSpell.prototype.deHighlightCurSel = function() {
+  this.lang_cur_elm.className = "googie_list_onout";
+}
+
+GoogieSpell.prototype.highlightCurSel = function() {
+  for(var i=0; i < this.lang_elms.length; i++) {
+    if(this.lang_elms[i].googieId == GOOGIE_CUR_LANG) {
+      this.lang_elms[i].className = "googie_list_selected";
+      this.lang_cur_elm = this.lang_elms[i];
+    }
+    else {
+      this.lang_elms[i].className = "googie_list_onout";
+    }
+  }
+}
+
+GoogieSpell.prototype.showLangWindow = function(elm, ofst_top, ofst_left) {
+  if(!AJS.isDefined(ofst_top))
+    ofst_top = 20;
+  if(!AJS.isDefined(ofst_left))
+    ofst_left = 50;
+
+  this.createLangWindow();
+  AJS.getBody().appendChild(this.language_window);
+
+  var abs_pos = GoogieSpell.absolutePosition(elm);
+  AJS.showElement(this.language_window);
+  this.language_window.style.top = (abs_pos.y+ofst_top) + "px";
+  this.language_window.style.left = (abs_pos.x+ofst_left-this.language_window.offsetWidth) + "px";
+  this.highlightCurSel();
+  this.language_window.style.visibility = "visible";
+}
+
+GoogieSpell.prototype.flashNoSpellingErrorState = function() {
+  this.setStateChanged("no_error_found");
+  var me = this;
+  AJS.hideElement(this.switch_lan_pic);
+  this.gselm.innerHTML = this.lang_no_error_found;
+  this.gselm.className = "googie_check_spelling_ok";
+  this.gselm.style.textDecoration = "none";
+  this.gselm.style.cursor = "default";
+  var fu = function() {
+    AJS.removeElement(me.gselm);
+    me.checkSpellingState();
+  };
+  setTimeout(fu, 1000);
+}
+
+GoogieSpell.prototype.resumeEditingState = function() {
+  this.setStateChanged("resume_editing");
+  var me = this;
+  AJS.hideElement(me.switch_lan_pic);
+
+  //Change link text to resume
+  me.gselm.innerHTML = this.lang_rsm_edt;
+  me.gselm.onclick = function(e) {
+    me.resumeEditing(e, me);
+  }
+  me.gselm.className = "googie_check_spelling_ok";
+  me.edit_layer.scrollTop = me.ta_scroll_top;
+}
+
+GoogieSpell.prototype.createChangeLangPic = function() {
+  var switch_lan = AJS.A({'class': 'googie_lang_3d_on', 'style': 'padding-left: 6px;'}, AJS.IMG({'src': this.img_dir + 'change_lang.gif', 'alt': "Change language"}));
+  switch_lan.onmouseover = function() {
+    if(this.className != "googie_lang_3d_click")
+      this.className = "googie_lang_3d_on";
+  }
+
+  var me = this;
+  switch_lan.onclick = function() {
+    if(this.className == "googie_lang_3d_click") {
+      me.hideLangWindow();
+    }
+    else {
+      me.showLangWindow(switch_lan);
+      this.className = "googie_lang_3d_click";
+    }
+  }
+  return switch_lan;
+}
+
+GoogieSpell.prototype.createSpellDiv = function() {
+  var chk_spell = AJS.SPAN({'class': 'googie_check_spelling_link'});
+  chk_spell.innerHTML = this.lang_chck_spell;
+  var spell_img = null;
+  if(this.show_spell_img)
+    spell_img = AJS.IMG({'src': this.img_dir + "spellc.gif"});
+  return AJS.SPAN(spell_img, " ", chk_spell);
+}
+
+GoogieSpell.prototype.checkSpellingState = function() {
+  this.setStateChanged("check_spelling");
+  var me = this;
+  if(this.show_change_lang_pic)
+    this.switch_lan_pic = this.createChangeLangPic();
+  else
+    this.switch_lan_pic = AJS.SPAN();
+
+  var span_chck = this.createSpellDiv();
+  span_chck.onclick = function() {
+    me.spellCheck(span_chck);
+  }
+  AJS.appendChildNodes(this.spell_container, span_chck, " ", this.switch_lan_pic);
+  // modified by roundcube
+  this.check_link = span_chck;
+}
+
+GoogieSpell.prototype.setLanguages = function(lang_dict) {
+  this.lang_to_word = lang_dict;
+  this.langlist_codes = AJS.keys(lang_dict);
+}
+
+GoogieSpell.prototype.decorateTextarea = function(id, /*optional*/spell_container_id, force_width) {
+  var me = this;
+  
+  if(typeof(id) == "string")
+    this.text_area = AJS.getElement(id);
+  else
+    this.text_area = id;
+
+  var r_width;
+
+  if(this.text_area != null) {
+    if(AJS.isDefined(spell_container_id)) {
+      if(typeof(spell_container_id) == "string")
+        this.spell_container = AJS.getElement(spell_container_id);
+      else
+        this.spell_container = spell_container_id;
+    }
+    else {
+      var table = AJS.TABLE();
+      var tbody = AJS.TBODY();
+      var tr = AJS.TR();
+      if(AJS.isDefined(force_width)) {
+        r_width = force_width;
+      }
+      else {
+        r_width = this.text_area.offsetWidth + "px";
+      }
+
+      var spell_container = AJS.TD();
+      this.spell_container = spell_container;
+
+      tr.appendChild(spell_container);
+
+      tbody.appendChild(tr);
+      table.appendChild(tbody);
+
+      AJS.insertBefore(table, this.text_area);
+
+      //Set width
+      table.style.width = '100%';  // modified by roundcube (old: r_width)
+      spell_container.style.width = r_width;
+      spell_container.style.textAlign = "right";
+    }
+
+    this.checkSpellingState();
+  }
+  else {
+    alert("Text area not found");
+  }
+}
diff --git a/program/lib/Auth/SASL.php b/program/lib/Auth/SASL.php
new file mode 100755 (executable)
index 0000000..8cbd8d8
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+// +-----------------------------------------------------------------------+ 
+// | Copyright (c) 2002-2003 Richard Heyes                                 | 
+// | All rights reserved.                                                  | 
+// |                                                                       | 
+// | Redistribution and use in source and binary forms, with or without    | 
+// | modification, are permitted provided that the following conditions    | 
+// | are met:                                                              | 
+// |                                                                       | 
+// | o Redistributions of source code must retain the above copyright      | 
+// |   notice, this list of conditions and the following disclaimer.       | 
+// | o Redistributions in binary form must reproduce the above copyright   | 
+// |   notice, this list of conditions and the following disclaimer in the | 
+// |   documentation and/or other materials provided with the distribution.| 
+// | o The names of the authors may not be used to endorse or promote      | 
+// |   products derived from this software without specific prior written  | 
+// |   permission.                                                         | 
+// |                                                                       | 
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
+// |                                                                       | 
+// +-----------------------------------------------------------------------+ 
+// | Author: Richard Heyes <richard@php.net>                               | 
+// +-----------------------------------------------------------------------+ 
+// 
+// $Id: SASL.php 17 2005-10-03 20:25:31Z roundcube $
+
+/**
+* Client implementation of various SASL mechanisms 
+*
+* @author  Richard Heyes <richard@php.net>
+* @access  public
+* @version 1.0
+* @package Auth_SASL
+*/
+
+require_once('PEAR.php');
+
+class Auth_SASL
+{
+    /**
+    * Factory class. Returns an object of the request
+    * type.
+    *
+    * @param string $type One of: Anonymous
+    *                             Plain
+    *                             CramMD5
+    *                             DigestMD5
+    *                     Types are not case sensitive
+    */
+    function &factory($type)
+    {
+        switch (strtolower($type)) {
+            case 'anonymous':
+                $filename  = 'Auth/SASL/Anonymous.php';
+                $classname = 'Auth_SASL_Anonymous';
+                break;
+
+            case 'login':
+                $filename  = 'Auth/SASL/Login.php';
+                $classname = 'Auth_SASL_Login';
+                break;
+
+            case 'plain':
+                $filename  = 'Auth/SASL/Plain.php';
+                $classname = 'Auth_SASL_Plain';
+                break;
+
+            case 'crammd5':
+                $filename  = 'Auth/SASL/CramMD5.php';
+                $classname = 'Auth_SASL_CramMD5';
+                break;
+
+            case 'digestmd5':
+                $filename  = 'Auth/SASL/DigestMD5.php';
+                $classname = 'Auth_SASL_DigestMD5';
+                break;
+
+            default:
+                return PEAR::raiseError('Invalid SASL mechanism type');
+                break;
+        }
+
+        require_once($filename);
+        return new $classname();
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/program/lib/Auth/SASL/Anonymous.php b/program/lib/Auth/SASL/Anonymous.php
new file mode 100755 (executable)
index 0000000..ac82dbb
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+// +-----------------------------------------------------------------------+ 
+// | Copyright (c) 2002-2003 Richard Heyes                                 | 
+// | All rights reserved.                                                  | 
+// |                                                                       | 
+// | Redistribution and use in source and binary forms, with or without    | 
+// | modification, are permitted provided that the following conditions    | 
+// | are met:                                                              | 
+// |                                                                       | 
+// | o Redistributions of source code must retain the above copyright      | 
+// |   notice, this list of conditions and the following disclaimer.       | 
+// | o Redistributions in binary form must reproduce the above copyright   | 
+// |   notice, this list of conditions and the following disclaimer in the | 
+// |   documentation and/or other materials provided with the distribution.| 
+// | o The names of the authors may not be used to endorse or promote      | 
+// |   products derived from this software without specific prior written  | 
+// |   permission.                                                         | 
+// |                                                                       | 
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
+// |                                                                       | 
+// +-----------------------------------------------------------------------+ 
+// | Author: Richard Heyes <richard@php.net>                               | 
+// +-----------------------------------------------------------------------+ 
+// 
+// $Id: Anonymous.php 17 2005-10-03 20:25:31Z roundcube $
+
+/**
+* Implmentation of ANONYMOUS SASL mechanism
+*
+* @author  Richard Heyes <richard@php.net>
+* @access  public
+* @version 1.0
+* @package Auth_SASL
+*/
+
+require_once('Auth/SASL/Common.php');
+
+class Auth_SASL_Anonymous extends Auth_SASL_Common
+{
+    /**
+    * Not much to do here except return the token supplied.
+    * No encoding, hashing or encryption takes place for this
+    * mechanism, simply one of:
+    *  o An email address
+    *  o An opaque string not containing "@" that can be interpreted
+    *    by the sysadmin
+    *  o Nothing
+    *
+    * We could have some logic here for the second option, but this
+    * would by no means create something interpretable.
+    *
+    * @param  string $token Optional email address or string to provide
+    *                       as trace information.
+    * @return string        The unaltered input token
+    */
+    function getResponse($token = '')
+    {
+        return $token;
+    }
+}
+?>
\ No newline at end of file
diff --git a/program/lib/Auth/SASL/Common.php b/program/lib/Auth/SASL/Common.php
new file mode 100755 (executable)
index 0000000..183a442
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// +-----------------------------------------------------------------------+ 
+// | Copyright (c) 2002-2003 Richard Heyes                                 | 
+// | All rights reserved.                                                  | 
+// |                                                                       | 
+// | Redistribution and use in source and binary forms, with or without    | 
+// | modification, are permitted provided that the following conditions    | 
+// | are met:                                                              | 
+// |                                                                       | 
+// | o Redistributions of source code must retain the above copyright      | 
+// |   notice, this list of conditions and the following disclaimer.       | 
+// | o Redistributions in binary form must reproduce the above copyright   | 
+// |   notice, this list of conditions and the following disclaimer in the | 
+// |   documentation and/or other materials provided with the distribution.| 
+// | o The names of the authors may not be used to endorse or promote      | 
+// |   products derived from this software without specific prior written  | 
+// |   permission.                                                         | 
+// |                                                                       | 
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
+// |                                                                       | 
+// +-----------------------------------------------------------------------+ 
+// | Author: Richard Heyes <richard@php.net>                               | 
+// +-----------------------------------------------------------------------+ 
+// 
+// $Id: Common.php 17 2005-10-03 20:25:31Z roundcube $
+
+/**
+* Common functionality to SASL mechanisms
+*
+* @author  Richard Heyes <richard@php.net>
+* @access  public
+* @version 1.0
+* @package Auth_SASL
+*/
+
+class Auth_SASL_Common
+{
+    /**
+    * Function which implements HMAC MD5 digest
+    *
+    * @param  string $key  The secret key
+    * @param  string $data The data to protect
+    * @return string       The HMAC MD5 digest
+    */
+    function _HMAC_MD5($key, $data)
+    {
+        if (strlen($key) > 64) {
+            $key = pack('H32', md5($key));
+        }
+
+        if (strlen($key) < 64) {
+            $key = str_pad($key, 64, chr(0));
+        }
+
+        $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
+        $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
+
+        $inner  = pack('H32', md5($k_ipad . $data));
+        $digest = md5($k_opad . $inner);
+
+        return $digest;
+    }
+}
+?>
diff --git a/program/lib/Auth/SASL/CramMD5.php b/program/lib/Auth/SASL/CramMD5.php
new file mode 100755 (executable)
index 0000000..678aa09
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// +-----------------------------------------------------------------------+ 
+// | Copyright (c) 2002-2003 Richard Heyes                                 | 
+// | All rights reserved.                                                  | 
+// |                                                                       | 
+// | Redistribution and use in source and binary forms, with or without    | 
+// | modification, are permitted provided that the following conditions    | 
+// | are met:                                                              | 
+// |                                                                       | 
+// | o Redistributions of source code must retain the above copyright      | 
+// |   notice, this list of conditions and the following disclaimer.       | 
+// | o Redistributions in binary form must reproduce the above copyright   | 
+// |   notice, this list of conditions and the following disclaimer in the | 
+// |   documentation and/or other materials provided with the distribution.| 
+// | o The names of the authors may not be used to endorse or promote      | 
+// |   products derived from this software without specific prior written  | 
+// |   permission.                                                         | 
+// |                                                                       | 
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
+// |                                                                       | 
+// +-----------------------------------------------------------------------+ 
+// | Author: Richard Heyes <richard@php.net>                               | 
+// +-----------------------------------------------------------------------+ 
+// 
+// $Id: CramMD5.php 17 2005-10-03 20:25:31Z roundcube $
+
+/**
+* Implmentation of CRAM-MD5 SASL mechanism
+*
+* @author  Richard Heyes <richard@php.net>
+* @access  public
+* @version 1.0
+* @package Auth_SASL
+*/
+
+require_once('Auth/SASL/Common.php');
+
+class Auth_SASL_CramMD5 extends Auth_SASL_Common
+{
+    /**
+    * Implements the CRAM-MD5 SASL mechanism
+    * This DOES NOT base64 encode the return value,
+    * you will need to do that yourself.
+    *
+    * @param string $user      Username
+    * @param string $pass      Password
+    * @param string $challenge The challenge supplied by the server.
+    *                          this should be already base64_decoded.
+    *
+    * @return string The string to pass back to the server, of the form
+    *                "<user> <digest>". This is NOT base64_encoded.
+    */
+    function getResponse($user, $pass, $challenge)
+    {
+        return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
+    }
+}
+?>
\ No newline at end of file
diff --git a/program/lib/Auth/SASL/DigestMD5.php b/program/lib/Auth/SASL/DigestMD5.php
new file mode 100755 (executable)
index 0000000..404851f
--- /dev/null
@@ -0,0 +1,194 @@
+<?php
+// +-----------------------------------------------------------------------+ 
+// | Copyright (c) 2002-2003 Richard Heyes                                 | 
+// | All rights reserved.                                                  | 
+// |                                                                       | 
+// | Redistribution and use in source and binary forms, with or without    | 
+// | modification, are permitted provided that the following conditions    | 
+// | are met:                                                              | 
+// |                                                                       | 
+// | o Redistributions of source code must retain the above copyright      | 
+// |   notice, this list of conditions and the following disclaimer.       | 
+// | o Redistributions in binary form must reproduce the above copyright   | 
+// |   notice, this list of conditions and the following disclaimer in the | 
+// |   documentation and/or other materials provided with the distribution.| 
+// | o The names of the authors may not be used to endorse or promote      | 
+// |   products derived from this software without specific prior written  | 
+// |   permission.                                                         | 
+// |                                                                       | 
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
+// |                                                                       | 
+// +-----------------------------------------------------------------------+ 
+// | Author: Richard Heyes <richard@php.net>                               | 
+// +-----------------------------------------------------------------------+ 
+// 
+// $Id: DigestMD5.php 17 2005-10-03 20:25:31Z roundcube $
+
+/**
+* Implmentation of DIGEST-MD5 SASL mechanism
+*
+* @author  Richard Heyes <richard@php.net>
+* @access  public
+* @version 1.0
+* @package Auth_SASL
+*/
+
+require_once('Auth/SASL/Common.php');
+
+class Auth_SASL_DigestMD5 extends Auth_SASL_Common
+{
+    /**
+    * Provides the (main) client response for DIGEST-MD5
+    * requires a few extra parameters than the other
+    * mechanisms, which are unavoidable.
+    * 
+    * @param  string $authcid   Authentication id (username)
+    * @param  string $pass      Password
+    * @param  string $challenge The digest challenge sent by the server
+    * @param  string $hostname  The hostname of the machine you're connecting to
+    * @param  string $service   The servicename (eg. imap, pop, acap etc)
+    * @param  string $authzid   Authorization id (username to proxy as)
+    * @return string            The digest response (NOT base64 encoded)
+    * @access public
+    */
+    function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
+    {
+        $challenge = $this->_parseChallenge($challenge);
+        $authzid_string = '';
+        if ($authzid != '') {
+            $authzid_string = ',authzid="' . $authzid . '"'; 
+        }
+
+        if (!empty($challenge)) {
+            $cnonce         = $this->_getCnonce();
+            $digest_uri     = sprintf('%s/%s', $service, $hostname);
+            $response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);
+
+            return sprintf('username="%s",realm="%s"' . $authzid_string  . ',nonce="%s",cnonce="%s",nc="00000001",qop=auth,digest-uri="%s",response=%s,%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
+        } else {
+            return PEAR::raiseError('Invalid digest challenge');
+        }
+    }
+    
+    /**
+    * Parses and verifies the digest challenge*
+    *
+    * @param  string $challenge The digest challenge
+    * @return array             The parsed challenge as an assoc
+    *                           array in the form "directive => value".
+    * @access private
+    */
+    function _parseChallenge($challenge)
+    {
+        $tokens = array();
+        while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
+
+            // Ignore these as per rfc2831
+            if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
+                $challenge = substr($challenge, strlen($matches[0]) + 1);
+                continue;
+            }
+
+            // Allowed multiple "realm" and "auth-param"
+            if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
+                if (is_array($tokens[$matches[1]])) {
+                    $tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
+                } else {
+                    $tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
+                }
+
+            // Any other multiple instance = failure
+            } elseif (!empty($tokens[$matches[1]])) {
+                $tokens = array();
+                break;
+
+            } else {
+                $tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
+            }
+
+            // Remove the just parsed directive from the challenge
+            $challenge = substr($challenge, strlen($matches[0]) + 1);
+        }
+
+        /**
+        * Defaults and required directives
+        */
+        // Realm
+        if (empty($tokens['realm'])) {
+            $uname = posix_uname();
+            $tokens['realm'] = $uname['nodename'];
+        }
+        
+        // Maxbuf
+        if (empty($tokens['maxbuf'])) {
+            $tokens['maxbuf'] = 65536;
+        }
+        
+        // Required: nonce, algorithm
+        if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
+            return array();
+        }
+        
+        return $tokens;
+    }
+
+    /**
+    * Creates the response= part of the digest response
+    *
+    * @param  string $authcid    Authentication id (username)
+    * @param  string $pass       Password
+    * @param  string $realm      Realm as provided by the server
+    * @param  string $nonce      Nonce as provided by the server
+    * @param  string $cnonce     Client nonce
+    * @param  string $digest_uri The digest-uri= value part of the response
+    * @param  string $authzid    Authorization id
+    * @return string             The response= part of the digest response
+    * @access private
+    */    
+    function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
+    {
+        if ($authzid == '') {
+            $A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
+        } else {
+            $A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
+        }
+        $A2 = 'AUTHENTICATE:' . $digest_uri;
+        return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
+    }
+
+    /**
+    * Creates the client nonce for the response
+    *
+    * @return string  The cnonce value
+    * @access private
+    */
+    function _getCnonce()
+    {
+        if (file_exists('/dev/urandom')) {
+            return base64_encode(fread(fopen('/dev/urandom', 'r'), 32));
+
+        } elseif (file_exists('/dev/random')) {
+            return base64_encode(fread(fopen('/dev/random', 'r'), 32));
+
+        } else {
+            $str = '';
+            mt_srand((double)microtime()*10000000);
+            for ($i=0; $i<32; $i++) {
+                $str .= chr(mt_rand(0, 255));
+            }
+            
+            return base64_encode($str);
+        }
+    }
+}
+?>
diff --git a/program/lib/Auth/SASL/Login.php b/program/lib/Auth/SASL/Login.php
new file mode 100755 (executable)
index 0000000..58338a3
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+// +-----------------------------------------------------------------------+ 
+// | Copyright (c) 2002-2003 Richard Heyes                                 | 
+// | All rights reserved.                                                  | 
+// |                                                                       | 
+// | Redistribution and use in source and binary forms, with or without    | 
+// | modification, are permitted provided that the following conditions    | 
+// | are met:                                                              | 
+// |                                                                       | 
+// | o Redistributions of source code must retain the above copyright      | 
+// |   notice, this list of conditions and the following disclaimer.       | 
+// | o Redistributions in binary form must reproduce the above copyright   | 
+// |   notice, this list of conditions and the following disclaimer in the | 
+// |   documentation and/or other materials provided with the distribution.| 
+// | o The names of the authors may not be used to endorse or promote      | 
+// |   products derived from this software without specific prior written  | 
+// |   permission.                                                         | 
+// |                                                                       | 
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
+// |                                                                       | 
+// +-----------------------------------------------------------------------+ 
+// | Author: Richard Heyes <richard@php.net>                               | 
+// +-----------------------------------------------------------------------+ 
+// 
+// $Id: Login.php 17 2005-10-03 20:25:31Z roundcube $
+
+/**
+* This is technically not a SASL mechanism, however
+* it's used by Net_Sieve, Net_Cyrus and potentially
+* other protocols , so here is a good place to abstract
+* it.
+*
+* @author  Richard Heyes <richard@php.net>
+* @access  public
+* @version 1.0
+* @package Auth_SASL
+*/
+
+require_once('Auth/SASL/Common.php');
+
+class Auth_SASL_Login extends Auth_SASL_Common
+{
+    /**
+    * Pseudo SASL LOGIN mechanism
+    *
+    * @param  string $user Username
+    * @param  string $pass Password
+    * @return string       LOGIN string
+    */
+    function getResponse($user, $pass)
+    {
+        return sprintf('LOGIN %s %s', $user, $pass);
+    }
+}
+?>
\ No newline at end of file
diff --git a/program/lib/Auth/SASL/Plain.php b/program/lib/Auth/SASL/Plain.php
new file mode 100755 (executable)
index 0000000..87f2c16
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+// +-----------------------------------------------------------------------+ 
+// | Copyright (c) 2002-2003 Richard Heyes                                 | 
+// | All rights reserved.                                                  | 
+// |                                                                       | 
+// | Redistribution and use in source and binary forms, with or without    | 
+// | modification, are permitted provided that the following conditions    | 
+// | are met:                                                              | 
+// |                                                                       | 
+// | o Redistributions of source code must retain the above copyright      | 
+// |   notice, this list of conditions and the following disclaimer.       | 
+// | o Redistributions in binary form must reproduce the above copyright   | 
+// |   notice, this list of conditions and the following disclaimer in the | 
+// |   documentation and/or other materials provided with the distribution.| 
+// | o The names of the authors may not be used to endorse or promote      | 
+// |   products derived from this software without specific prior written  | 
+// |   permission.                                                         | 
+// |                                                                       | 
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
+// |                                                                       | 
+// +-----------------------------------------------------------------------+ 
+// | Author: Richard Heyes <richard@php.net>                               | 
+// +-----------------------------------------------------------------------+ 
+// 
+// $Id: Plain.php 17 2005-10-03 20:25:31Z roundcube $
+
+/**
+* Implmentation of PLAIN SASL mechanism
+*
+* @author  Richard Heyes <richard@php.net>
+* @access  public
+* @version 1.0
+* @package Auth_SASL
+*/
+
+require_once('Auth/SASL/Common.php');
+
+class Auth_SASL_Plain extends Auth_SASL_Common
+{
+    /**
+    * Returns PLAIN response
+    *
+    * @param  string $authcid   Authentication id (username)
+    * @param  string $pass      Password
+    * @param  string $authzid   Autorization id
+    * @return string            PLAIN Response
+    */
+    function getResponse($authcid, $pass, $authzid = '')
+    {
+        return $authzid . chr(0) . $authcid . chr(0) . $pass;
+    }
+}
+?>
diff --git a/program/lib/DB.php b/program/lib/DB.php
new file mode 100644 (file)
index 0000000..f276454
--- /dev/null
@@ -0,0 +1,1388 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Database independent query interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: DB.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+
+// {{{ constants
+// {{{ error codes
+
+/**#@+
+ * One of PEAR DB's portable error codes.
+ * @see DB_common::errorCode(), DB::errorMessage()
+ *
+ * {@internal If you add an error code here, make sure you also add a textual
+ * version of it in DB::errorMessage().}}
+ */
+
+/**
+ * The code returned by many methods upon success
+ */
+define('DB_OK', 1);
+
+/**
+ * Unkown error
+ */
+define('DB_ERROR', -1);
+
+/**
+ * Syntax error
+ */
+define('DB_ERROR_SYNTAX', -2);
+
+/**
+ * Tried to insert a duplicate value into a primary or unique index
+ */
+define('DB_ERROR_CONSTRAINT', -3);
+
+/**
+ * An identifier in the query refers to a non-existant object
+ */
+define('DB_ERROR_NOT_FOUND', -4);
+
+/**
+ * Tried to create a duplicate object
+ */
+define('DB_ERROR_ALREADY_EXISTS', -5);
+
+/**
+ * The current driver does not support the action you attempted
+ */
+define('DB_ERROR_UNSUPPORTED', -6);
+
+/**
+ * The number of parameters does not match the number of placeholders
+ */
+define('DB_ERROR_MISMATCH', -7);
+
+/**
+ * A literal submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID', -8);
+
+/**
+ * The current DBMS does not support the action you attempted
+ */
+define('DB_ERROR_NOT_CAPABLE', -9);
+
+/**
+ * A literal submitted was too long so the end of it was removed
+ */
+define('DB_ERROR_TRUNCATED', -10);
+
+/**
+ * A literal number submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_NUMBER', -11);
+
+/**
+ * A literal date submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_DATE', -12);
+
+/**
+ * Attempt to divide something by zero
+ */
+define('DB_ERROR_DIVZERO', -13);
+
+/**
+ * A database needs to be selected
+ */
+define('DB_ERROR_NODBSELECTED', -14);
+
+/**
+ * Could not create the object requested
+ */
+define('DB_ERROR_CANNOT_CREATE', -15);
+
+/**
+ * Could not drop the database requested because it does not exist
+ */
+define('DB_ERROR_CANNOT_DROP', -17);
+
+/**
+ * An identifier in the query refers to a non-existant table
+ */
+define('DB_ERROR_NOSUCHTABLE', -18);
+
+/**
+ * An identifier in the query refers to a non-existant column
+ */
+define('DB_ERROR_NOSUCHFIELD', -19);
+
+/**
+ * The data submitted to the method was inappropriate
+ */
+define('DB_ERROR_NEED_MORE_DATA', -20);
+
+/**
+ * The attempt to lock the table failed
+ */
+define('DB_ERROR_NOT_LOCKED', -21);
+
+/**
+ * The number of columns doesn't match the number of values
+ */
+define('DB_ERROR_VALUE_COUNT_ON_ROW', -22);
+
+/**
+ * The DSN submitted has problems
+ */
+define('DB_ERROR_INVALID_DSN', -23);
+
+/**
+ * Could not connect to the database
+ */
+define('DB_ERROR_CONNECT_FAILED', -24);
+
+/**
+ * The PHP extension needed for this DBMS could not be found
+ */
+define('DB_ERROR_EXTENSION_NOT_FOUND',-25);
+
+/**
+ * The present user has inadequate permissions to perform the task requestd
+ */
+define('DB_ERROR_ACCESS_VIOLATION', -26);
+
+/**
+ * The database requested does not exist
+ */
+define('DB_ERROR_NOSUCHDB', -27);
+
+/**
+ * Tried to insert a null value into a column that doesn't allow nulls
+ */
+define('DB_ERROR_CONSTRAINT_NOT_NULL',-29);
+/**#@-*/
+
+
+// }}}
+// {{{ prepared statement-related
+
+
+/**#@+
+ * Identifiers for the placeholders used in prepared statements.
+ * @see DB_common::prepare()
+ */
+
+/**
+ * Indicates a scalar (<kbd>?</kbd>) placeholder was used
+ *
+ * Quote and escape the value as necessary.
+ */
+define('DB_PARAM_SCALAR', 1);
+
+/**
+ * Indicates an opaque (<kbd>&</kbd>) placeholder was used
+ *
+ * The value presented is a file name.  Extract the contents of that file
+ * and place them in this column.
+ */
+define('DB_PARAM_OPAQUE', 2);
+
+/**
+ * Indicates a misc (<kbd>!</kbd>) placeholder was used
+ *
+ * The value should not be quoted or escaped.
+ */
+define('DB_PARAM_MISC',   3);
+/**#@-*/
+
+
+// }}}
+// {{{ binary data-related
+
+
+/**#@+
+ * The different ways of returning binary data from queries.
+ */
+
+/**
+ * Sends the fetched data straight through to output
+ */
+define('DB_BINMODE_PASSTHRU', 1);
+
+/**
+ * Lets you return data as usual
+ */
+define('DB_BINMODE_RETURN', 2);
+
+/**
+ * Converts the data to hex format before returning it
+ *
+ * For example the string "123" would become "313233".
+ */
+define('DB_BINMODE_CONVERT', 3);
+/**#@-*/
+
+
+// }}}
+// {{{ fetch modes
+
+
+/**#@+
+ * Fetch Modes.
+ * @see DB_common::setFetchMode()
+ */
+
+/**
+ * Indicates the current default fetch mode should be used
+ * @see DB_common::$fetchmode
+ */
+define('DB_FETCHMODE_DEFAULT', 0);
+
+/**
+ * Column data indexed by numbers, ordered from 0 and up
+ */
+define('DB_FETCHMODE_ORDERED', 1);
+
+/**
+ * Column data indexed by column names
+ */
+define('DB_FETCHMODE_ASSOC', 2);
+
+/**
+ * Column data as object properties
+ */
+define('DB_FETCHMODE_OBJECT', 3);
+
+/**
+ * For multi-dimensional results, make the column name the first level
+ * of the array and put the row number in the second level of the array
+ *
+ * This is flipped from the normal behavior, which puts the row numbers
+ * in the first level of the array and the column names in the second level.
+ */
+define('DB_FETCHMODE_FLIPPED', 4);
+/**#@-*/
+
+/**#@+
+ * Old fetch modes.  Left here for compatibility.
+ */
+define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED);
+define('DB_GETMODE_ASSOC',   DB_FETCHMODE_ASSOC);
+define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED);
+/**#@-*/
+
+
+// }}}
+// {{{ tableInfo() && autoPrepare()-related
+
+
+/**#@+
+ * The type of information to return from the tableInfo() method.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::tableInfo()
+ *
+ * {@internal Since the TABLEINFO constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}}
+ */
+define('DB_TABLEINFO_ORDER', 1);
+define('DB_TABLEINFO_ORDERTABLE', 2);
+define('DB_TABLEINFO_FULL', 3);
+/**#@-*/
+
+
+/**#@+
+ * The type of query to create with the automatic query building methods.
+ * @see DB_common::autoPrepare(), DB_common::autoExecute()
+ */
+define('DB_AUTOQUERY_INSERT', 1);
+define('DB_AUTOQUERY_UPDATE', 2);
+/**#@-*/
+
+
+// }}}
+// {{{ portability modes
+
+
+/**#@+
+ * Portability Modes.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::setOption()
+ *
+ * {@internal Since the PORTABILITY constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}}
+ */
+
+/**
+ * Turn off all portability features
+ */
+define('DB_PORTABILITY_NONE', 0);
+
+/**
+ * Convert names of tables and fields to lower case
+ * when using the get*(), fetch*() and tableInfo() methods
+ */
+define('DB_PORTABILITY_LOWERCASE', 1);
+
+/**
+ * Right trim the data output by get*() and fetch*()
+ */
+define('DB_PORTABILITY_RTRIM', 2);
+
+/**
+ * Force reporting the number of rows deleted
+ */
+define('DB_PORTABILITY_DELETE_COUNT', 4);
+
+/**
+ * Enable hack that makes numRows() work in Oracle
+ */
+define('DB_PORTABILITY_NUMROWS', 8);
+
+/**
+ * Makes certain error messages in certain drivers compatible
+ * with those from other DBMS's
+ *
+ * + mysql, mysqli:  change unique/primary key constraints
+ *   DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
+ *
+ * + odbc(access):  MS's ODBC driver reports 'no such field' as code
+ *   07001, which means 'too few parameters.'  When this option is on
+ *   that code gets mapped to DB_ERROR_NOSUCHFIELD.
+ */
+define('DB_PORTABILITY_ERRORS', 16);
+
+/**
+ * Convert null values to empty strings in data output by
+ * get*() and fetch*()
+ */
+define('DB_PORTABILITY_NULL_TO_EMPTY', 32);
+
+/**
+ * Turn on all portability features
+ */
+define('DB_PORTABILITY_ALL', 63);
+/**#@-*/
+
+// }}}
+
+
+// }}}
+// {{{ class DB
+
+/**
+ * Database independent query interface
+ *
+ * The main "DB" class is simply a container class with some static
+ * methods for creating DB objects as well as some utility functions
+ * common to all parts of DB.
+ *
+ * The object model of DB is as follows (indentation means inheritance):
+ * <pre>
+ * DB           The main DB class.  This is simply a utility class
+ *              with some "static" methods for creating DB objects as
+ *              well as common utility functions for other DB classes.
+ *
+ * DB_common    The base for each DB implementation.  Provides default
+ * |            implementations (in OO lingo virtual methods) for
+ * |            the actual DB implementations as well as a bunch of
+ * |            query utility functions.
+ * |
+ * +-DB_mysql   The DB implementation for MySQL.  Inherits DB_common.
+ *              When calling DB::factory or DB::connect for MySQL
+ *              connections, the object returned is an instance of this
+ *              class.
+ * </pre>
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB
+{
+    // {{{ &factory()
+
+    /**
+     * Create a new DB object for the specified database type but don't
+     * connect to the database
+     *
+     * @param string $type     the database type (eg "mysql")
+     * @param array  $options  an associative array of option names and values
+     *
+     * @return object  a new DB object.  A DB_Error object on failure.
+     *
+     * @see DB_common::setOption()
+     */
+    function &factory($type, $options = false)
+    {
+        if (!is_array($options)) {
+            $options = array('persistent' => $options);
+        }
+
+        if (isset($options['debug']) && $options['debug'] >= 2) {
+            // expose php errors with sufficient debug level
+            include_once "DB/{$type}.php";
+        } else {
+            @include_once "DB/{$type}.php";
+        }
+
+        $classname = "DB_${type}";
+
+        if (!class_exists($classname)) {
+            $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+                                    "Unable to include the DB/{$type}.php"
+                                    . " file for '$dsn'",
+                                    'DB_Error', true);
+            return $tmp;
+        }
+
+        @$obj =& new $classname;
+
+        foreach ($options as $option => $value) {
+            $test = $obj->setOption($option, $value);
+            if (DB::isError($test)) {
+                return $test;
+            }
+        }
+
+        return $obj;
+    }
+
+    // }}}
+    // {{{ &connect()
+
+    /**
+     * Create a new DB object including a connection to the specified database
+     *
+     * Example 1.
+     * <code>
+     * require_once 'DB.php';
+     *
+     * $dsn = 'pgsql://user:password@host/database';
+     * $options = array(
+     *     'debug'       => 2,
+     *     'portability' => DB_PORTABILITY_ALL,
+     * );
+     *
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param mixed $dsn      the string "data source name" or array in the
+     *                         format returned by DB::parseDSN()
+     * @param array $options  an associative array of option names and values
+     *
+     * @return object  a new DB object.  A DB_Error object on failure.
+     *
+     * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(),
+     *       DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(),
+     *       DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(),
+     *       DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(),
+     *       DB_sybase::connect()
+     *
+     * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError()
+     */
+    function &connect($dsn, $options = array())
+    {
+        $dsninfo = DB::parseDSN($dsn);
+        $type = $dsninfo['phptype'];
+
+        if (!is_array($options)) {
+            /*
+             * For backwards compatibility.  $options used to be boolean,
+             * indicating whether the connection should be persistent.
+             */
+            $options = array('persistent' => $options);
+        }
+
+        if (isset($options['debug']) && $options['debug'] >= 2) {
+            // expose php errors with sufficient debug level
+            include_once "DB/${type}.php";
+        } else {
+            @include_once "DB/${type}.php";
+        }
+
+        $classname = "DB_${type}";
+        if (!class_exists($classname)) {
+            $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+                                    "Unable to include the DB/{$type}.php"
+                                    . " file for '$dsn'",
+                                    'DB_Error', true);
+            return $tmp;
+        }
+
+        @$obj =& new $classname;
+
+        foreach ($options as $option => $value) {
+            $test = $obj->setOption($option, $value);
+            if (DB::isError($test)) {
+                return $test;
+            }
+        }
+
+        $err = $obj->connect($dsninfo, $obj->getOption('persistent'));
+        if (DB::isError($err)) {
+            $err->addUserInfo($dsn);
+            return $err;
+        }
+
+        return $obj;
+    }
+
+    // }}}
+    // {{{ apiVersion()
+
+    /**
+     * Return the DB API version
+     *
+     * @return string  the DB API version number
+     */
+    function apiVersion()
+    {
+        return '@package_version@';
+    }
+
+    // }}}
+    // {{{ isError()
+
+    /**
+     * Determines if a variable is a DB_Error object
+     *
+     * @param mixed $value  the variable to check
+     *
+     * @return bool  whether $value is DB_Error object
+     */
+    function isError($value)
+    {
+        return is_a($value, 'DB_Error');
+    }
+
+    // }}}
+    // {{{ isConnection()
+
+    /**
+     * Determines if a value is a DB_<driver> object
+     *
+     * @param mixed $value  the value to test
+     *
+     * @return bool  whether $value is a DB_<driver> object
+     */
+    function isConnection($value)
+    {
+        return (is_object($value) &&
+                is_subclass_of($value, 'db_common') &&
+                method_exists($value, 'simpleQuery'));
+    }
+
+    // }}}
+    // {{{ isManip()
+
+    /**
+     * Tell whether a query is a data manipulation or data definition query
+     *
+     * Examples of data manipulation queries are INSERT, UPDATE and DELETE.
+     * Examples of data definition queries are CREATE, DROP, ALTER, GRANT,
+     * REVOKE.
+     *
+     * @param string $query  the query
+     *
+     * @return boolean  whether $query is a data manipulation query
+     */
+    function isManip($query)
+    {
+        $manips = 'INSERT|UPDATE|DELETE|REPLACE|'
+                . 'CREATE|DROP|'
+                . 'LOAD DATA|SELECT .* INTO|COPY|'
+                . 'ALTER|GRANT|REVOKE|'
+                . 'LOCK|UNLOCK';
+        if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) {
+            return true;
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ errorMessage()
+
+    /**
+     * Return a textual error message for a DB error code
+     *
+     * @param integer $value  the DB error code
+     *
+     * @return string  the error message or false if the error code was
+     *                  not recognized
+     */
+    function errorMessage($value)
+    {
+        static $errorMessages;
+        if (!isset($errorMessages)) {
+            $errorMessages = array(
+                DB_ERROR                    => 'unknown error',
+                DB_ERROR_ACCESS_VIOLATION   => 'insufficient permissions',
+                DB_ERROR_ALREADY_EXISTS     => 'already exists',
+                DB_ERROR_CANNOT_CREATE      => 'can not create',
+                DB_ERROR_CANNOT_DROP        => 'can not drop',
+                DB_ERROR_CONNECT_FAILED     => 'connect failed',
+                DB_ERROR_CONSTRAINT         => 'constraint violation',
+                DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+                DB_ERROR_DIVZERO            => 'division by zero',
+                DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
+                DB_ERROR_INVALID            => 'invalid',
+                DB_ERROR_INVALID_DATE       => 'invalid date or time',
+                DB_ERROR_INVALID_DSN        => 'invalid DSN',
+                DB_ERROR_INVALID_NUMBER     => 'invalid number',
+                DB_ERROR_MISMATCH           => 'mismatch',
+                DB_ERROR_NEED_MORE_DATA     => 'insufficient data supplied',
+                DB_ERROR_NODBSELECTED       => 'no database selected',
+                DB_ERROR_NOSUCHDB           => 'no such database',
+                DB_ERROR_NOSUCHFIELD        => 'no such field',
+                DB_ERROR_NOSUCHTABLE        => 'no such table',
+                DB_ERROR_NOT_CAPABLE        => 'DB backend not capable',
+                DB_ERROR_NOT_FOUND          => 'not found',
+                DB_ERROR_NOT_LOCKED         => 'not locked',
+                DB_ERROR_SYNTAX             => 'syntax error',
+                DB_ERROR_UNSUPPORTED        => 'not supported',
+                DB_ERROR_TRUNCATED          => 'truncated',
+                DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
+                DB_OK                       => 'no error',
+            );
+        }
+
+        if (DB::isError($value)) {
+            $value = $value->getCode();
+        }
+
+        return isset($errorMessages[$value]) ? $errorMessages[$value]
+                     : $errorMessages[DB_ERROR];
+    }
+
+    // }}}
+    // {{{ parseDSN()
+
+    /**
+     * Parse a data source name
+     *
+     * Additional keys can be added by appending a URI query string to the
+     * end of the DSN.
+     *
+     * The format of the supplied DSN is in its fullest form:
+     * <code>
+     *  phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
+     * </code>
+     *
+     * Most variations are allowed:
+     * <code>
+     *  phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
+     *  phptype://username:password@hostspec/database_name
+     *  phptype://username:password@hostspec
+     *  phptype://username@hostspec
+     *  phptype://hostspec/database
+     *  phptype://hostspec
+     *  phptype(dbsyntax)
+     *  phptype
+     * </code>
+     *
+     * @param string $dsn Data Source Name to be parsed
+     *
+     * @return array an associative array with the following keys:
+     *  + phptype:  Database backend used in PHP (mysql, odbc etc.)
+     *  + dbsyntax: Database used with regards to SQL syntax etc.
+     *  + protocol: Communication protocol to use (tcp, unix etc.)
+     *  + hostspec: Host specification (hostname[:port])
+     *  + database: Database to use on the DBMS server
+     *  + username: User name for login
+     *  + password: Password for login
+     */
+    function parseDSN($dsn)
+    {
+        $parsed = array(
+            'phptype'  => false,
+            'dbsyntax' => false,
+            'username' => false,
+            'password' => false,
+            'protocol' => false,
+            'hostspec' => false,
+            'port'     => false,
+            'socket'   => false,
+            'database' => false,
+        );
+
+        if (is_array($dsn)) {
+            $dsn = array_merge($parsed, $dsn);
+            if (!$dsn['dbsyntax']) {
+                $dsn['dbsyntax'] = $dsn['phptype'];
+            }
+            return $dsn;
+        }
+
+        // Find phptype and dbsyntax
+        if (($pos = strpos($dsn, '://')) !== false) {
+            $str = substr($dsn, 0, $pos);
+            $dsn = substr($dsn, $pos + 3);
+        } else {
+            $str = $dsn;
+            $dsn = null;
+        }
+
+        // Get phptype and dbsyntax
+        // $str => phptype(dbsyntax)
+        if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
+            $parsed['phptype']  = $arr[1];
+            $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
+        } else {
+            $parsed['phptype']  = $str;
+            $parsed['dbsyntax'] = $str;
+        }
+
+        if (!count($dsn)) {
+            return $parsed;
+        }
+
+        // Get (if found): username and password
+        // $dsn => username:password@protocol+hostspec/database
+        if (($at = strrpos($dsn,'@')) !== false) {
+            $str = substr($dsn, 0, $at);
+            $dsn = substr($dsn, $at + 1);
+            if (($pos = strpos($str, ':')) !== false) {
+                $parsed['username'] = rawurldecode(substr($str, 0, $pos));
+                $parsed['password'] = rawurldecode(substr($str, $pos + 1));
+            } else {
+                $parsed['username'] = rawurldecode($str);
+            }
+        }
+
+        // Find protocol and hostspec
+
+        if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
+            // $dsn => proto(proto_opts)/database
+            $proto       = $match[1];
+            $proto_opts  = $match[2] ? $match[2] : false;
+            $dsn         = $match[3];
+
+        } else {
+            // $dsn => protocol+hostspec/database (old format)
+            if (strpos($dsn, '+') !== false) {
+                list($proto, $dsn) = explode('+', $dsn, 2);
+            }
+            if (strpos($dsn, '/') !== false) {
+                list($proto_opts, $dsn) = explode('/', $dsn, 2);
+            } else {
+                $proto_opts = $dsn;
+                $dsn = null;
+            }
+        }
+
+        // process the different protocol options
+        $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
+        $proto_opts = rawurldecode($proto_opts);
+        if ($parsed['protocol'] == 'tcp') {
+            if (strpos($proto_opts, ':') !== false) {
+                list($parsed['hostspec'],
+                     $parsed['port']) = explode(':', $proto_opts);
+            } else {
+                $parsed['hostspec'] = $proto_opts;
+            }
+        } elseif ($parsed['protocol'] == 'unix') {
+            $parsed['socket'] = $proto_opts;
+        }
+
+        // Get dabase if any
+        // $dsn => database
+        if ($dsn) {
+            if (($pos = strpos($dsn, '?')) === false) {
+                // /database
+                $parsed['database'] = rawurldecode($dsn);
+            } else {
+                // /database?param1=value1&param2=value2
+                $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
+                $dsn = substr($dsn, $pos + 1);
+                if (strpos($dsn, '&') !== false) {
+                    $opts = explode('&', $dsn);
+                } else { // database?param1=value1
+                    $opts = array($dsn);
+                }
+                foreach ($opts as $opt) {
+                    list($key, $value) = explode('=', $opt);
+                    if (!isset($parsed[$key])) {
+                        // don't allow params overwrite
+                        $parsed[$key] = rawurldecode($value);
+                    }
+                }
+            }
+        }
+
+        return $parsed;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class DB_Error
+
+/**
+ * DB_Error implements a class for reporting portable database error
+ * messages
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_Error extends PEAR_Error
+{
+    // {{{ constructor
+
+    /**
+     * DB_Error constructor
+     *
+     * @param mixed $code       DB error code, or string with error message
+     * @param int   $mode       what "error mode" to operate in
+     * @param int   $level      what error level to use for $mode &
+     *                           PEAR_ERROR_TRIGGER
+     * @param mixed $debuginfo  additional debug info, such as the last query
+     *
+     * @see PEAR_Error
+     */
+    function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
+                      $level = E_USER_NOTICE, $debuginfo = null)
+    {
+        if (is_int($code)) {
+            $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code,
+                              $mode, $level, $debuginfo);
+        } else {
+            $this->PEAR_Error("DB Error: $code", DB_ERROR,
+                              $mode, $level, $debuginfo);
+        }
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class DB_result
+
+/**
+ * This class implements a wrapper for a DB result set
+ *
+ * A new instance of this class will be returned by the DB implementation
+ * after processing a query that returns data.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_result
+{
+    // {{{ properties
+
+    /**
+     * Should results be freed automatically when there are no more rows?
+     * @var boolean
+     * @see DB_common::$options
+     */
+    var $autofree;
+
+    /**
+     * A reference to the DB_<driver> object
+     * @var object
+     */
+    var $dbh;
+
+    /**
+     * The current default fetch mode
+     * @var integer
+     * @see DB_common::$fetchmode
+     */
+    var $fetchmode;
+
+    /**
+     * The name of the class into which results should be fetched when
+     * DB_FETCHMODE_OBJECT is in effect
+     *
+     * @var string
+     * @see DB_common::$fetchmode_object_class
+     */
+    var $fetchmode_object_class;
+
+    /**
+     * The number of rows to fetch from a limit query
+     * @var integer
+     */
+    var $limit_count = null;
+
+    /**
+     * The row to start fetching from in limit queries
+     * @var integer
+     */
+    var $limit_from = null;
+
+    /**
+     * The execute parameters that created this result
+     * @var array
+     * @since Property available since Release 1.7.0
+     */
+    var $parameters;
+
+    /**
+     * The query string that created this result
+     *
+     * Copied here incase it changes in $dbh, which is referenced
+     *
+     * @var string
+     * @since Property available since Release 1.7.0
+     */
+    var $query;
+
+    /**
+     * The query result resource id created by PHP
+     * @var resource
+     */
+    var $result;
+
+    /**
+     * The present row being dealt with
+     * @var integer
+     */
+    var $row_counter = null;
+
+    /**
+     * The prepared statement resource id created by PHP in $dbh
+     *
+     * This resource is only available when the result set was created using
+     * a driver's native execute() method, not PEAR DB's emulated one.
+     *
+     * Copied here incase it changes in $dbh, which is referenced
+     *
+     * {@internal  Mainly here because the InterBase/Firebird API is only
+     * able to retrieve data from result sets if the statemnt handle is
+     * still in scope.}}
+     *
+     * @var resource
+     * @since Property available since Release 1.7.0
+     */
+    var $statement;
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor sets the object's properties
+     *
+     * @param object   &$dbh     the DB object reference
+     * @param resource $result   the result resource id
+     * @param array    $options  an associative array with result options
+     *
+     * @return void
+     */
+    function DB_result(&$dbh, $result, $options = array())
+    {
+        $this->autofree    = $dbh->options['autofree'];
+        $this->dbh         = &$dbh;
+        $this->fetchmode   = $dbh->fetchmode;
+        $this->fetchmode_object_class = $dbh->fetchmode_object_class;
+        $this->parameters  = $dbh->last_parameters;
+        $this->query       = $dbh->last_query;
+        $this->result      = $result;
+        $this->statement   = empty($dbh->last_stmt) ? null : $dbh->last_stmt;
+        foreach ($options as $key => $value) {
+            $this->setOption($key, $value);
+        }
+    }
+
+    /**
+     * Set options for the DB_result object
+     *
+     * @param string $key    the option to set
+     * @param mixed  $value  the value to set the option to
+     *
+     * @return void
+     */
+    function setOption($key, $value = null)
+    {
+        switch ($key) {
+            case 'limit_from':
+                $this->limit_from = $value;
+                break;
+            case 'limit_count':
+                $this->limit_count = $value;
+        }
+    }
+
+    // }}}
+    // {{{ fetchRow()
+
+    /**
+     * Fetch a row of data and return it by reference into an array
+     *
+     * The type of array returned can be controlled either by setting this
+     * method's <var>$fetchmode</var> parameter or by changing the default
+     * fetch mode setFetchMode() before calling this method.
+     *
+     * There are two options for standardizing the information returned
+     * from databases, ensuring their values are consistent when changing
+     * DBMS's.  These portability options can be turned on when creating a
+     * new DB object or by using setOption().
+     *
+     *   + <var>DB_PORTABILITY_LOWERCASE</var>
+     *     convert names of fields to lower case
+     *
+     *   + <var>DB_PORTABILITY_RTRIM</var>
+     *     right trim the data
+     *
+     * @param int $fetchmode  the constant indicating how to format the data
+     * @param int $rownum     the row number to fetch (index starts at 0)
+     *
+     * @return mixed  an array or object containing the row's data,
+     *                 NULL when the end of the result set is reached
+     *                 or a DB_Error object on failure.
+     *
+     * @see DB_common::setOption(), DB_common::setFetchMode()
+     */
+    function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->fetchmode;
+        }
+        if ($fetchmode === DB_FETCHMODE_OBJECT) {
+            $fetchmode = DB_FETCHMODE_ASSOC;
+            $object_class = $this->fetchmode_object_class;
+        }
+        if ($this->limit_from !== null) {
+            if ($this->row_counter === null) {
+                $this->row_counter = $this->limit_from;
+                // Skip rows
+                if ($this->dbh->features['limit'] === false) {
+                    $i = 0;
+                    while ($i++ < $this->limit_from) {
+                        $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+                    }
+                }
+            }
+            if ($this->row_counter >= ($this->limit_from + $this->limit_count))
+            {
+                if ($this->autofree) {
+                    $this->free();
+                }
+                $tmp = null;
+                return $tmp;
+            }
+            if ($this->dbh->features['limit'] === 'emulate') {
+                $rownum = $this->row_counter;
+            }
+            $this->row_counter++;
+        }
+        $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+        if ($res === DB_OK) {
+            if (isset($object_class)) {
+                // The default mode is specified in the
+                // DB_common::fetchmode_object_class property
+                if ($object_class == 'stdClass') {
+                    $arr = (object) $arr;
+                } else {
+                    $arr = &new $object_class($arr);
+                }
+            }
+            return $arr;
+        }
+        if ($res == null && $this->autofree) {
+            $this->free();
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Fetch a row of data into an array which is passed by reference
+     *
+     * The type of array returned can be controlled either by setting this
+     * method's <var>$fetchmode</var> parameter or by changing the default
+     * fetch mode setFetchMode() before calling this method.
+     *
+     * There are two options for standardizing the information returned
+     * from databases, ensuring their values are consistent when changing
+     * DBMS's.  These portability options can be turned on when creating a
+     * new DB object or by using setOption().
+     *
+     *   + <var>DB_PORTABILITY_LOWERCASE</var>
+     *     convert names of fields to lower case
+     *
+     *   + <var>DB_PORTABILITY_RTRIM</var>
+     *     right trim the data
+     *
+     * @param array &$arr       the variable where the data should be placed
+     * @param int   $fetchmode  the constant indicating how to format the data
+     * @param int   $rownum     the row number to fetch (index starts at 0)
+     *
+     * @return mixed  DB_OK if a row is processed, NULL when the end of the
+     *                 result set is reached or a DB_Error object on failure
+     *
+     * @see DB_common::setOption(), DB_common::setFetchMode()
+     */
+    function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+    {
+        if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->fetchmode;
+        }
+        if ($fetchmode === DB_FETCHMODE_OBJECT) {
+            $fetchmode = DB_FETCHMODE_ASSOC;
+            $object_class = $this->fetchmode_object_class;
+        }
+        if ($this->limit_from !== null) {
+            if ($this->row_counter === null) {
+                $this->row_counter = $this->limit_from;
+                // Skip rows
+                if ($this->dbh->features['limit'] === false) {
+                    $i = 0;
+                    while ($i++ < $this->limit_from) {
+                        $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+                    }
+                }
+            }
+            if ($this->row_counter >= (
+                    $this->limit_from + $this->limit_count))
+            {
+                if ($this->autofree) {
+                    $this->free();
+                }
+                return null;
+            }
+            if ($this->dbh->features['limit'] === 'emulate') {
+                $rownum = $this->row_counter;
+            }
+
+            $this->row_counter++;
+        }
+        $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+        if ($res === DB_OK) {
+            if (isset($object_class)) {
+                // default mode specified in the
+                // DB_common::fetchmode_object_class property
+                if ($object_class == 'stdClass') {
+                    $arr = (object) $arr;
+                } else {
+                    $arr = new $object_class($arr);
+                }
+            }
+            return DB_OK;
+        }
+        if ($res == null && $this->autofree) {
+            $this->free();
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Get the the number of columns in a result set
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     */
+    function numCols()
+    {
+        return $this->dbh->numCols($this->result);
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Get the number of rows in a result set
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function numRows()
+    {
+        if ($this->dbh->features['numrows'] === 'emulate'
+            && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS)
+        {
+            if ($this->dbh->features['prepare']) {
+                $res = $this->dbh->query($this->query, $this->parameters);
+            } else {
+                $res = $this->dbh->query($this->query);
+            }
+            if (DB::isError($res)) {
+                return $res;
+            }
+            $i = 0;
+            while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) {
+                $i++;
+            }
+            return $i;
+        } else {
+            return $this->dbh->numRows($this->result);
+        }
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Get the next result if a batch of queries was executed
+     *
+     * @return bool  true if a new result is available or false if not
+     */
+    function nextResult()
+    {
+        return $this->dbh->nextResult($this->result);
+    }
+
+    // }}}
+    // {{{ free()
+
+    /**
+     * Frees the resources allocated for this result set
+     *
+     * @return bool  true on success.  A DB_Error object on failure.
+     */
+    function free()
+    {
+        $err = $this->dbh->freeResult($this->result);
+        if (DB::isError($err)) {
+            return $err;
+        }
+        $this->result = false;
+        $this->statement = false;
+        return true;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * @see DB_common::tableInfo()
+     * @deprecated Method deprecated some time before Release 1.2
+     */
+    function tableInfo($mode = null)
+    {
+        if (is_string($mode)) {
+            return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+        return $this->dbh->tableInfo($this, $mode);
+    }
+
+    // }}}
+    // {{{ getQuery()
+
+    /**
+     * Determine the query string that created this result
+     *
+     * @return string  the query string
+     *
+     * @since Method available since Release 1.7.0
+     */
+    function getQuery()
+    {
+        return $this->query;
+    }
+
+    // }}}
+    // {{{ getRowCounter()
+
+    /**
+     * Tells which row number is currently being processed
+     *
+     * @return integer  the current row being looked at.  Starts at 1.
+     */
+    function getRowCounter()
+    {
+        return $this->row_counter;
+    }
+
+    // }}}
+}
+
+// }}}
+// {{{ class DB_row
+
+/**
+ * PEAR DB Row Object
+ *
+ * The object contains a row of data from a result set.  Each column's data
+ * is placed in a property named for the column.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ * @see        DB_common::setFetchMode()
+ */
+class DB_row
+{
+    // {{{ constructor
+
+    /**
+     * The constructor places a row's data into properties of this object
+     *
+     * @param array  the array containing the row's data
+     *
+     * @return void
+     */
+    function DB_row(&$arr)
+    {
+        foreach ($arr as $key => $value) {
+            $this->$key = &$arr[$key];
+        }
+    }
+
+    // }}}
+}
+
+// }}}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/common.php b/program/lib/DB/common.php
new file mode 100644 (file)
index 0000000..e143c3d
--- /dev/null
@@ -0,0 +1,2157 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Contains the DB_common base class
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: common.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+/**
+ * DB_common is the base class from which each database driver class extends
+ *
+ * All common methods are declared here.  If a given DBMS driver contains
+ * a particular method, that method will overload the one here.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_common extends PEAR
+{
+    // {{{ properties
+
+    /**
+     * The current default fetch mode
+     * @var integer
+     */
+    var $fetchmode = DB_FETCHMODE_ORDERED;
+
+    /**
+     * The name of the class into which results should be fetched when
+     * DB_FETCHMODE_OBJECT is in effect
+     *
+     * @var string
+     */
+    var $fetchmode_object_class = 'stdClass';
+
+    /**
+     * Was a connection present when the object was serialized()?
+     * @var bool
+     * @see DB_common::__sleep(), DB_common::__wake()
+     */
+    var $was_connected = null;
+
+    /**
+     * The most recently executed query
+     * @var string
+     */
+    var $last_query = '';
+
+    /**
+     * Run-time configuration options
+     *
+     * The 'optimize' option has been deprecated.  Use the 'portability'
+     * option instead.
+     *
+     * @var array
+     * @see DB_common::setOption()
+     */
+    var $options = array(
+        'result_buffering' => 500,
+        'persistent' => false,
+        'ssl' => false,
+        'debug' => 0,
+        'seqname_format' => '%s_seq',
+        'autofree' => false,
+        'portability' => DB_PORTABILITY_NONE,
+        'optimize' => 'performance',  // Deprecated.  Use 'portability'.
+    );
+
+    /**
+     * The parameters from the most recently executed query
+     * @var array
+     * @since Property available since Release 1.7.0
+     */
+    var $last_parameters = array();
+
+    /**
+     * The elements from each prepared statement
+     * @var array
+     */
+    var $prepare_tokens = array();
+
+    /**
+     * The data types of the various elements in each prepared statement
+     * @var array
+     */
+    var $prepare_types = array();
+
+    /**
+     * The prepared queries
+     * @var array
+     */
+    var $prepared_queries = array();
+
+
+    // }}}
+    // {{{ DB_common
+
+    /**
+     * This constructor calls <kbd>$this->PEAR('DB_Error')</kbd>
+     *
+     * @return void
+     */
+    function DB_common()
+    {
+        $this->PEAR('DB_Error');
+    }
+
+    // }}}
+    // {{{ __sleep()
+
+    /**
+     * Automatically indicates which properties should be saved
+     * when PHP's serialize() function is called
+     *
+     * @return array  the array of properties names that should be saved
+     */
+    function __sleep()
+    {
+        if ($this->connection) {
+            // Don't disconnect(), people use serialize() for many reasons
+            $this->was_connected = true;
+        } else {
+            $this->was_connected = false;
+        }
+        if (isset($this->autocommit)) {
+            return array('autocommit',
+                         'dbsyntax',
+                         'dsn',
+                         'features',
+                         'fetchmode',
+                         'fetchmode_object_class',
+                         'options',
+                         'was_connected',
+                   );
+        } else {
+            return array('dbsyntax',
+                         'dsn',
+                         'features',
+                         'fetchmode',
+                         'fetchmode_object_class',
+                         'options',
+                         'was_connected',
+                   );
+        }
+    }
+
+    // }}}
+    // {{{ __wakeup()
+
+    /**
+     * Automatically reconnects to the database when PHP's unserialize()
+     * function is called
+     *
+     * The reconnection attempt is only performed if the object was connected
+     * at the time PHP's serialize() function was run.
+     *
+     * @return void
+     */
+    function __wakeup()
+    {
+        if ($this->was_connected) {
+            $this->connect($this->dsn, $this->options);
+        }
+    }
+
+    // }}}
+    // {{{ __toString()
+
+    /**
+     * Automatic string conversion for PHP 5
+     *
+     * @return string  a string describing the current PEAR DB object
+     *
+     * @since Method available since Release 1.7.0
+     */
+    function __toString()
+    {
+        $info = strtolower(get_class($this));
+        $info .=  ': (phptype=' . $this->phptype .
+                  ', dbsyntax=' . $this->dbsyntax .
+                  ')';
+        if ($this->connection) {
+            $info .= ' [connected]';
+        }
+        return $info;
+    }
+
+    // }}}
+    // {{{ toString()
+
+    /**
+     * DEPRECATED:  String conversion method
+     *
+     * @return string  a string describing the current PEAR DB object
+     *
+     * @deprecated Method deprecated in Release 1.7.0
+     */
+    function toString()
+    {
+        return $this->__toString();
+    }
+
+    // }}}
+    // {{{ quoteString()
+
+    /**
+     * DEPRECATED: Quotes a string so it can be safely used within string
+     * delimiters in a query
+     *
+     * @param string $string  the string to be quoted
+     *
+     * @return string  the quoted string
+     *
+     * @see DB_common::quoteSmart(), DB_common::escapeSimple()
+     * @deprecated Method deprecated some time before Release 1.2
+     */
+    function quoteString($string)
+    {
+        $string = $this->quote($string);
+        if ($string{0} == "'") {
+            return substr($string, 1, -1);
+        }
+        return $string;
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * DEPRECATED: Quotes a string so it can be safely used in a query
+     *
+     * @param string $string  the string to quote
+     *
+     * @return string  the quoted string or the string <samp>NULL</samp>
+     *                  if the value submitted is <kbd>null</kbd>.
+     *
+     * @see DB_common::quoteSmart(), DB_common::escapeSimple()
+     * @deprecated Deprecated in release 1.6.0
+     */
+    function quote($string = null)
+    {
+        return ($string === null) ? 'NULL'
+                                  : "'" . str_replace("'", "''", $string) . "'";
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * Quotes a string so it can be safely used as a table or column name
+     *
+     * Delimiting style depends on which database driver is being used.
+     *
+     * NOTE: just because you CAN use delimited identifiers doesn't mean
+     * you SHOULD use them.  In general, they end up causing way more
+     * problems than they solve.
+     *
+     * Portability is broken by using the following characters inside
+     * delimited identifiers:
+     *   + backtick (<kbd>`</kbd>) -- due to MySQL
+     *   + double quote (<kbd>"</kbd>) -- due to Oracle
+     *   + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
+     *
+     * Delimited identifiers are known to generally work correctly under
+     * the following drivers:
+     *   + mssql
+     *   + mysql
+     *   + mysqli
+     *   + oci8
+     *   + odbc(access)
+     *   + odbc(db2)
+     *   + pgsql
+     *   + sqlite
+     *   + sybase (must execute <kbd>set quoted_identifier on</kbd> sometime
+     *     prior to use)
+     *
+     * InterBase doesn't seem to be able to use delimited identifiers
+     * via PHP 4.  They work fine under PHP 5.
+     *
+     * @param string $str  the identifier name to be quoted
+     *
+     * @return string  the quoted identifier
+     *
+     * @since Method available since Release 1.6.0
+     */
+    function quoteIdentifier($str)
+    {
+        return '"' . str_replace('"', '""', $str) . '"';
+    }
+
+    // }}}
+    // {{{ quoteSmart()
+
+    /**
+     * Formats input so it can be safely used in a query
+     *
+     * The output depends on the PHP data type of input and the database
+     * type being used.
+     *
+     * @param mixed $in  the data to be formatted
+     *
+     * @return mixed  the formatted data.  The format depends on the input's
+     *                 PHP type:
+     * <ul>
+     *  <li>
+     *    <kbd>input</kbd> -> <samp>returns</samp>
+     *  </li>
+     *  <li>
+     *    <kbd>null</kbd> -> the string <samp>NULL</samp>
+     *  </li>
+     *  <li>
+     *    <kbd>integer</kbd> or <kbd>double</kbd> -> the unquoted number
+     *  </li>
+     *  <li>
+     *    <kbd>bool</kbd> -> output depends on the driver in use
+     *    Most drivers return integers: <samp>1</samp> if
+     *    <kbd>true</kbd> or <samp>0</samp> if
+     *    <kbd>false</kbd>.
+     *    Some return strings: <samp>TRUE</samp> if
+     *    <kbd>true</kbd> or <samp>FALSE</samp> if
+     *    <kbd>false</kbd>.
+     *    Finally one returns strings: <samp>T</samp> if
+     *    <kbd>true</kbd> or <samp>F</samp> if
+     *    <kbd>false</kbd>. Here is a list of each DBMS,
+     *    the values returned and the suggested column type:
+     *    <ul>
+     *      <li>
+     *        <kbd>dbase</kbd> -> <samp>T/F</samp>
+     *        (<kbd>Logical</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>fbase</kbd> -> <samp>TRUE/FALSE</samp>
+     *        (<kbd>BOOLEAN</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>ibase</kbd> -> <samp>1/0</samp>
+     *        (<kbd>SMALLINT</kbd>) [1]
+     *      </li>
+     *      <li>
+     *        <kbd>ifx</kbd> -> <samp>1/0</samp>
+     *        (<kbd>SMALLINT</kbd>) [1]
+     *      </li>
+     *      <li>
+     *        <kbd>msql</kbd> -> <samp>1/0</samp>
+     *        (<kbd>INTEGER</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>mssql</kbd> -> <samp>1/0</samp>
+     *        (<kbd>BIT</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>mysql</kbd> -> <samp>1/0</samp>
+     *        (<kbd>TINYINT(1)</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>mysqli</kbd> -> <samp>1/0</samp>
+     *        (<kbd>TINYINT(1)</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>oci8</kbd> -> <samp>1/0</samp>
+     *        (<kbd>NUMBER(1)</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>odbc</kbd> -> <samp>1/0</samp>
+     *        (<kbd>SMALLINT</kbd>) [1]
+     *      </li>
+     *      <li>
+     *        <kbd>pgsql</kbd> -> <samp>TRUE/FALSE</samp>
+     *        (<kbd>BOOLEAN</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>sqlite</kbd> -> <samp>1/0</samp>
+     *        (<kbd>INTEGER</kbd>)
+     *      </li>
+     *      <li>
+     *        <kbd>sybase</kbd> -> <samp>1/0</samp>
+     *        (<kbd>TINYINT(1)</kbd>)
+     *      </li>
+     *    </ul>
+     *    [1] Accommodate the lowest common denominator because not all
+     *    versions of have <kbd>BOOLEAN</kbd>.
+     *  </li>
+     *  <li>
+     *    other (including strings and numeric strings) ->
+     *    the data with single quotes escaped by preceeding
+     *    single quotes, backslashes are escaped by preceeding
+     *    backslashes, then the whole string is encapsulated
+     *    between single quotes
+     *  </li>
+     * </ul>
+     *
+     * @see DB_common::escapeSimple()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteSmart($in)
+    {
+        if (is_int($in) || is_double($in)) {
+            return $in;
+        } elseif (is_bool($in)) {
+            return $in ? 1 : 0;
+        } elseif (is_null($in)) {
+            return 'NULL';
+        } else {
+            return "'" . $this->escapeSimple($in) . "'";
+        }
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * In SQLite, this makes things safe for inserts/updates, but may
+     * cause problems when performing text comparisons against columns
+     * containing binary data. See the
+     * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function escapeSimple($str)
+    {
+        return str_replace("'", "''", $str);
+    }
+
+    // }}}
+    // {{{ provides()
+
+    /**
+     * Tells whether the present driver supports a given feature
+     *
+     * @param string $feature  the feature you're curious about
+     *
+     * @return bool  whether this driver supports $feature
+     */
+    function provides($feature)
+    {
+        return $this->features[$feature];
+    }
+
+    // }}}
+    // {{{ setFetchMode()
+
+    /**
+     * Sets the fetch mode that should be used by default for query results
+     *
+     * @param integer $fetchmode    DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC
+     *                               or DB_FETCHMODE_OBJECT
+     * @param string $object_class  the class name of the object to be returned
+     *                               by the fetch methods when the
+     *                               DB_FETCHMODE_OBJECT mode is selected.
+     *                               If no class is specified by default a cast
+     *                               to object from the assoc array row will be
+     *                               done.  There is also the posibility to use
+     *                               and extend the 'DB_row' class.
+     *
+     * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT
+     */
+    function setFetchMode($fetchmode, $object_class = 'stdClass')
+    {
+        switch ($fetchmode) {
+            case DB_FETCHMODE_OBJECT:
+                $this->fetchmode_object_class = $object_class;
+            case DB_FETCHMODE_ORDERED:
+            case DB_FETCHMODE_ASSOC:
+                $this->fetchmode = $fetchmode;
+                break;
+            default:
+                return $this->raiseError('invalid fetchmode mode');
+        }
+    }
+
+    // }}}
+    // {{{ setOption()
+
+    /**
+     * Sets run-time configuration options for PEAR DB
+     *
+     * Options, their data types, default values and description:
+     * <ul>
+     * <li>
+     * <var>autofree</var> <kbd>boolean</kbd> = <samp>false</samp>
+     *      <br />should results be freed automatically when there are no
+     *            more rows?
+     * </li><li>
+     * <var>result_buffering</var> <kbd>integer</kbd> = <samp>500</samp>
+     *      <br />how many rows of the result set should be buffered?
+     *      <br />In mysql: mysql_unbuffered_query() is used instead of
+     *            mysql_query() if this value is 0.  (Release 1.7.0)
+     *      <br />In oci8: this value is passed to ocisetprefetch().
+     *            (Release 1.7.0)
+     * </li><li>
+     * <var>debug</var> <kbd>integer</kbd> = <samp>0</samp>
+     *      <br />debug level
+     * </li><li>
+     * <var>persistent</var> <kbd>boolean</kbd> = <samp>false</samp>
+     *      <br />should the connection be persistent?
+     * </li><li>
+     * <var>portability</var> <kbd>integer</kbd> = <samp>DB_PORTABILITY_NONE</samp>
+     *      <br />portability mode constant (see below)
+     * </li><li>
+     * <var>seqname_format</var> <kbd>string</kbd> = <samp>%s_seq</samp>
+     *      <br />the sprintf() format string used on sequence names.  This
+     *            format is applied to sequence names passed to
+     *            createSequence(), nextID() and dropSequence().
+     * </li><li>
+     * <var>ssl</var> <kbd>boolean</kbd> = <samp>false</samp>
+     *      <br />use ssl to connect?
+     * </li>
+     * </ul>
+     *
+     * -----------------------------------------
+     *
+     * PORTABILITY MODES
+     *
+     * These modes are bitwised, so they can be combined using <kbd>|</kbd>
+     * and removed using <kbd>^</kbd>.  See the examples section below on how
+     * to do this.
+     *
+     * <samp>DB_PORTABILITY_NONE</samp>
+     * turn off all portability features
+     *
+     * This mode gets automatically turned on if the deprecated
+     * <var>optimize</var> option gets set to <samp>performance</samp>.
+     *
+     *
+     * <samp>DB_PORTABILITY_LOWERCASE</samp>
+     * convert names of tables and fields to lower case when using
+     * <kbd>get*()</kbd>, <kbd>fetch*()</kbd> and <kbd>tableInfo()</kbd>
+     *
+     * This mode gets automatically turned on in the following databases
+     * if the deprecated option <var>optimize</var> gets set to
+     * <samp>portability</samp>:
+     * + oci8
+     *
+     *
+     * <samp>DB_PORTABILITY_RTRIM</samp>
+     * right trim the data output by <kbd>get*()</kbd> <kbd>fetch*()</kbd>
+     *
+     *
+     * <samp>DB_PORTABILITY_DELETE_COUNT</samp>
+     * force reporting the number of rows deleted
+     *
+     * Some DBMS's don't count the number of rows deleted when performing
+     * simple <kbd>DELETE FROM tablename</kbd> queries.  This portability
+     * mode tricks such DBMS's into telling the count by adding
+     * <samp>WHERE 1=1</samp> to the end of <kbd>DELETE</kbd> queries.
+     *
+     * This mode gets automatically turned on in the following databases
+     * if the deprecated option <var>optimize</var> gets set to
+     * <samp>portability</samp>:
+     * + fbsql
+     * + mysql
+     * + mysqli
+     * + sqlite
+     *
+     *
+     * <samp>DB_PORTABILITY_NUMROWS</samp>
+     * enable hack that makes <kbd>numRows()</kbd> work in Oracle
+     *
+     * This mode gets automatically turned on in the following databases
+     * if the deprecated option <var>optimize</var> gets set to
+     * <samp>portability</samp>:
+     * + oci8
+     *
+     *
+     * <samp>DB_PORTABILITY_ERRORS</samp>
+     * makes certain error messages in certain drivers compatible
+     * with those from other DBMS's
+     *
+     * + mysql, mysqli:  change unique/primary key constraints
+     *   DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
+     *
+     * + odbc(access):  MS's ODBC driver reports 'no such field' as code
+     *   07001, which means 'too few parameters.'  When this option is on
+     *   that code gets mapped to DB_ERROR_NOSUCHFIELD.
+     *   DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD
+     *
+     * <samp>DB_PORTABILITY_NULL_TO_EMPTY</samp>
+     * convert null values to empty strings in data output by get*() and
+     * fetch*().  Needed because Oracle considers empty strings to be null,
+     * while most other DBMS's know the difference between empty and null.
+     *
+     *
+     * <samp>DB_PORTABILITY_ALL</samp>
+     * turn on all portability features
+     *
+     * -----------------------------------------
+     *
+     * Example 1. Simple setOption() example
+     * <code>
+     * $db->setOption('autofree', true);
+     * </code>
+     *
+     * Example 2. Portability for lowercasing and trimming
+     * <code>
+     * $db->setOption('portability',
+     *                 DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM);
+     * </code>
+     *
+     * Example 3. All portability options except trimming
+     * <code>
+     * $db->setOption('portability',
+     *                 DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM);
+     * </code>
+     *
+     * @param string $option option name
+     * @param mixed  $value value for the option
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::$options
+     */
+    function setOption($option, $value)
+    {
+        if (isset($this->options[$option])) {
+            $this->options[$option] = $value;
+
+            /*
+             * Backwards compatibility check for the deprecated 'optimize'
+             * option.  Done here in case settings change after connecting.
+             */
+            if ($option == 'optimize') {
+                if ($value == 'portability') {
+                    switch ($this->phptype) {
+                        case 'oci8':
+                            $this->options['portability'] =
+                                    DB_PORTABILITY_LOWERCASE |
+                                    DB_PORTABILITY_NUMROWS;
+                            break;
+                        case 'fbsql':
+                        case 'mysql':
+                        case 'mysqli':
+                        case 'sqlite':
+                            $this->options['portability'] =
+                                    DB_PORTABILITY_DELETE_COUNT;
+                            break;
+                    }
+                } else {
+                    $this->options['portability'] = DB_PORTABILITY_NONE;
+                }
+            }
+
+            return DB_OK;
+        }
+        return $this->raiseError("unknown option $option");
+    }
+
+    // }}}
+    // {{{ getOption()
+
+    /**
+     * Returns the value of an option
+     *
+     * @param string $option  the option name you're curious about
+     *
+     * @return mixed  the option's value
+     */
+    function getOption($option)
+    {
+        if (isset($this->options[$option])) {
+            return $this->options[$option];
+        }
+        return $this->raiseError("unknown option $option");
+    }
+
+    // }}}
+    // {{{ prepare()
+
+    /**
+     * Prepares a query for multiple execution with execute()
+     *
+     * Creates a query that can be run multiple times.  Each time it is run,
+     * the placeholders, if any, will be replaced by the contents of
+     * execute()'s $data argument.
+     *
+     * Three types of placeholders can be used:
+     *   + <kbd>?</kbd>  scalar value (i.e. strings, integers).  The system
+     *                   will automatically quote and escape the data.
+     *   + <kbd>!</kbd>  value is inserted 'as is'
+     *   + <kbd>&</kbd>  requires a file name.  The file's contents get
+     *                   inserted into the query (i.e. saving binary
+     *                   data in a db)
+     *
+     * Example 1.
+     * <code>
+     * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
+     * $data = array(
+     *     "John's text",
+     *     "'it''s good'",
+     *     'filename.txt'
+     * );
+     * $res = $db->execute($sth, $data);
+     * </code>
+     *
+     * Use backslashes to escape placeholder characters if you don't want
+     * them to be interpreted as placeholders:
+     * <pre>
+     *    "UPDATE foo SET col=? WHERE col='over \& under'"
+     * </pre>
+     *
+     * With some database backends, this is emulated.
+     *
+     * {@internal ibase and oci8 have their own prepare() methods.}}
+     *
+     * @param string $query  the query to be prepared
+     *
+     * @return mixed  DB statement resource on success. A DB_Error object
+     *                 on failure.
+     *
+     * @see DB_common::execute()
+     */
+    function prepare($query)
+    {
+        $tokens   = preg_split('/((?<!\\\)[&?!])/', $query, -1,
+                               PREG_SPLIT_DELIM_CAPTURE);
+        $token     = 0;
+        $types     = array();
+        $newtokens = array();
+
+        foreach ($tokens as $val) {
+            switch ($val) {
+                case '?':
+                    $types[$token++] = DB_PARAM_SCALAR;
+                    break;
+                case '&':
+                    $types[$token++] = DB_PARAM_OPAQUE;
+                    break;
+                case '!':
+                    $types[$token++] = DB_PARAM_MISC;
+                    break;
+                default:
+                    $newtokens[] = preg_replace('/\\\([&?!])/', "\\1", $val);
+            }
+        }
+
+        $this->prepare_tokens[] = &$newtokens;
+        end($this->prepare_tokens);
+
+        $k = key($this->prepare_tokens);
+        $this->prepare_types[$k] = $types;
+        $this->prepared_queries[$k] = implode(' ', $newtokens);
+
+        return $k;
+    }
+
+    // }}}
+    // {{{ autoPrepare()
+
+    /**
+     * Automaticaly generates an insert or update query and pass it to prepare()
+     *
+     * @param string $table         the table name
+     * @param array  $table_fields  the array of field names
+     * @param int    $mode          a type of query to make:
+     *                               DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+     * @param string $where         for update queries: the WHERE clause to
+     *                               append to the SQL statement.  Don't
+     *                               include the "WHERE" keyword.
+     *
+     * @return resource  the query handle
+     *
+     * @uses DB_common::prepare(), DB_common::buildManipSQL()
+     */
+    function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT,
+                         $where = false)
+    {
+        $query = $this->buildManipSQL($table, $table_fields, $mode, $where);
+        if (DB::isError($query)) {
+            return $query;
+        }
+        return $this->prepare($query);
+    }
+
+    // }}}
+    // {{{ autoExecute()
+
+    /**
+     * Automaticaly generates an insert or update query and call prepare()
+     * and execute() with it
+     *
+     * @param string $table         the table name
+     * @param array  $fields_values the associative array where $key is a
+     *                               field name and $value its value
+     * @param int    $mode          a type of query to make:
+     *                               DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+     * @param string $where         for update queries: the WHERE clause to
+     *                               append to the SQL statement.  Don't
+     *                               include the "WHERE" keyword.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     *
+     * @uses DB_common::autoPrepare(), DB_common::execute()
+     */
+    function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT,
+                         $where = false)
+    {
+        $sth = $this->autoPrepare($table, array_keys($fields_values), $mode,
+                                  $where);
+        if (DB::isError($sth)) {
+            return $sth;
+        }
+        $ret =& $this->execute($sth, array_values($fields_values));
+        $this->freePrepared($sth);
+        return $ret;
+
+    }
+
+    // }}}
+    // {{{ buildManipSQL()
+
+    /**
+     * Produces an SQL query string for autoPrepare()
+     *
+     * Example:
+     * <pre>
+     * buildManipSQL('table_sql', array('field1', 'field2', 'field3'),
+     *               DB_AUTOQUERY_INSERT);
+     * </pre>
+     *
+     * That returns
+     * <samp>
+     * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?)
+     * </samp>
+     *
+     * NOTES:
+     *   - This belongs more to a SQL Builder class, but this is a simple
+     *     facility.
+     *   - Be carefull! If you don't give a $where param with an UPDATE
+     *     query, all the records of the table will be updated!
+     *
+     * @param string $table         the table name
+     * @param array  $table_fields  the array of field names
+     * @param int    $mode          a type of query to make:
+     *                               DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+     * @param string $where         for update queries: the WHERE clause to
+     *                               append to the SQL statement.  Don't
+     *                               include the "WHERE" keyword.
+     *
+     * @return string  the sql query for autoPrepare()
+     */
+    function buildManipSQL($table, $table_fields, $mode, $where = false)
+    {
+        if (count($table_fields) == 0) {
+            return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+        $first = true;
+        switch ($mode) {
+            case DB_AUTOQUERY_INSERT:
+                $values = '';
+                $names = '';
+                foreach ($table_fields as $value) {
+                    if ($first) {
+                        $first = false;
+                    } else {
+                        $names .= ',';
+                        $values .= ',';
+                    }
+                    $names .= $value;
+                    $values .= '?';
+                }
+                return "INSERT INTO $table ($names) VALUES ($values)";
+            case DB_AUTOQUERY_UPDATE:
+                $set = '';
+                foreach ($table_fields as $value) {
+                    if ($first) {
+                        $first = false;
+                    } else {
+                        $set .= ',';
+                    }
+                    $set .= "$value = ?";
+                }
+                $sql = "UPDATE $table SET $set";
+                if ($where) {
+                    $sql .= " WHERE $where";
+                }
+                return $sql;
+            default:
+                return $this->raiseError(DB_ERROR_SYNTAX);
+        }
+    }
+
+    // }}}
+    // {{{ execute()
+
+    /**
+     * Executes a DB statement prepared with prepare()
+     *
+     * Example 1.
+     * <code>
+     * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
+     * $data = array(
+     *     "John's text",
+     *     "'it''s good'",
+     *     'filename.txt'
+     * );
+     * $res =& $db->execute($sth, $data);
+     * </code>
+     *
+     * @param resource $stmt  a DB statement resource returned from prepare()
+     * @param mixed    $data  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     *
+     * {@internal ibase and oci8 have their own execute() methods.}}
+     *
+     * @see DB_common::prepare()
+     */
+    function &execute($stmt, $data = array())
+    {
+        $realquery = $this->executeEmulateQuery($stmt, $data);
+        if (DB::isError($realquery)) {
+            return $realquery;
+        }
+        $result = $this->simpleQuery($realquery);
+
+        if ($result === DB_OK || DB::isError($result)) {
+            return $result;
+        } else {
+            $tmp =& new DB_result($this, $result);
+            return $tmp;
+        }
+    }
+
+    // }}}
+    // {{{ executeEmulateQuery()
+
+    /**
+     * Emulates executing prepared statements if the DBMS not support them
+     *
+     * @param resource $stmt  a DB statement resource returned from execute()
+     * @param mixed    $data  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a string containing the real query run when emulating
+     *                 prepare/execute.  A DB_Error object on failure.
+     *
+     * @access protected
+     * @see DB_common::execute()
+     */
+    function executeEmulateQuery($stmt, $data = array())
+    {
+        $stmt = (int)$stmt;
+        $data = (array)$data;
+        $this->last_parameters = $data;
+
+        if (count($this->prepare_types[$stmt]) != count($data)) {
+            $this->last_query = $this->prepared_queries[$stmt];
+            return $this->raiseError(DB_ERROR_MISMATCH);
+        }
+
+        $realquery = $this->prepare_tokens[$stmt][0];
+
+        $i = 0;
+        foreach ($data as $value) {
+            if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) {
+                $realquery .= $this->quoteSmart($value);
+            } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) {
+                $fp = @fopen($value, 'rb');
+                if (!$fp) {
+                    return $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
+                }
+                $realquery .= $this->quoteSmart(fread($fp, filesize($value)));
+                fclose($fp);
+            } else {
+                $realquery .= $value;
+            }
+
+            $realquery .= $this->prepare_tokens[$stmt][++$i];
+        }
+
+        return $realquery;
+    }
+
+    // }}}
+    // {{{ executeMultiple()
+
+    /**
+     * Performs several execute() calls on the same statement handle
+     *
+     * $data must be an array indexed numerically
+     * from 0, one execute call is done for every "row" in the array.
+     *
+     * If an error occurs during execute(), executeMultiple() does not
+     * execute the unfinished rows, but rather returns that error.
+     *
+     * @param resource $stmt  query handle from prepare()
+     * @param array    $data  numeric array containing the
+     *                         data to insert into the query
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::prepare(), DB_common::execute()
+     */
+    function executeMultiple($stmt, $data)
+    {
+        foreach ($data as $value) {
+            $res =& $this->execute($stmt, $value);
+            if (DB::isError($res)) {
+                return $res;
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freePrepared()
+
+    /**
+     * Frees the internal resources associated with a prepared query
+     *
+     * @param resource $stmt           the prepared statement's PHP resource
+     * @param bool     $free_resource  should the PHP resource be freed too?
+     *                                  Use false if you need to get data
+     *                                  from the result set later.
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_common::prepare()
+     */
+    function freePrepared($stmt, $free_resource = true)
+    {
+        $stmt = (int)$stmt;
+        if (isset($this->prepare_tokens[$stmt])) {
+            unset($this->prepare_tokens[$stmt]);
+            unset($this->prepare_types[$stmt]);
+            unset($this->prepared_queries[$stmt]);
+            return true;
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * It is defined here to ensure all drivers have this method available.
+     *
+     * @param string $query  the query string to modify
+     *
+     * @return string  the modified query string
+     *
+     * @access protected
+     * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(),
+     *      DB_sqlite::modifyQuery()
+     */
+    function modifyQuery($query)
+    {
+        return $query;
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * It is defined here to assure that all implementations
+     * have this method defined.
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        return $query;
+    }
+
+    // }}}
+    // {{{ query()
+
+    /**
+     * Sends a query to the database server
+     *
+     * The query string can be either a normal statement to be sent directly
+     * to the server OR if <var>$params</var> are passed the query can have
+     * placeholders and it will be passed through prepare() and execute().
+     *
+     * @param string $query   the SQL query or the statement to prepare
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_result, DB_common::prepare(), DB_common::execute()
+     */
+    function &query($query, $params = array())
+    {
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+            $ret =& $this->execute($sth, $params);
+            $this->freePrepared($sth, false);
+            return $ret;
+        } else {
+            $this->last_parameters = array();
+            $result = $this->simpleQuery($query);
+            if ($result === DB_OK || DB::isError($result)) {
+                return $result;
+            } else {
+                $tmp =& new DB_result($this, $result);
+                return $tmp;
+            }
+        }
+    }
+
+    // }}}
+    // {{{ limitQuery()
+
+    /**
+     * Generates and executes a LIMIT query
+     *
+     * @param string $query   the query
+     * @param intr   $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  a new DB_result object for successful SELECT queries
+     *                 or DB_OK for successul data manipulation queries.
+     *                 A DB_Error object on failure.
+     */
+    function &limitQuery($query, $from, $count, $params = array())
+    {
+        $query = $this->modifyLimitQuery($query, $from, $count, $params);
+        if (DB::isError($query)){
+            return $query;
+        }
+        $result =& $this->query($query, $params);
+        if (is_a($result, 'DB_result')) {
+            $result->setOption('limit_from', $from);
+            $result->setOption('limit_count', $count);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ getOne()
+
+    /**
+     * Fetches the first column of the first row from a query result
+     *
+     * Takes care of doing the query and freeing the results when finished.
+     *
+     * @param string $query   the SQL query
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return mixed  the returned value of the query.
+     *                 A DB_Error object on failure.
+     */
+    function &getOne($query, $params = array())
+    {
+        $params = (array)$params;
+        // modifyLimitQuery() would be nice here, but it causes BC issues
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+
+        $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED);
+        $res->free();
+
+        if ($err !== DB_OK) {
+            return $err;
+        }
+
+        return $row[0];
+    }
+
+    // }}}
+    // {{{ getRow()
+
+    /**
+     * Fetches the first row of data returned from a query result
+     *
+     * Takes care of doing the query and freeing the results when finished.
+     *
+     * @param string $query   the SQL query
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     * @param int $fetchmode  the fetch mode to use
+     *
+     * @return array  the first row of results as an array.
+     *                 A DB_Error object on failure.
+     */
+    function &getRow($query, $params = array(),
+                     $fetchmode = DB_FETCHMODE_DEFAULT)
+    {
+        // compat check, the params and fetchmode parameters used to
+        // have the opposite order
+        if (!is_array($params)) {
+            if (is_array($fetchmode)) {
+                if ($params === null) {
+                    $tmp = DB_FETCHMODE_DEFAULT;
+                } else {
+                    $tmp = $params;
+                }
+                $params = $fetchmode;
+                $fetchmode = $tmp;
+            } elseif ($params !== null) {
+                $fetchmode = $params;
+                $params = array();
+            }
+        }
+        // modifyLimitQuery() would be nice here, but it causes BC issues
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+
+        $err = $res->fetchInto($row, $fetchmode);
+
+        $res->free();
+
+        if ($err !== DB_OK) {
+            return $err;
+        }
+
+        return $row;
+    }
+
+    // }}}
+    // {{{ getCol()
+
+    /**
+     * Fetches a single column from a query result and returns it as an
+     * indexed array
+     *
+     * @param string $query   the SQL query
+     * @param mixed  $col     which column to return (integer [column number,
+     *                         starting at 0] or string [column name])
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return array  the results as an array.  A DB_Error object on failure.
+     *
+     * @see DB_common::query()
+     */
+    function &getCol($query, $col = 0, $params = array())
+    {
+        $params = (array)$params;
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+
+        $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC;
+
+        if (!is_array($row = $res->fetchRow($fetchmode))) {
+            $ret = array();
+        } else {
+            if (!array_key_exists($col, $row)) {
+                $ret =& $this->raiseError(DB_ERROR_NOSUCHFIELD);
+            } else {
+                $ret = array($row[$col]);
+                while (is_array($row = $res->fetchRow($fetchmode))) {
+                    $ret[] = $row[$col];
+                }
+            }
+        }
+
+        $res->free();
+
+        if (DB::isError($row)) {
+            $ret = $row;
+        }
+
+        return $ret;
+    }
+
+    // }}}
+    // {{{ getAssoc()
+
+    /**
+     * Fetches an entire query result and returns it as an
+     * associative array using the first column as the key
+     *
+     * If the result set contains more than two columns, the value
+     * will be an array of the values from column 2-n.  If the result
+     * set contains only two columns, the returned value will be a
+     * scalar with the value of the second column (unless forced to an
+     * array with the $force_array parameter).  A DB error code is
+     * returned on errors.  If the result set contains fewer than two
+     * columns, a DB_ERROR_TRUNCATED error is returned.
+     *
+     * For example, if the table "mytable" contains:
+     *
+     * <pre>
+     *  ID      TEXT       DATE
+     * --------------------------------
+     *  1       'one'      944679408
+     *  2       'two'      944679408
+     *  3       'three'    944679408
+     * </pre>
+     *
+     * Then the call getAssoc('SELECT id,text FROM mytable') returns:
+     * <pre>
+     *   array(
+     *     '1' => 'one',
+     *     '2' => 'two',
+     *     '3' => 'three',
+     *   )
+     * </pre>
+     *
+     * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns:
+     * <pre>
+     *   array(
+     *     '1' => array('one', '944679408'),
+     *     '2' => array('two', '944679408'),
+     *     '3' => array('three', '944679408')
+     *   )
+     * </pre>
+     *
+     * If the more than one row occurs with the same value in the
+     * first column, the last row overwrites all previous ones by
+     * default.  Use the $group parameter if you don't want to
+     * overwrite like this.  Example:
+     *
+     * <pre>
+     * getAssoc('SELECT category,id,name FROM mytable', false, null,
+     *          DB_FETCHMODE_ASSOC, true) returns:
+     *
+     *   array(
+     *     '1' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            ),
+     *     '9' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            )
+     *   )
+     * </pre>
+     *
+     * Keep in mind that database functions in PHP usually return string
+     * values for results regardless of the database's internal type.
+     *
+     * @param string $query        the SQL query
+     * @param bool   $force_array  used only when the query returns
+     *                              exactly two columns.  If true, the values
+     *                              of the returned array will be one-element
+     *                              arrays instead of scalars.
+     * @param mixed  $params       array, string or numeric data to be used in
+     *                              execution of the statement.  Quantity of
+     *                              items passed must match quantity of
+     *                              placeholders in query:  meaning 1
+     *                              placeholder for non-array parameters or
+     *                              1 placeholder per array element.
+     * @param int   $fetchmode     the fetch mode to use
+     * @param bool  $group         if true, the values of the returned array
+     *                              is wrapped in another array.  If the same
+     *                              key value (in the first column) repeats
+     *                              itself, the values will be appended to
+     *                              this array instead of overwriting the
+     *                              existing values.
+     *
+     * @return array  the associative array containing the query results.
+     *                A DB_Error object on failure.
+     */
+    function &getAssoc($query, $force_array = false, $params = array(),
+                       $fetchmode = DB_FETCHMODE_DEFAULT, $group = false)
+    {
+        $params = (array)$params;
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if (DB::isError($res)) {
+            return $res;
+        }
+        if ($fetchmode == DB_FETCHMODE_DEFAULT) {
+            $fetchmode = $this->fetchmode;
+        }
+        $cols = $res->numCols();
+
+        if ($cols < 2) {
+            $tmp =& $this->raiseError(DB_ERROR_TRUNCATED);
+            return $tmp;
+        }
+
+        $results = array();
+
+        if ($cols > 2 || $force_array) {
+            // return array values
+            // XXX this part can be optimized
+            if ($fetchmode == DB_FETCHMODE_ASSOC) {
+                while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) {
+                    reset($row);
+                    $key = current($row);
+                    unset($row[key($row)]);
+                    if ($group) {
+                        $results[$key][] = $row;
+                    } else {
+                        $results[$key] = $row;
+                    }
+                }
+            } elseif ($fetchmode == DB_FETCHMODE_OBJECT) {
+                while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) {
+                    $arr = get_object_vars($row);
+                    $key = current($arr);
+                    if ($group) {
+                        $results[$key][] = $row;
+                    } else {
+                        $results[$key] = $row;
+                    }
+                }
+            } else {
+                while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
+                    // we shift away the first element to get
+                    // indices running from 0 again
+                    $key = array_shift($row);
+                    if ($group) {
+                        $results[$key][] = $row;
+                    } else {
+                        $results[$key] = $row;
+                    }
+                }
+            }
+            if (DB::isError($row)) {
+                $results = $row;
+            }
+        } else {
+            // return scalar values
+            while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
+                if ($group) {
+                    $results[$row[0]][] = $row[1];
+                } else {
+                    $results[$row[0]] = $row[1];
+                }
+            }
+            if (DB::isError($row)) {
+                $results = $row;
+            }
+        }
+
+        $res->free();
+
+        return $results;
+    }
+
+    // }}}
+    // {{{ getAll()
+
+    /**
+     * Fetches all of the rows from a query result
+     *
+     * @param string $query      the SQL query
+     * @param mixed  $params     array, string or numeric data to be used in
+     *                            execution of the statement.  Quantity of
+     *                            items passed must match quantity of
+     *                            placeholders in query:  meaning 1
+     *                            placeholder for non-array parameters or
+     *                            1 placeholder per array element.
+     * @param int    $fetchmode  the fetch mode to use:
+     *                            + DB_FETCHMODE_ORDERED
+     *                            + DB_FETCHMODE_ASSOC
+     *                            + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED
+     *                            + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED
+     *
+     * @return array  the nested array.  A DB_Error object on failure.
+     */
+    function &getAll($query, $params = array(),
+                     $fetchmode = DB_FETCHMODE_DEFAULT)
+    {
+        // compat check, the params and fetchmode parameters used to
+        // have the opposite order
+        if (!is_array($params)) {
+            if (is_array($fetchmode)) {
+                if ($params === null) {
+                    $tmp = DB_FETCHMODE_DEFAULT;
+                } else {
+                    $tmp = $params;
+                }
+                $params = $fetchmode;
+                $fetchmode = $tmp;
+            } elseif ($params !== null) {
+                $fetchmode = $params;
+                $params = array();
+            }
+        }
+
+        if (sizeof($params) > 0) {
+            $sth = $this->prepare($query);
+
+            if (DB::isError($sth)) {
+                return $sth;
+            }
+
+            $res =& $this->execute($sth, $params);
+            $this->freePrepared($sth);
+        } else {
+            $res =& $this->query($query);
+        }
+
+        if ($res === DB_OK || DB::isError($res)) {
+            return $res;
+        }
+
+        $results = array();
+        while (DB_OK === $res->fetchInto($row, $fetchmode)) {
+            if ($fetchmode & DB_FETCHMODE_FLIPPED) {
+                foreach ($row as $key => $val) {
+                    $results[$key][] = $val;
+                }
+            } else {
+                $results[] = $row;
+            }
+        }
+
+        $res->free();
+
+        if (DB::isError($row)) {
+            $tmp =& $this->raiseError($row);
+            return $tmp;
+        }
+        return $results;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Determines the number of rows in a query result
+     *
+     * @param resource $result  the query result idenifier produced by PHP
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function numRows($result)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ getSequenceName()
+
+    /**
+     * Generates the name used inside the database for a sequence
+     *
+     * The createSequence() docblock contains notes about storing sequence
+     * names.
+     *
+     * @param string $sqn  the sequence's public name
+     *
+     * @return string  the sequence's name in the backend
+     *
+     * @access protected
+     * @see DB_common::createSequence(), DB_common::dropSequence(),
+     *      DB_common::nextID(), DB_common::setOption()
+     */
+    function getSequenceName($sqn)
+    {
+        return sprintf($this->getOption('seqname_format'),
+                       preg_replace('/[^a-z0-9_.]/i', '_', $sqn));
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::dropSequence(),
+     *      DB_common::getSequenceName()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * The name of a given sequence is determined by passing the string
+     * provided in the <var>$seq_name</var> argument through PHP's sprintf()
+     * function using the value from the <var>seqname_format</var> option as
+     * the sprintf()'s format argument.
+     *
+     * <var>seqname_format</var> is set via setOption().
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_common::nextID()
+     */
+    function createSequence($seq_name)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_common::nextID()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ raiseError()
+
+    /**
+     * Communicates an error and invoke error callbacks, etc
+     *
+     * Basically a wrapper for PEAR::raiseError without the message string.
+     *
+     * @param mixed   integer error code, or a PEAR error object (all
+     *                 other parameters are ignored if this parameter is
+     *                 an object
+     * @param int     error mode, see PEAR_Error docs
+     * @param mixed   if error mode is PEAR_ERROR_TRIGGER, this is the
+     *                 error level (E_USER_NOTICE etc).  If error mode is
+     *                 PEAR_ERROR_CALLBACK, this is the callback function,
+     *                 either as a function name, or as an array of an
+     *                 object and method name.  For other error modes this
+     *                 parameter is ignored.
+     * @param string  extra debug information.  Defaults to the last
+     *                 query and native error code.
+     * @param mixed   native error code, integer or string depending the
+     *                 backend
+     *
+     * @return object  the PEAR_Error object
+     *
+     * @see PEAR_Error
+     */
+    function &raiseError($code = DB_ERROR, $mode = null, $options = null,
+                         $userinfo = null, $nativecode = null)
+    {
+        // The error is yet a DB error object
+        if (is_object($code)) {
+            // because we the static PEAR::raiseError, our global
+            // handler should be used if it is set
+            if ($mode === null && !empty($this->_default_error_mode)) {
+                $mode    = $this->_default_error_mode;
+                $options = $this->_default_error_options;
+            }
+            $tmp = PEAR::raiseError($code, null, $mode, $options,
+                                    null, null, true);
+            return $tmp;
+        }
+
+        if ($userinfo === null) {
+            $userinfo = $this->last_query;
+        }
+
+        if ($nativecode) {
+            $userinfo .= ' [nativecode=' . trim($nativecode) . ']';
+        } else {
+            $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']';
+        }
+
+        $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo,
+                                'DB_Error', true);
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return mixed  the DBMS' error code.  A DB_Error object on failure.
+     */
+    function errorNative()
+    {
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Maps native error codes to DB's portable ones
+     *
+     * Uses the <var>$errorcode_map</var> property defined in each driver.
+     *
+     * @param string|int $nativecode  the error code returned by the DBMS
+     *
+     * @return int  the portable DB error code.  Return DB_ERROR if the
+     *               current driver doesn't have a mapping for the
+     *               $nativecode submitted.
+     */
+    function errorCode($nativecode)
+    {
+        if (isset($this->errorcode_map[$nativecode])) {
+            return $this->errorcode_map[$nativecode];
+        }
+        // Fall back to DB_ERROR if there was no mapping.
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ errorMessage()
+
+    /**
+     * Maps a DB error code to a textual message
+     *
+     * @param integer $dbcode  the DB error code
+     *
+     * @return string  the error message corresponding to the error code
+     *                  submitted.  FALSE if the error code is unknown.
+     *
+     * @see DB::errorMessage()
+     */
+    function errorMessage($dbcode)
+    {
+        return DB::errorMessage($this->errorcode_map[$dbcode]);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * The format of the resulting array depends on which <var>$mode</var>
+     * you select.  The sample output below is based on this query:
+     * <pre>
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * </pre>
+     *
+     * <ul>
+     * <li>
+     *
+     * <kbd>null</kbd> (default)
+     *   <pre>
+     *   [0] => Array (
+     *       [table] => tblFoo
+     *       [name] => fldId
+     *       [type] => int
+     *       [len] => 11
+     *       [flags] => primary_key not_null
+     *   )
+     *   [1] => Array (
+     *       [table] => tblFoo
+     *       [name] => fldPhone
+     *       [type] => string
+     *       [len] => 20
+     *       [flags] =>
+     *   )
+     *   [2] => Array (
+     *       [table] => tblBar
+     *       [name] => fldId
+     *       [type] => int
+     *       [len] => 11
+     *       [flags] => primary_key not_null
+     *   )
+     *   </pre>
+     *
+     * </li><li>
+     *
+     * <kbd>DB_TABLEINFO_ORDER</kbd>
+     *
+     *   <p>In addition to the information found in the default output,
+     *   a notation of the number of columns is provided by the
+     *   <samp>num_fields</samp> element while the <samp>order</samp>
+     *   element provides an array with the column names as the keys and
+     *   their location index number (corresponding to the keys in the
+     *   the default output) as the values.</p>
+     *
+     *   <p>If a result set has identical field names, the last one is
+     *   used.</p>
+     *
+     *   <pre>
+     *   [num_fields] => 3
+     *   [order] => Array (
+     *       [fldId] => 2
+     *       [fldTrans] => 1
+     *   )
+     *   </pre>
+     *
+     * </li><li>
+     *
+     * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>
+     *
+     *   <p>Similar to <kbd>DB_TABLEINFO_ORDER</kbd> but adds more
+     *   dimensions to the array in which the table names are keys and
+     *   the field names are sub-keys.  This is helpful for queries that
+     *   join tables which have identical field names.</p>
+     *
+     *   <pre>
+     *   [num_fields] => 3
+     *   [ordertable] => Array (
+     *       [tblFoo] => Array (
+     *           [fldId] => 0
+     *           [fldPhone] => 1
+     *       )
+     *       [tblBar] => Array (
+     *           [fldId] => 2
+     *       )
+     *   )
+     *   </pre>
+     *
+     * </li>
+     * </ul>
+     *
+     * The <samp>flags</samp> element contains a space separated list
+     * of extra information about the field.  This data is inconsistent
+     * between DBMS's due to the way each DBMS works.
+     *   + <samp>primary_key</samp>
+     *   + <samp>unique_key</samp>
+     *   + <samp>multiple_key</samp>
+     *   + <samp>not_null</samp>
+     *
+     * Most DBMS's only provide the <samp>table</samp> and <samp>flags</samp>
+     * elements if <var>$result</var> is a table name.  The following DBMS's
+     * provide full information from queries:
+     *   + fbsql
+     *   + mysql
+     *
+     * If the 'portability' option has <samp>DB_PORTABILITY_LOWERCASE</samp>
+     * turned on, the names of tables and fields will be lowercased.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                string containing the name of a table.
+     *                                While this also accepts a query result
+     *                                resource identifier, this behavior is
+     *                                deprecated.
+     * @param int  $mode   either unused or one of the tableInfo modes:
+     *                     <kbd>DB_TABLEINFO_ORDERTABLE</kbd>,
+     *                     <kbd>DB_TABLEINFO_ORDER</kbd> or
+     *                     <kbd>DB_TABLEINFO_FULL</kbd> (which does both).
+     *                     These are bitwise, so the first two can be
+     *                     combined using <kbd>|</kbd>.
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::setOption()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        /*
+         * If the DB_<driver> class has a tableInfo() method, that one
+         * overrides this one.  But, if the driver doesn't have one,
+         * this method runs and tells users about that fact.
+         */
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ getTables()
+
+    /**
+     * Lists the tables in the current database
+     *
+     * @return array  the list of tables.  A DB_Error object on failure.
+     *
+     * @deprecated Method deprecated some time before Release 1.2
+     */
+    function getTables()
+    {
+        return $this->getListOf('tables');
+    }
+
+    // }}}
+    // {{{ getListOf()
+
+    /**
+     * Lists internal database information
+     *
+     * @param string $type  type of information being sought.
+     *                       Common items being sought are:
+     *                       tables, databases, users, views, functions
+     *                       Each DBMS's has its own capabilities.
+     *
+     * @return array  an array listing the items sought.
+     *                 A DB DB_Error object on failure.
+     */
+    function getListOf($type)
+    {
+        $sql = $this->getSpecialQuery($type);
+        if ($sql === null) {
+            $this->last_query = '';
+            return $this->raiseError(DB_ERROR_UNSUPPORTED);
+        } elseif (is_int($sql) || DB::isError($sql)) {
+            // Previous error
+            return $this->raiseError($sql);
+        } elseif (is_array($sql)) {
+            // Already the result
+            return $sql;
+        }
+        // Launch this query
+        return $this->getCol($sql);
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        return $this->raiseError(DB_ERROR_UNSUPPORTED);
+    }
+
+    // }}}
+    // {{{ _rtrimArrayValues()
+
+    /**
+     * Right-trims all strings in an array
+     *
+     * @param array $array  the array to be trimmed (passed by reference)
+     *
+     * @return void
+     *
+     * @access protected
+     */
+    function _rtrimArrayValues(&$array)
+    {
+        foreach ($array as $key => $value) {
+            if (is_string($value)) {
+                $array[$key] = rtrim($value);
+            }
+        }
+    }
+
+    // }}}
+    // {{{ _convertNullArrayValuesToEmpty()
+
+    /**
+     * Converts all null values in an array to empty strings
+     *
+     * @param array  $array  the array to be de-nullified (passed by reference)
+     *
+     * @return void
+     *
+     * @access protected
+     */
+    function _convertNullArrayValuesToEmpty(&$array)
+    {
+        foreach ($array as $key => $value) {
+            if (is_null($value)) {
+                $array[$key] = '';
+            }
+        }
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/dbase.php b/program/lib/DB/dbase.php
new file mode 100644 (file)
index 0000000..b1f8d0c
--- /dev/null
@@ -0,0 +1,510 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's dbase extension
+ * for interacting with dBase databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: dbase.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's dbase extension
+ * for interacting with dBase databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_dbase extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'dbase';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'dbase';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => false,
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => false,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => false,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * A means of emulating result resources
+     * @var array
+     */
+    var $res_row = array();
+
+    /**
+     * The quantity of results so far
+     *
+     * For emulating result resources.
+     *
+     * @var integer
+     */
+    var $result = 0;
+
+    /**
+     * Maps dbase data type id's to human readable strings
+     *
+     * The human readable values are based on the output of PHP's
+     * dbase_get_header_info() function.
+     *
+     * @var array
+     * @since Property available since Release 1.7.0
+     */
+    var $types = array(
+        'C' => 'character',
+        'D' => 'date',
+        'L' => 'boolean',
+        'M' => 'memo',
+        'N' => 'number',
+    );
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_dbase()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database and create it if it doesn't exist
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's dbase driver supports the following extra DSN options:
+     *   + mode    An integer specifying the read/write mode to use
+     *              (0 = read only, 1 = write only, 2 = read/write).
+     *              Available since PEAR DB 1.7.0.
+     *   + fields  An array of arrays that PHP's dbase_create() function needs
+     *              to create a new database.  This information is used if the
+     *              dBase file specified in the "database" segment of the DSN
+     *              does not exist.  For more info, see the PHP manual's
+     *              {@link http://php.net/dbase_create dbase_create()} page.
+     *              Available since PEAR DB 1.7.0.
+     *
+     * Example of how to connect and establish a new dBase file if necessary:
+     * <code>
+     * require_once 'DB.php';
+     *
+     * $dsn = array(
+     *     'phptype'  => 'dbase',
+     *     'database' => '/path/and/name/of/dbase/file',
+     *     'mode'     => 2,
+     *     'fields'   => array(
+     *         array('a', 'N', 5, 0),
+     *         array('b', 'C', 40),
+     *         array('c', 'C', 255),
+     *         array('d', 'C', 20),
+     *     ),
+     * );
+     * $options = array(
+     *     'debug'       => 2,
+     *     'portability' => DB_PORTABILITY_ALL,
+     * );
+     *
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('dbase')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        /*
+         * Turn track_errors on for entire script since $php_errormsg
+         * is the only way to find errors from the dbase extension.
+         */
+        ini_set('track_errors', 1);
+        $php_errormsg = '';
+
+        if (!file_exists($dsn['database'])) {
+            $this->dsn['mode'] = 2;
+            if (empty($dsn['fields']) || !is_array($dsn['fields'])) {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         'the dbase file does not exist and '
+                                         . 'it could not be created because '
+                                         . 'the "fields" element of the DSN '
+                                         . 'is not properly set');
+            }
+            $this->connection = @dbase_create($dsn['database'],
+                                              $dsn['fields']);
+            if (!$this->connection) {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         'the dbase file does not exist and '
+                                         . 'the attempt to create it failed: '
+                                         . $php_errormsg);
+            }
+        } else {
+            if (!isset($this->dsn['mode'])) {
+                $this->dsn['mode'] = 0;
+            }
+            $this->connection = @dbase_open($dsn['database'],
+                                            $this->dsn['mode']);
+            if (!$this->connection) {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $php_errormsg);
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @dbase_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ &query()
+
+    function &query($query = null)
+    {
+        // emulate result resources
+        $this->res_row[(int)$this->result] = 0;
+        $tmp =& new DB_result($this, $this->result++);
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum === null) {
+            $rownum = $this->res_row[(int)$result]++;
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @dbase_get_record_with_names($this->connection, $rownum);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @dbase_get_record($this->connection, $rownum);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($foo)
+    {
+        return @dbase_numfields($this->connection);
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($foo)
+    {
+        return @dbase_numrecords($this->connection);
+    }
+
+    // }}}
+    // {{{ quoteSmart()
+
+    /**
+     * Formats input so it can be safely used in a query
+     *
+     * @param mixed $in  the data to be formatted
+     *
+     * @return mixed  the formatted data.  The format depends on the input's
+     *                 PHP type:
+     *                 + null = the string <samp>NULL</samp>
+     *                 + boolean = <samp>T</samp> if true or
+     *                   <samp>F</samp> if false.  Use the <kbd>Logical</kbd>
+     *                   data type.
+     *                 + integer or double = the unquoted number
+     *                 + other (including strings and numeric strings) =
+     *                   the data with single quotes escaped by preceeding
+     *                   single quotes then the whole string is encapsulated
+     *                   between single quotes
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteSmart($in)
+    {
+        if (is_int($in) || is_double($in)) {
+            return $in;
+        } elseif (is_bool($in)) {
+            return $in ? 'T' : 'F';
+        } elseif (is_null($in)) {
+            return 'NULL';
+        } else {
+            return "'" . $this->escapeSimple($in) . "'";
+        }
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about the current database
+     *
+     * @param mixed $result  THIS IS UNUSED IN DBASE.  The current database
+     *                       is examined regardless of what is provided here.
+     * @param int   $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     * @since Method available since Release 1.7.0
+     */
+    function tableInfo($result = null, $mode = null)
+    {
+        if (function_exists('dbase_get_header_info')) {
+            $id = @dbase_get_header_info($this->connection);
+            if (!$id && $php_errormsg) {
+                return $this->raiseError(DB_ERROR,
+                                         null, null, null,
+                                         $php_errormsg);
+            }
+        } else {
+            /*
+             * This segment for PHP 4 is loosely based on code by
+             * Hadi Rusiah <deegos@yahoo.com> in the comments on
+             * the dBase reference page in the PHP manual.
+             */
+            $db = @fopen($this->dsn['database'], 'r');
+            if (!$db) {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $php_errormsg);
+            }
+
+            $id = array();
+            $i  = 0;
+
+            $line = fread($db, 32);
+            while (!feof($db)) {
+                $line = fread($db, 32);
+                if (substr($line, 0, 1) == chr(13)) {
+                    break;
+                } else {
+                    $pos = strpos(substr($line, 0, 10), chr(0));
+                    $pos = ($pos == 0 ? 10 : $pos);
+                    $id[$i] = array(
+                        'name'   => substr($line, 0, $pos),
+                        'type'   => $this->types[substr($line, 11, 1)],
+                        'length' => ord(substr($line, 16, 1)),
+                        'precision' => ord(substr($line, 17, 1)),
+                    );
+                }
+                $i++;
+            }
+
+            fclose($db);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $res   = array();
+        $count = count($id);
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table' => $this->dsn['database'],
+                'name'  => $case_func($id[$i]['name']),
+                'type'  => $id[$i]['type'],
+                'len'   => $id[$i]['length'],
+                'flags' => ''
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        return $res;
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/fbsql.php b/program/lib/DB/fbsql.php
new file mode 100644 (file)
index 0000000..431c0d4
--- /dev/null
@@ -0,0 +1,770 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's fbsql extension
+ * for interacting with FrontBase databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Frank M. Kromann <frank@frontbase.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: fbsql.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's fbsql extension
+ * for interacting with FrontBase databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Frank M. Kromann <frank@frontbase.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ * @since      Class functional since Release 1.7.0
+ */
+class DB_fbsql extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'fbsql';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'fbsql';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+         22 => DB_ERROR_SYNTAX,
+         85 => DB_ERROR_ALREADY_EXISTS,
+        108 => DB_ERROR_SYNTAX,
+        116 => DB_ERROR_NOSUCHTABLE,
+        124 => DB_ERROR_VALUE_COUNT_ON_ROW,
+        215 => DB_ERROR_NOSUCHFIELD,
+        217 => DB_ERROR_INVALID_NUMBER,
+        226 => DB_ERROR_NOSUCHFIELD,
+        231 => DB_ERROR_INVALID,
+        239 => DB_ERROR_TRUNCATED,
+        251 => DB_ERROR_SYNTAX,
+        266 => DB_ERROR_NOT_FOUND,
+        357 => DB_ERROR_CONSTRAINT_NOT_NULL,
+        358 => DB_ERROR_CONSTRAINT,
+        360 => DB_ERROR_CONSTRAINT,
+        361 => DB_ERROR_CONSTRAINT,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_fbsql()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('fbsql')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $params = array(
+            $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
+            $dsn['username'] ? $dsn['username'] : null,
+            $dsn['password'] ? $dsn['password'] : null,
+        );
+
+        $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect';
+
+        $ini = ini_get('track_errors');
+        $php_errormsg = '';
+        if ($ini) {
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+        } else {
+            ini_set('track_errors', 1);
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+            ini_set('track_errors', $ini);
+        }
+
+        if (!$this->connection) {
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     $php_errormsg);
+        }
+
+        if ($dsn['database']) {
+            if (!@fbsql_select_db($dsn['database'], $this->connection)) {
+                return $this->fbsqlRaiseError();
+            }
+        }
+
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @fbsql_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        $result = @fbsql_query("$query;", $this->connection);
+        if (!$result) {
+            return $this->fbsqlRaiseError();
+        }
+        // Determine which queries that should return data, and which
+        // should return an error code only.
+        if (DB::isManip($query)) {
+            return DB_OK;
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal fbsql result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return @fbsql_next_result($result);
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@fbsql_data_seek($result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @fbsql_fetch_array($result, FBSQL_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @fbsql_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @fbsql_free_result($result);
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff=false)
+    {
+        if ($onoff) {
+            $this->query("SET COMMIT TRUE");
+        } else {
+            $this->query("SET COMMIT FALSE");
+        }
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        @fbsql_commit();
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        @fbsql_rollback();
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @fbsql_num_fields($result);
+        if (!$cols) {
+            return $this->fbsqlRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @fbsql_num_rows($result);
+        if ($rows === null) {
+            return $this->fbsqlRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (DB::isManip($this->last_query)) {
+            $result = @fbsql_affected_rows($this->connection);
+        } else {
+            $result = 0;
+        }
+        return $result;
+     }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_fbsql::createSequence(), DB_fbsql::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        do {
+            $repeat = 0;
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query('SELECT UNIQUE FROM ' . $seqname);
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+                $repeat = 1;
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $result;
+                }
+            } else {
+                $repeat = 0;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->fbsqlRaiseError();
+        }
+        $result->fetchInto($tmp, DB_FETCHMODE_ORDERED);
+        return $tmp[0];
+    }
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_fbsql::nextID(), DB_fbsql::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $res = $this->query('CREATE TABLE ' . $seqname
+                            . ' (id INTEGER NOT NULL,'
+                            . ' PRIMARY KEY(id))');
+        if ($res) {
+            $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_fbsql::nextID(), DB_fbsql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)
+                            . ' RESTRICT');
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        if (DB::isManip($query)) {
+            return preg_replace('/^([\s(])*SELECT/i',
+                                "\\1SELECT TOP($count)", $query);
+        } else {
+            return preg_replace('/([\s(])*SELECT/i',
+                                "\\1SELECT TOP($from, $count)", $query);
+        }
+    }
+
+    // }}}
+    // {{{ quoteSmart()
+
+    /**
+     * Formats input so it can be safely used in a query
+     *
+     * @param mixed $in  the data to be formatted
+     *
+     * @return mixed  the formatted data.  The format depends on the input's
+     *                 PHP type:
+     *                 + null = the string <samp>NULL</samp>
+     *                 + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
+     *                 + integer or double = the unquoted number
+     *                 + other (including strings and numeric strings) =
+     *                   the data escaped according to FrontBase's settings
+     *                   then encapsulated between single quotes
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteSmart($in)
+    {
+        if (is_int($in) || is_double($in)) {
+            return $in;
+        } elseif (is_bool($in)) {
+            return $in ? 'TRUE' : 'FALSE';
+        } elseif (is_null($in)) {
+            return 'NULL';
+        } else {
+            return "'" . $this->escapeSimple($in) . "'";
+        }
+    }
+
+    // }}}
+    // {{{ fbsqlRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_fbsql::errorNative(), DB_common::errorCode()
+     */
+    function fbsqlRaiseError($errno = null)
+    {
+        if ($errno === null) {
+            $errno = $this->errorCode(fbsql_errno($this->connection));
+        }
+        return $this->raiseError($errno, null, null, null,
+                                 @fbsql_error($this->connection));
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return int  the DBMS' error code
+     */
+    function errorNative()
+    {
+        return @fbsql_errno($this->connection);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @fbsql_list_fields($this->dsn['database'],
+                                     $result, $this->connection);
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @fbsql_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table' => $case_func(@fbsql_field_table($id, $i)),
+                'name'  => $case_func(@fbsql_field_name($id, $i)),
+                'type'  => @fbsql_field_type($id, $i),
+                'len'   => @fbsql_field_len($id, $i),
+                'flags' => @fbsql_field_flags($id, $i),
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @fbsql_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SELECT "table_name" FROM information_schema.tables'
+                       . ' t0, information_schema.schemata t1'
+                       . ' WHERE t0.schema_pk=t1.schema_pk AND'
+                       . ' "table_type" = \'BASE TABLE\''
+                       . ' AND "schema_name" = current_schema';
+            case 'views':
+                return 'SELECT "table_name" FROM information_schema.tables'
+                       . ' t0, information_schema.schemata t1'
+                       . ' WHERE t0.schema_pk=t1.schema_pk AND'
+                       . ' "table_type" = \'VIEW\''
+                       . ' AND "schema_name" = current_schema';
+            case 'users':
+                return 'SELECT "user_name" from information_schema.users'; 
+            case 'functions':
+                return 'SELECT "routine_name" FROM'
+                       . ' information_schema.psm_routines'
+                       . ' t0, information_schema.schemata t1'
+                       . ' WHERE t0.schema_pk=t1.schema_pk'
+                       . ' AND "routine_kind"=\'FUNCTION\''
+                       . ' AND "schema_name" = current_schema';
+            case 'procedures':
+                return 'SELECT "routine_name" FROM'
+                       . ' information_schema.psm_routines'
+                       . ' t0, information_schema.schemata t1'
+                       . ' WHERE t0.schema_pk=t1.schema_pk'
+                       . ' AND "routine_kind"=\'PROCEDURE\''
+                       . ' AND "schema_name" = current_schema';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/ibase.php b/program/lib/DB/ibase.php
new file mode 100644 (file)
index 0000000..8b21a3c
--- /dev/null
@@ -0,0 +1,1071 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's interbase extension
+ * for interacting with Interbase and Firebird databases
+ *
+ * While this class works with PHP 4, PHP's InterBase extension is
+ * unstable in PHP 4.  Use PHP 5.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: ibase.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's interbase extension
+ * for interacting with Interbase and Firebird databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * While this class works with PHP 4, PHP's InterBase extension is
+ * unstable in PHP 4.  Use PHP 5.
+ *
+ * NOTICE:  limitQuery() only works for Firebird.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ * @since      Class became stable in Release 1.7.0
+ */
+class DB_ibase extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'ibase';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'ibase';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * NOTE: only firebird supports limit.
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => false,
+        'new_link'      => false,
+        'numrows'       => 'emulate',
+        'pconnect'      => true,
+        'prepare'       => true,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+        -104 => DB_ERROR_SYNTAX,
+        -150 => DB_ERROR_ACCESS_VIOLATION,
+        -151 => DB_ERROR_ACCESS_VIOLATION,
+        -155 => DB_ERROR_NOSUCHTABLE,
+        -157 => DB_ERROR_NOSUCHFIELD,
+        -158 => DB_ERROR_VALUE_COUNT_ON_ROW,
+        -170 => DB_ERROR_MISMATCH,
+        -171 => DB_ERROR_MISMATCH,
+        -172 => DB_ERROR_INVALID,
+        // -204 =>  // Covers too many errors, need to use regex on msg
+        -205 => DB_ERROR_NOSUCHFIELD,
+        -206 => DB_ERROR_NOSUCHFIELD,
+        -208 => DB_ERROR_INVALID,
+        -219 => DB_ERROR_NOSUCHTABLE,
+        -297 => DB_ERROR_CONSTRAINT,
+        -303 => DB_ERROR_INVALID,
+        -413 => DB_ERROR_INVALID_NUMBER,
+        -530 => DB_ERROR_CONSTRAINT,
+        -551 => DB_ERROR_ACCESS_VIOLATION,
+        -552 => DB_ERROR_ACCESS_VIOLATION,
+        // -607 =>  // Covers too many errors, need to use regex on msg
+        -625 => DB_ERROR_CONSTRAINT_NOT_NULL,
+        -803 => DB_ERROR_CONSTRAINT,
+        -804 => DB_ERROR_VALUE_COUNT_ON_ROW,
+        -904 => DB_ERROR_CONNECT_FAILED,
+        -922 => DB_ERROR_NOSUCHDB,
+        -923 => DB_ERROR_CONNECT_FAILED,
+        -924 => DB_ERROR_CONNECT_FAILED
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * The number of rows affected by a data manipulation query
+     * @var integer
+     * @access private
+     */
+    var $affected = 0;
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The prepared statement handle from the most recently executed statement
+     *
+     * {@internal  Mainly here because the InterBase/Firebird API is only
+     * able to retrieve data from result sets if the statemnt handle is
+     * still in scope.}}
+     *
+     * @var resource
+     */
+    var $last_stmt;
+
+    /**
+     * Is the given prepared statement a data manipulation query?
+     * @var array
+     * @access private
+     */
+    var $manip_query = array();
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_ibase()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's ibase driver supports the following extra DSN options:
+     *   + buffers    The number of database buffers to allocate for the
+     *                 server-side cache.
+     *   + charset    The default character set for a database.
+     *   + dialect    The default SQL dialect for any statement
+     *                 executed within a connection.  Defaults to the
+     *                 highest one supported by client libraries.
+     *                 Functional only with InterBase 6 and up.
+     *   + role       Functional only with InterBase 5 and up.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('interbase')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+        if ($this->dbsyntax == 'firebird') {
+            $this->features['limit'] = 'alter';
+        }
+
+        $params = array(
+            $dsn['hostspec']
+                    ? ($dsn['hostspec'] . ':' . $dsn['database'])
+                    : $dsn['database'],
+            $dsn['username'] ? $dsn['username'] : null,
+            $dsn['password'] ? $dsn['password'] : null,
+            isset($dsn['charset']) ? $dsn['charset'] : null,
+            isset($dsn['buffers']) ? $dsn['buffers'] : null,
+            isset($dsn['dialect']) ? $dsn['dialect'] : null,
+            isset($dsn['role'])    ? $dsn['role'] : null,
+        );
+
+        $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect';
+
+        $this->connection = @call_user_func_array($connect_function, $params);
+        if (!$this->connection) {
+            return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @ibase_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        $result = @ibase_query($this->connection, $query);
+
+        if (!$result) {
+            return $this->ibaseRaiseError();
+        }
+        if ($this->autocommit && $ismanip) {
+            @ibase_commit($this->connection);
+        }
+        if ($ismanip) {
+            $this->affected = $result;
+            return DB_OK;
+        } else {
+            $this->affected = 0;
+            return $result;
+        }
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * Only works with Firebird.
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        if ($this->dsn['dbsyntax'] == 'firebird') {
+            $query = preg_replace('/^([\s(])*SELECT/i',
+                                  "SELECT FIRST $count SKIP $from", $query);
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal ibase result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            if (function_exists('ibase_fetch_assoc')) {
+                $arr = @ibase_fetch_assoc($result);
+            } else {
+                $arr = get_object_vars(ibase_fetch_object($result));
+            }
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @ibase_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @ibase_free_result($result);
+    }
+
+    // }}}
+    // {{{ freeQuery()
+
+    function freeQuery($query)
+    {
+        @ibase_free_query($query);
+        return true;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (is_integer($this->affected)) {
+            return $this->affected;
+        }
+        return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @ibase_num_fields($result);
+        if (!$cols) {
+            return $this->ibaseRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ prepare()
+
+    /**
+     * Prepares a query for multiple execution with execute().
+     *
+     * prepare() requires a generic query as string like <code>
+     *    INSERT INTO numbers VALUES (?, ?, ?)
+     * </code>.  The <kbd>?</kbd> characters are placeholders.
+     *
+     * Three types of placeholders can be used:
+     *   + <kbd>?</kbd>  a quoted scalar value, i.e. strings, integers
+     *   + <kbd>!</kbd>  value is inserted 'as is'
+     *   + <kbd>&</kbd>  requires a file name.  The file's contents get
+     *                     inserted into the query (i.e. saving binary
+     *                     data in a db)
+     *
+     * Use backslashes to escape placeholder characters if you don't want
+     * them to be interpreted as placeholders.  Example: <code>
+     *    "UPDATE foo SET col=? WHERE col='over \& under'"
+     * </code>
+     *
+     * @param string $query query to be prepared
+     * @return mixed DB statement resource on success. DB_Error on failure.
+     */
+    function prepare($query)
+    {
+        $tokens   = preg_split('/((?<!\\\)[&?!])/', $query, -1,
+                               PREG_SPLIT_DELIM_CAPTURE);
+        $token    = 0;
+        $types    = array();
+        $newquery = '';
+
+        foreach ($tokens as $key => $val) {
+            switch ($val) {
+                case '?':
+                    $types[$token++] = DB_PARAM_SCALAR;
+                    break;
+                case '&':
+                    $types[$token++] = DB_PARAM_OPAQUE;
+                    break;
+                case '!':
+                    $types[$token++] = DB_PARAM_MISC;
+                    break;
+                default:
+                    $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
+                    $newquery .= $tokens[$key] . '?';
+            }
+        }
+
+        $newquery = substr($newquery, 0, -1);
+        $this->last_query = $query;
+        $newquery = $this->modifyQuery($newquery);
+        $stmt = @ibase_prepare($this->connection, $newquery);
+        $this->prepare_types[(int)$stmt] = $types;
+        $this->manip_query[(int)$stmt]   = DB::isManip($query);
+        return $stmt;
+    }
+
+    // }}}
+    // {{{ execute()
+
+    /**
+     * Executes a DB statement prepared with prepare().
+     *
+     * @param resource  $stmt  a DB statement resource returned from prepare()
+     * @param mixed  $data  array, string or numeric data to be used in
+     *                      execution of the statement.  Quantity of items
+     *                      passed must match quantity of placeholders in
+     *                      query:  meaning 1 for non-array items or the
+     *                      quantity of elements in the array.
+     * @return object  a new DB_Result or a DB_Error when fail
+     * @see DB_ibase::prepare()
+     * @access public
+     */
+    function &execute($stmt, $data = array())
+    {
+        $data = (array)$data;
+        $this->last_parameters = $data;
+
+        $types =& $this->prepare_types[(int)$stmt];
+        if (count($types) != count($data)) {
+            $tmp =& $this->raiseError(DB_ERROR_MISMATCH);
+            return $tmp;
+        }
+
+        $i = 0;
+        foreach ($data as $key => $value) {
+            if ($types[$i] == DB_PARAM_MISC) {
+                /*
+                 * ibase doesn't seem to have the ability to pass a
+                 * parameter along unchanged, so strip off quotes from start
+                 * and end, plus turn two single quotes to one single quote,
+                 * in order to avoid the quotes getting escaped by
+                 * ibase and ending up in the database.
+                 */
+                $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
+                $data[$key] = str_replace("''", "'", $data[$key]);
+            } elseif ($types[$i] == DB_PARAM_OPAQUE) {
+                $fp = @fopen($data[$key], 'rb');
+                if (!$fp) {
+                    $tmp =& $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
+                    return $tmp;
+                }
+                $data[$key] = fread($fp, filesize($data[$key]));
+                fclose($fp);
+            }
+            $i++;
+        }
+
+        array_unshift($data, $stmt);
+
+        $res = call_user_func_array('ibase_execute', $data);
+        if (!$res) {
+            $tmp =& $this->ibaseRaiseError();
+            return $tmp;
+        }
+        /* XXX need this?
+        if ($this->autocommit && $this->manip_query[(int)$stmt]) {
+            @ibase_commit($this->connection);
+        }*/
+        $this->last_stmt = $stmt;
+        if ($this->manip_query[(int)$stmt]) {
+            $tmp = DB_OK;
+        } else {
+            $tmp =& new DB_result($this, $res);
+        }
+        return $tmp;
+    }
+
+    /**
+     * Frees the internal resources associated with a prepared query
+     *
+     * @param resource $stmt           the prepared statement's PHP resource
+     * @param bool     $free_resource  should the PHP resource be freed too?
+     *                                  Use false if you need to get data
+     *                                  from the result set later.
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_ibase::prepare()
+     */
+    function freePrepared($stmt, $free_resource = true)
+    {
+        if (!is_resource($stmt)) {
+            return false;
+        }
+        if ($free_resource) {
+            @ibase_free_query($stmt);
+        }
+        unset($this->prepare_tokens[(int)$stmt]);
+        unset($this->prepare_types[(int)$stmt]);
+        unset($this->manip_query[(int)$stmt]);
+        return true;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        $this->autocommit = $onoff ? 1 : 0;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        return @ibase_commit($this->connection);
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        return @ibase_rollback($this->connection);
+    }
+
+    // }}}
+    // {{{ transactionInit()
+
+    function transactionInit($trans_args = 0)
+    {
+        return $trans_args
+                ? @ibase_trans($trans_args, $this->connection)
+                : @ibase_trans();
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_ibase::createSequence(), DB_ibase::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $sqn = strtoupper($this->getSequenceName($seq_name));
+        $repeat = 0;
+        do {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result =& $this->query("SELECT GEN_ID(${sqn}, 1) "
+                                   . 'FROM RDB$GENERATORS '
+                                   . "WHERE RDB\$GENERATOR_NAME='${sqn}'");
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result)) {
+                $repeat = 1;
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $result;
+                }
+            } else {
+                $repeat = 0;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+        $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        $result->free();
+        return $arr[0];
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_ibase::nextID(), DB_ibase::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $sqn = strtoupper($this->getSequenceName($seq_name));
+        $this->pushErrorHandling(PEAR_ERROR_RETURN);
+        $result = $this->query("CREATE GENERATOR ${sqn}");
+        $this->popErrorHandling();
+
+        return $result;
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_ibase::nextID(), DB_ibase::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DELETE FROM RDB$GENERATORS '
+                            . "WHERE RDB\$GENERATOR_NAME='"
+                            . strtoupper($this->getSequenceName($seq_name))
+                            . "'");
+    }
+
+    // }}}
+    // {{{ _ibaseFieldFlags()
+
+    /**
+     * Get the column's flags
+     *
+     * Supports "primary_key", "unique_key", "not_null", "default",
+     * "computed" and "blob".
+     *
+     * @param string $field_name  the name of the field
+     * @param string $table_name  the name of the table
+     *
+     * @return string  the flags
+     *
+     * @access private
+     */
+    function _ibaseFieldFlags($field_name, $table_name)
+    {
+        $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE'
+               .' FROM RDB$INDEX_SEGMENTS I'
+               .'  JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME'
+               .' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\''
+               .'  AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'';
+
+        $result = @ibase_query($this->connection, $sql);
+        if (!$result) {
+            return $this->ibaseRaiseError();
+        }
+
+        $flags = '';
+        if ($obj = @ibase_fetch_object($result)) {
+            @ibase_free_result($result);
+            if (isset($obj->CTYPE)  && trim($obj->CTYPE) == 'PRIMARY KEY') {
+                $flags .= 'primary_key ';
+            }
+            if (isset($obj->CTYPE)  && trim($obj->CTYPE) == 'UNIQUE') {
+                $flags .= 'unique_key ';
+            }
+        }
+
+        $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,'
+               .'  R.RDB$DEFAULT_SOURCE AS DSOURCE,'
+               .'  F.RDB$FIELD_TYPE AS FTYPE,'
+               .'  F.RDB$COMPUTED_SOURCE AS CSOURCE'
+               .' FROM RDB$RELATION_FIELDS R '
+               .'  JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME'
+               .' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''
+               .'  AND R.RDB$FIELD_NAME=\'' . $field_name . '\'';
+
+        $result = @ibase_query($this->connection, $sql);
+        if (!$result) {
+            return $this->ibaseRaiseError();
+        }
+        if ($obj = @ibase_fetch_object($result)) {
+            @ibase_free_result($result);
+            if (isset($obj->NFLAG)) {
+                $flags .= 'not_null ';
+            }
+            if (isset($obj->DSOURCE)) {
+                $flags .= 'default ';
+            }
+            if (isset($obj->CSOURCE)) {
+                $flags .= 'computed ';
+            }
+            if (isset($obj->FTYPE)  && $obj->FTYPE == 261) {
+                $flags .= 'blob ';
+            }
+        }
+
+        return trim($flags);
+    }
+
+    // }}}
+    // {{{ ibaseRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_ibase::errorNative(), DB_ibase::errorCode()
+     */
+    function &ibaseRaiseError($errno = null)
+    {
+        if ($errno === null) {
+            $errno = $this->errorCode($this->errorNative());
+        }
+        $tmp =& $this->raiseError($errno, null, null, null, @ibase_errmsg());
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return int  the DBMS' error code.  NULL if there is no error code.
+     *
+     * @since Method available since Release 1.7.0
+     */
+    function errorNative()
+    {
+        if (function_exists('ibase_errcode')) {
+            return @ibase_errcode();
+        }
+        if (preg_match('/^Dynamic SQL Error SQL error code = ([0-9-]+)/i',
+                       @ibase_errmsg(), $m)) {
+            return (int)$m[1];
+        }
+        return null;
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Maps native error codes to DB's portable ones
+     *
+     * @param int $nativecode  the error code returned by the DBMS
+     *
+     * @return int  the portable DB error code.  Return DB_ERROR if the
+     *               current driver doesn't have a mapping for the
+     *               $nativecode submitted.
+     *
+     * @since Method available since Release 1.7.0
+     */
+    function errorCode($nativecode = null)
+    {
+        if (isset($this->errorcode_map[$nativecode])) {
+            return $this->errorcode_map[$nativecode];
+        }
+
+        static $error_regexps;
+        if (!isset($error_regexps)) {
+            $error_regexps = array(
+                '/generator .* is not defined/'
+                    => DB_ERROR_SYNTAX,  // for compat. w ibase_errcode()
+                '/table.*(not exist|not found|unknown)/i'
+                    => DB_ERROR_NOSUCHTABLE,
+                '/table .* already exists/i'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/unsuccessful metadata update .* failed attempt to store duplicate value/i'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/unsuccessful metadata update .* not found/i'
+                    => DB_ERROR_NOT_FOUND,
+                '/validation error for column .* value "\*\*\* null/i'
+                    => DB_ERROR_CONSTRAINT_NOT_NULL,
+                '/violation of [\w ]+ constraint/i'
+                    => DB_ERROR_CONSTRAINT,
+                '/conversion error from string/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/no permission for/i'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/arithmetic exception, numeric overflow, or string truncation/i'
+                    => DB_ERROR_INVALID,
+            );
+        }
+
+        $errormsg = @ibase_errmsg();
+        foreach ($error_regexps as $regexp => $code) {
+            if (preg_match($regexp, $errormsg)) {
+                return $code;
+            }
+        }
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+     * is a table name.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @ibase_query($this->connection,
+                               "SELECT * FROM $result WHERE 1=0");
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @ibase_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $info = @ibase_field_info($id, $i);
+            $res[$i] = array(
+                'table' => $got_string ? $case_func($result) : '',
+                'name'  => $case_func($info['name']),
+                'type'  => $info['type'],
+                'len'   => $info['length'],
+                'flags' => ($got_string)
+                            ? $this->_ibaseFieldFlags($info['name'], $result)
+                            : '',
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @ibase_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM '
+                       . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0';
+            case 'views':
+                return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS';
+            case 'users':
+                return 'SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/ifx.php b/program/lib/DB/ifx.php
new file mode 100644 (file)
index 0000000..aa6747d
--- /dev/null
@@ -0,0 +1,681 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's ifx extension
+ * for interacting with Informix databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: ifx.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's ifx extension
+ * for interacting with Informix databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * More info on Informix errors can be found at:
+ * http://www.informix.com/answers/english/ierrors.htm
+ *
+ * TODO:
+ *   - set needed env Informix vars on connect
+ *   - implement native prepare/execute
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_ifx extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'ifx';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'ifx';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'emulate',
+        'new_link'      => false,
+        'numrows'       => 'emulate',
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+        '-201'    => DB_ERROR_SYNTAX,
+        '-206'    => DB_ERROR_NOSUCHTABLE,
+        '-217'    => DB_ERROR_NOSUCHFIELD,
+        '-236'    => DB_ERROR_VALUE_COUNT_ON_ROW,
+        '-239'    => DB_ERROR_CONSTRAINT,
+        '-253'    => DB_ERROR_SYNTAX,
+        '-292'    => DB_ERROR_CONSTRAINT_NOT_NULL,
+        '-310'    => DB_ERROR_ALREADY_EXISTS,
+        '-316'    => DB_ERROR_ALREADY_EXISTS,
+        '-319'    => DB_ERROR_NOT_FOUND,
+        '-329'    => DB_ERROR_NODBSELECTED,
+        '-346'    => DB_ERROR_CONSTRAINT,
+        '-386'    => DB_ERROR_CONSTRAINT_NOT_NULL,
+        '-391'    => DB_ERROR_CONSTRAINT_NOT_NULL,
+        '-554'    => DB_ERROR_SYNTAX,
+        '-691'    => DB_ERROR_CONSTRAINT,
+        '-692'    => DB_ERROR_CONSTRAINT,
+        '-703'    => DB_ERROR_CONSTRAINT_NOT_NULL,
+        '-1204'   => DB_ERROR_INVALID_DATE,
+        '-1205'   => DB_ERROR_INVALID_DATE,
+        '-1206'   => DB_ERROR_INVALID_DATE,
+        '-1209'   => DB_ERROR_INVALID_DATE,
+        '-1210'   => DB_ERROR_INVALID_DATE,
+        '-1212'   => DB_ERROR_INVALID_DATE,
+        '-1213'   => DB_ERROR_INVALID_NUMBER,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The number of rows affected by a data manipulation query
+     * @var integer
+     * @access private
+     */
+    var $affected = 0;
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_ifx()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('informix') &&
+            !PEAR::loadExtension('Informix'))
+        {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $dbhost = $dsn['hostspec'] ? '@' . $dsn['hostspec'] : '';
+        $dbname = $dsn['database'] ? $dsn['database'] . $dbhost : '';
+        $user = $dsn['username'] ? $dsn['username'] : '';
+        $pw = $dsn['password'] ? $dsn['password'] : '';
+
+        $connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect';
+
+        $this->connection = @$connect_function($dbname, $user, $pw);
+        if (!is_resource($this->connection)) {
+            return $this->ifxRaiseError(DB_ERROR_CONNECT_FAILED);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @ifx_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $this->affected   = null;
+        if (preg_match('/(SELECT)/i', $query)) {    //TESTME: Use !DB::isManip()?
+            // the scroll is needed for fetching absolute row numbers
+            // in a select query result
+            $result = @ifx_query($query, $this->connection, IFX_SCROLL);
+        } else {
+            if (!$this->autocommit && $ismanip) {
+                if ($this->transaction_opcount == 0) {
+                    $result = @ifx_query('BEGIN WORK', $this->connection);
+                    if (!$result) {
+                        return $this->ifxRaiseError();
+                    }
+                }
+                $this->transaction_opcount++;
+            }
+            $result = @ifx_query($query, $this->connection);
+        }
+        if (!$result) {
+            return $this->ifxRaiseError();
+        }
+        $this->affected = @ifx_affected_rows($result);
+        // Determine which queries should return data, and which
+        // should return an error code only.
+        if (preg_match('/(SELECT)/i', $query)) {
+            return $result;
+        }
+        // XXX Testme: free results inside a transaction
+        // may cause to stop it and commit the work?
+
+        // Result has to be freed even with a insert or update
+        @ifx_free_result($result);
+
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal ifx result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (DB::isManip($this->last_query)) {
+            return $this->affected;
+        } else {
+            return 0;
+        }
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if (($rownum !== null) && ($rownum < 0)) {
+            return null;
+        }
+        if ($rownum === null) {
+            /*
+             * Even though fetch_row() should return the next row  if
+             * $rownum is null, it doesn't in all cases.  Bug 598.
+             */
+            $rownum = 'NEXT';
+        } else {
+            // Index starts at row 1, unlike most DBMS's starting at 0.
+            $rownum++;
+        }
+        if (!$arr = @ifx_fetch_row($result, $rownum)) {
+            return null;
+        }
+        if ($fetchmode !== DB_FETCHMODE_ASSOC) {
+            $i=0;
+            $order = array();
+            foreach ($arr as $val) {
+                $order[$i++] = $val;
+            }
+            $arr = $order;
+        } elseif ($fetchmode == DB_FETCHMODE_ASSOC &&
+                  $this->options['portability'] & DB_PORTABILITY_LOWERCASE)
+        {
+            $arr = array_change_key_case($arr, CASE_LOWER);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        if (!$cols = @ifx_num_fields($result)) {
+            return $this->ifxRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @ifx_free_result($result);
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = true)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            $result = @ifx_query('COMMIT WORK', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->ifxRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            $result = @ifx_query('ROLLBACK WORK', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->ifxRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ ifxRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_ifx::errorNative(), DB_ifx::errorCode()
+     */
+    function ifxRaiseError($errno = null)
+    {
+        if ($errno === null) {
+            $errno = $this->errorCode(ifx_error());
+        }
+        return $this->raiseError($errno, null, null, null,
+                                 $this->errorNative());
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code and message produced by the last query
+     *
+     * @return string  the DBMS' error code and message
+     */
+    function errorNative()
+    {
+        return @ifx_error() . ' ' . @ifx_errormsg();
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Maps native error codes to DB's portable ones.
+     *
+     * Requires that the DB implementation's constructor fills
+     * in the <var>$errorcode_map</var> property.
+     *
+     * @param  string  $nativecode  error code returned by the database
+     * @return int a portable DB error code, or DB_ERROR if this DB
+     * implementation has no mapping for the given error code.
+     */
+    function errorCode($nativecode)
+    {
+        if (ereg('SQLCODE=(.*)]', $nativecode, $match)) {
+            $code = $match[1];
+            if (isset($this->errorcode_map[$code])) {
+                return $this->errorcode_map[$code];
+            }
+        }
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * NOTE: only supports 'table' if <var>$result</var> is a table name.
+     *
+     * If analyzing a query result and the result has duplicate field names,
+     * an error will be raised saying
+     * <samp>can't distinguish duplicate field names</samp>.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     * @since Method available since Release 1.6.0
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @ifx_query("SELECT * FROM $result WHERE 1=0",
+                             $this->connection);
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        $flds = @ifx_fieldproperties($id);
+        $count = @ifx_num_fields($id);
+
+        if (count($flds) != $count) {
+            return $this->raiseError("can't distinguish duplicate field names");
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $i   = 0;
+        $res = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        foreach ($flds as $key => $value) {
+            $props = explode(';', $value);
+            $res[$i] = array(
+                'table' => $got_string ? $case_func($result) : '',
+                'name'  => $case_func($key),
+                'type'  => $props[0],
+                'len'   => $props[1],
+                'flags' => $props[4] == 'N' ? 'not_null' : '',
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+            $i++;
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @ifx_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SELECT tabname FROM systables WHERE tabid >= 100';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/msql.php b/program/lib/DB/msql.php
new file mode 100644 (file)
index 0000000..76cd4e9
--- /dev/null
@@ -0,0 +1,810 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's msql extension
+ * for interacting with Mini SQL databases
+ *
+ * PHP's mSQL extension did weird things with NULL values prior to PHP
+ * 4.3.11 and 5.0.4.  Make sure your version of PHP meets or exceeds
+ * those versions.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: msql.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's msql extension
+ * for interacting with Mini SQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * PHP's mSQL extension did weird things with NULL values prior to PHP
+ * 4.3.11 and 5.0.4.  Make sure your version of PHP meets or exceeds
+ * those versions.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ * @since      Class not functional until Release 1.7.0
+ */
+class DB_msql extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'msql';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'msql';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'emulate',
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => false,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * The query result resource created by PHP
+     *
+     * Used to make affectedRows() work.  Only contains the result for
+     * data manipulation queries.  Contains false for other queries.
+     *
+     * @var resource
+     * @access private
+     */
+    var $_result;
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_msql()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * Example of how to connect:
+     * <code>
+     * require_once 'DB.php';
+     * 
+     * // $dsn = 'msql://hostname/dbname';  // use a TCP connection
+     * $dsn = 'msql:///dbname';             // use a socket
+     * $options = array(
+     *     'portability' => DB_PORTABILITY_ALL,
+     * );
+     * 
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('msql')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $params = array();
+        if ($dsn['hostspec']) {
+            $params[] = $dsn['port']
+                        ? $dsn['hostspec'] . ',' . $dsn['port']
+                        : $dsn['hostspec'];
+        }
+
+        $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect';
+
+        $ini = ini_get('track_errors');
+        $php_errormsg = '';
+        if ($ini) {
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+        } else {
+            ini_set('track_errors', 1);
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+            ini_set('track_errors', $ini);
+        }
+
+        if (!$this->connection) {
+            if (($err = @msql_error()) != '') {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $err);
+            } else {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $php_errormsg);
+            }
+        }
+
+        if (!@msql_select_db($dsn['database'], $this->connection)) {
+            return $this->msqlRaiseError();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @msql_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        $result = @msql_query($query, $this->connection);
+        if (!$result) {
+            return $this->msqlRaiseError();
+        }
+        // Determine which queries that should return data, and which
+        // should return an error code only.
+        if (DB::isManip($query)) {
+            $this->_result = $result;
+            return DB_OK;
+        } else {
+            $this->_result = false;
+            return $result;
+        }
+    }
+
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal msql result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * PHP's mSQL extension did weird things with NULL values prior to PHP
+     * 4.3.11 and 5.0.4.  Make sure your version of PHP meets or exceeds
+     * those versions.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@msql_data_seek($result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @msql_fetch_array($result, MSQL_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @msql_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @msql_free_result($result);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @msql_num_fields($result);
+        if (!$cols) {
+            return $this->msqlRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @msql_num_rows($result);
+        if ($rows === false) {
+            return $this->msqlRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ affected()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (!$this->_result) {
+            return 0;
+        }
+        return msql_affected_rows($this->_result);
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_msql::createSequence(), DB_msql::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $repeat = false;
+        do {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result =& $this->query("SELECT _seq FROM ${seqname}");
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+                $repeat = true;
+                $this->pushErrorHandling(PEAR_ERROR_RETURN);
+                $result = $this->createSequence($seq_name);
+                $this->popErrorHandling();
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+            } else {
+                $repeat = false;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+        $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        $result->free();
+        return $arr[0];
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * Also creates a new table to associate the sequence with.  Uses
+     * a separate table to ensure portability with other drivers.
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_msql::nextID(), DB_msql::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $res = $this->query('CREATE TABLE ' . $seqname
+                            . ' (id INTEGER NOT NULL)');
+        if (DB::isError($res)) {
+            return $res;
+        }
+        $res = $this->query("CREATE SEQUENCE ON ${seqname}");
+        return $res;
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_msql::nextID(), DB_msql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * mSQL does not support delimited identifiers
+     *
+     * @param string $str  the identifier name to be quoted
+     *
+     * @return object  a DB_Error object
+     *
+     * @see DB_common::quoteIdentifier()
+     * @since Method available since Release 1.7.0
+     */
+    function quoteIdentifier($str)
+    {
+        return $this->raiseError(DB_ERROR_UNSUPPORTED);
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.7.0
+     */
+    function escapeSimple($str)
+    {
+        return addslashes($str);
+    }
+
+    // }}}
+    // {{{ msqlRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_msql::errorNative(), DB_msql::errorCode()
+     */
+    function msqlRaiseError($errno = null)
+    {
+        $native = $this->errorNative();
+        if ($errno === null) {
+            $errno = $this->errorCode($native);
+        }
+        return $this->raiseError($errno, null, null, null, $native);
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error message produced by the last query
+     *
+     * @return string  the DBMS' error message
+     */
+    function errorNative()
+    {
+        return @msql_error();
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Determines PEAR::DB error code from the database's text error message
+     *
+     * @param string $errormsg  the error message returned from the database
+     *
+     * @return integer  the error number from a DB_ERROR* constant
+     */
+    function errorCode($errormsg)
+    {
+        static $error_regexps;
+        if (!isset($error_regexps)) {
+            $error_regexps = array(
+                '/^Access to database denied/i'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/^Bad index name/i'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/^Bad order field/i'
+                    => DB_ERROR_SYNTAX,
+                '/^Bad type for comparison/i'
+                    => DB_ERROR_SYNTAX,
+                '/^Can\'t perform LIKE on/i'
+                    => DB_ERROR_SYNTAX,
+                '/^Can\'t use TEXT fields in LIKE comparison/i'
+                    => DB_ERROR_SYNTAX,
+                '/^Couldn\'t create temporary table/i'
+                    => DB_ERROR_CANNOT_CREATE,
+                '/^Error creating table file/i'
+                    => DB_ERROR_CANNOT_CREATE,
+                '/^Field .* cannot be null$/i'
+                    => DB_ERROR_CONSTRAINT_NOT_NULL,
+                '/^Index (field|condition) .* cannot be null$/i'
+                    => DB_ERROR_SYNTAX,
+                '/^Invalid date format/i'
+                    => DB_ERROR_INVALID_DATE,
+                '/^Invalid time format/i'
+                    => DB_ERROR_INVALID,
+                '/^Literal value for .* is wrong type$/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/^No Database Selected/i'
+                    => DB_ERROR_NODBSELECTED,
+                '/^No value specified for field/i'
+                    => DB_ERROR_VALUE_COUNT_ON_ROW,
+                '/^Non unique value for unique index/i'
+                    => DB_ERROR_CONSTRAINT,
+                '/^Out of memory for temporary table/i'
+                    => DB_ERROR_CANNOT_CREATE,
+                '/^Permission denied/i'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/^Reference to un-selected table/i'
+                    => DB_ERROR_SYNTAX,
+                '/^syntax error/i'
+                    => DB_ERROR_SYNTAX,
+                '/^Table .* exists$/i'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/^Unknown database/i'
+                    => DB_ERROR_NOSUCHDB,
+                '/^Unknown field/i'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/^Unknown (index|system variable)/i'
+                    => DB_ERROR_NOT_FOUND,
+                '/^Unknown table/i'
+                    => DB_ERROR_NOSUCHTABLE,
+                '/^Unqualified field/i'
+                    => DB_ERROR_SYNTAX,
+            );
+        }
+
+        foreach ($error_regexps as $regexp => $code) {
+            if (preg_match($regexp, $errormsg)) {
+                return $code;
+            }
+        }
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::setOption()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @msql_query("SELECT * FROM $result",
+                              $this->connection);
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @msql_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $tmp = @msql_fetch_field($id);
+
+            $flags = '';
+            if ($tmp->not_null) {
+                $flags .= 'not_null ';
+            }
+            if ($tmp->unique) {
+                $flags .= 'unique_key ';
+            }
+            $flags = trim($flags);
+
+            $res[$i] = array(
+                'table' => $case_func($tmp->table),
+                'name'  => $case_func($tmp->name),
+                'type'  => $tmp->type,
+                'len'   => msql_field_len($id, $i),
+                'flags' => $flags,
+            );
+
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @msql_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtain a list of a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return array  the array containing the list of objects requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'databases':
+                $id = @msql_list_dbs($this->connection);
+                break;
+            case 'tables':
+                $id = @msql_list_tables($this->dsn['database'],
+                                        $this->connection);
+                break;
+            default:
+                return null;
+        }
+        if (!$id) {
+            return $this->msqlRaiseError();
+        }
+        $out = array();
+        while ($row = @msql_fetch_row($id)) {
+            $out[] = $row[0];
+        }
+        return $out;
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/mssql.php b/program/lib/DB/mssql.php
new file mode 100644 (file)
index 0000000..74f8cf4
--- /dev/null
@@ -0,0 +1,914 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's mssql extension
+ * for interacting with Microsoft SQL Server databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: mssql.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's mssql extension
+ * for interacting with Microsoft SQL Server databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_mssql extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'mssql';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'mssql';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'emulate',
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX
+    var $errorcode_map = array(
+        110   => DB_ERROR_VALUE_COUNT_ON_ROW,
+        155   => DB_ERROR_NOSUCHFIELD,
+        170   => DB_ERROR_SYNTAX,
+        207   => DB_ERROR_NOSUCHFIELD,
+        208   => DB_ERROR_NOSUCHTABLE,
+        245   => DB_ERROR_INVALID_NUMBER,
+        515   => DB_ERROR_CONSTRAINT_NOT_NULL,
+        547   => DB_ERROR_CONSTRAINT,
+        1913  => DB_ERROR_ALREADY_EXISTS,
+        2627  => DB_ERROR_CONSTRAINT,
+        2714  => DB_ERROR_ALREADY_EXISTS,
+        3701  => DB_ERROR_NOSUCHTABLE,
+        8134  => DB_ERROR_DIVZERO,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The database specified in the DSN
+     *
+     * It's a fix to allow calls to different databases in the same script.
+     *
+     * @var string
+     * @access private
+     */
+    var $_db = null;
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_mssql()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase')
+            && !PEAR::loadExtension('sybase_ct'))
+        {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $params = array(
+            $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
+            $dsn['username'] ? $dsn['username'] : null,
+            $dsn['password'] ? $dsn['password'] : null,
+        );
+        if ($dsn['port']) {
+            $params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':')
+                        . $dsn['port'];
+        }
+
+        $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect';
+
+        $this->connection = @call_user_func_array($connect_function, $params);
+
+        if (!$this->connection) {
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     @mssql_get_last_message());
+        }
+        if ($dsn['database']) {
+            if (!@mssql_select_db($dsn['database'], $this->connection)) {
+                return $this->raiseError(DB_ERROR_NODBSELECTED,
+                                         null, null, null,
+                                         @mssql_get_last_message());
+            }
+            $this->_db = $dsn['database'];
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @mssql_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        if (!@mssql_select_db($this->_db, $this->connection)) {
+            return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+        }
+        $query = $this->modifyQuery($query);
+        if (!$this->autocommit && $ismanip) {
+            if ($this->transaction_opcount == 0) {
+                $result = @mssql_query('BEGIN TRAN', $this->connection);
+                if (!$result) {
+                    return $this->mssqlRaiseError();
+                }
+            }
+            $this->transaction_opcount++;
+        }
+        $result = @mssql_query($query, $this->connection);
+        if (!$result) {
+            return $this->mssqlRaiseError();
+        }
+        // Determine which queries that should return data, and which
+        // should return an error code only.
+        return $ismanip ? DB_OK : $result;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal mssql result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return @mssql_next_result($result);
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@mssql_data_seek($result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @mssql_fetch_array($result, MSSQL_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @mssql_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @mssql_free_result($result);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @mssql_num_fields($result);
+        if (!$cols) {
+            return $this->mssqlRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @mssql_num_rows($result);
+        if ($rows === false) {
+            return $this->mssqlRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            if (!@mssql_select_db($this->_db, $this->connection)) {
+                return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+            }
+            $result = @mssql_query('COMMIT TRAN', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mssqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            if (!@mssql_select_db($this->_db, $this->connection)) {
+                return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+            }
+            $result = @mssql_query('ROLLBACK TRAN', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mssqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (DB::isManip($this->last_query)) {
+            $res = @mssql_query('select @@rowcount', $this->connection);
+            if (!$res) {
+                return $this->mssqlRaiseError();
+            }
+            $ar = @mssql_fetch_row($res);
+            if (!$ar) {
+                $result = 0;
+            } else {
+                @mssql_free_result($res);
+                $result = $ar[0];
+            }
+        } else {
+            $result = 0;
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_mssql::createSequence(), DB_mssql::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        if (!@mssql_select_db($this->_db, $this->connection)) {
+            return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+        }
+        $repeat = 0;
+        do {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result) &&
+                ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
+            {
+                $repeat = 1;
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+            } elseif (!DB::isError($result)) {
+                $result =& $this->query("SELECT @@IDENTITY FROM $seqname");
+                $repeat = 0;
+            } else {
+                $repeat = false;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+        $result = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        return $result[0];
+    }
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_mssql::nextID(), DB_mssql::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        return $this->query('CREATE TABLE '
+                            . $this->getSequenceName($seq_name)
+                            . ' ([id] [int] IDENTITY (1, 1) NOT NULL,'
+                            . ' [vapor] [int] NULL)');
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_mssql::nextID(), DB_mssql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * Quotes a string so it can be safely used as a table or column name
+     *
+     * @param string $str  identifier name to be quoted
+     *
+     * @return string  quoted identifier string
+     *
+     * @see DB_common::quoteIdentifier()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteIdentifier($str)
+    {
+        return '[' . str_replace(']', ']]', $str) . ']';
+    }
+
+    // }}}
+    // {{{ mssqlRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_mssql::errorNative(), DB_mssql::errorCode()
+     */
+    function mssqlRaiseError($code = null)
+    {
+        $message = @mssql_get_last_message();
+        if (!$code) {
+            $code = $this->errorNative();
+        }
+        return $this->raiseError($this->errorCode($code, $message),
+                                 null, null, null, "$code - $message");
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return int  the DBMS' error code
+     */
+    function errorNative()
+    {
+        $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
+        if (!$res) {
+            return DB_ERROR;
+        }
+        $row = @mssql_fetch_row($res);
+        return $row[0];
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Determines PEAR::DB error code from mssql's native codes.
+     *
+     * If <var>$nativecode</var> isn't known yet, it will be looked up.
+     *
+     * @param  mixed  $nativecode  mssql error code, if known
+     * @return integer  an error number from a DB error constant
+     * @see errorNative()
+     */
+    function errorCode($nativecode = null, $msg = '')
+    {
+        if (!$nativecode) {
+            $nativecode = $this->errorNative();
+        }
+        if (isset($this->errorcode_map[$nativecode])) {
+            if ($nativecode == 3701
+                && preg_match('/Cannot drop the index/i', $msg))
+            {
+                return DB_ERROR_NOT_FOUND;
+            }
+            return $this->errorcode_map[$nativecode];
+        } else {
+            return DB_ERROR;
+        }
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+     * is a table name.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            if (!@mssql_select_db($this->_db, $this->connection)) {
+                return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+            }
+            $id = @mssql_query("SELECT * FROM $result WHERE 1=0",
+                               $this->connection);
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @mssql_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table' => $got_string ? $case_func($result) : '',
+                'name'  => $case_func(@mssql_field_name($id, $i)),
+                'type'  => @mssql_field_type($id, $i),
+                'len'   => @mssql_field_length($id, $i),
+                // We only support flags for table
+                'flags' => $got_string
+                           ? $this->_mssql_field_flags($result,
+                                                       @mssql_field_name($id, $i))
+                           : '',
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @mssql_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ _mssql_field_flags()
+
+    /**
+     * Get a column's flags
+     *
+     * Supports "not_null", "primary_key",
+     * "auto_increment" (mssql identity), "timestamp" (mssql timestamp),
+     * "unique_key" (mssql unique index, unique check or primary_key) and
+     * "multiple_key" (multikey index)
+     *
+     * mssql timestamp is NOT similar to the mysql timestamp so this is maybe
+     * not useful at all - is the behaviour of mysql_field_flags that primary
+     * keys are alway unique? is the interpretation of multiple_key correct?
+     *
+     * @param string $table   the table name
+     * @param string $column  the field name
+     *
+     * @return string  the flags
+     *
+     * @access private
+     * @author Joern Barthel <j_barthel@web.de>
+     */
+    function _mssql_field_flags($table, $column)
+    {
+        static $tableName = null;
+        static $flags = array();
+
+        if ($table != $tableName) {
+
+            $flags = array();
+            $tableName = $table;
+
+            // get unique and primary keys
+            $res = $this->getAll("EXEC SP_HELPINDEX[$table]", DB_FETCHMODE_ASSOC);
+
+            foreach ($res as $val) {
+                $keys = explode(', ', $val['index_keys']);
+
+                if (sizeof($keys) > 1) {
+                    foreach ($keys as $key) {
+                        $this->_add_flag($flags[$key], 'multiple_key');
+                    }
+                }
+
+                if (strpos($val['index_description'], 'primary key')) {
+                    foreach ($keys as $key) {
+                        $this->_add_flag($flags[$key], 'primary_key');
+                    }
+                } elseif (strpos($val['index_description'], 'unique')) {
+                    foreach ($keys as $key) {
+                        $this->_add_flag($flags[$key], 'unique_key');
+                    }
+                }
+            }
+
+            // get auto_increment, not_null and timestamp
+            $res = $this->getAll("EXEC SP_COLUMNS[$table]", DB_FETCHMODE_ASSOC);
+
+            foreach ($res as $val) {
+                $val = array_change_key_case($val, CASE_LOWER);
+                if ($val['nullable'] == '0') {
+                    $this->_add_flag($flags[$val['column_name']], 'not_null');
+                }
+                if (strpos($val['type_name'], 'identity')) {
+                    $this->_add_flag($flags[$val['column_name']], 'auto_increment');
+                }
+                if (strpos($val['type_name'], 'timestamp')) {
+                    $this->_add_flag($flags[$val['column_name']], 'timestamp');
+                }
+            }
+        }
+
+        if (array_key_exists($column, $flags)) {
+            return(implode(' ', $flags[$column]));
+        }
+        return '';
+    }
+
+    // }}}
+    // {{{ _add_flag()
+
+    /**
+     * Adds a string to the flags array if the flag is not yet in there
+     * - if there is no flag present the array is created
+     *
+     * @param array  &$array  the reference to the flag-array
+     * @param string $value   the flag value
+     *
+     * @return void
+     *
+     * @access private
+     * @author Joern Barthel <j_barthel@web.de>
+     */
+    function _add_flag(&$array, $value)
+    {
+        if (!is_array($array)) {
+            $array = array($value);
+        } elseif (!in_array($value, $array)) {
+            array_push($array, $value);
+        }
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return "SELECT name FROM sysobjects WHERE type = 'U'"
+                       . ' ORDER BY name';
+            case 'views':
+                return "SELECT name FROM sysobjects WHERE type = 'V'";
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/mysql.php b/program/lib/DB/mysql.php
new file mode 100644 (file)
index 0000000..d443497
--- /dev/null
@@ -0,0 +1,1034 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's mysql extension
+ * for interacting with MySQL databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: mysql.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's mysql extension
+ * for interacting with MySQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_mysql extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'mysql';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'mysql';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => '4.2.0',
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+        1004 => DB_ERROR_CANNOT_CREATE,
+        1005 => DB_ERROR_CANNOT_CREATE,
+        1006 => DB_ERROR_CANNOT_CREATE,
+        1007 => DB_ERROR_ALREADY_EXISTS,
+        1008 => DB_ERROR_CANNOT_DROP,
+        1022 => DB_ERROR_ALREADY_EXISTS,
+        1044 => DB_ERROR_ACCESS_VIOLATION,
+        1046 => DB_ERROR_NODBSELECTED,
+        1048 => DB_ERROR_CONSTRAINT,
+        1049 => DB_ERROR_NOSUCHDB,
+        1050 => DB_ERROR_ALREADY_EXISTS,
+        1051 => DB_ERROR_NOSUCHTABLE,
+        1054 => DB_ERROR_NOSUCHFIELD,
+        1061 => DB_ERROR_ALREADY_EXISTS,
+        1062 => DB_ERROR_ALREADY_EXISTS,
+        1064 => DB_ERROR_SYNTAX,
+        1091 => DB_ERROR_NOT_FOUND,
+        1100 => DB_ERROR_NOT_LOCKED,
+        1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
+        1142 => DB_ERROR_ACCESS_VIOLATION,
+        1146 => DB_ERROR_NOSUCHTABLE,
+        1216 => DB_ERROR_CONSTRAINT,
+        1217 => DB_ERROR_CONSTRAINT,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The database specified in the DSN
+     *
+     * It's a fix to allow calls to different databases in the same script.
+     *
+     * @var string
+     * @access private
+     */
+    var $_db = '';
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_mysql()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's mysql driver supports the following extra DSN options:
+     *   + new_link      If set to true, causes subsequent calls to connect()
+     *                    to return a new connection link instead of the
+     *                    existing one.  WARNING: this is not portable to
+     *                    other DBMS's. Available since PEAR DB 1.7.0.
+     *   + client_flags  Any combination of MYSQL_CLIENT_* constants.
+     *                    Only used if PHP is at version 4.3.0 or greater.
+     *                    Available since PEAR DB 1.7.0.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('mysql')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $params = array();
+        if ($dsn['protocol'] && $dsn['protocol'] == 'unix') {
+            $params[0] = ':' . $dsn['socket'];
+        } else {
+            $params[0] = $dsn['hostspec'] ? $dsn['hostspec']
+                         : 'localhost';
+            if ($dsn['port']) {
+                $params[0] .= ':' . $dsn['port'];
+            }
+        }
+        $params[] = $dsn['username'] ? $dsn['username'] : null;
+        $params[] = $dsn['password'] ? $dsn['password'] : null;
+
+        if (!$persistent) {
+            if (isset($dsn['new_link'])
+                && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+            {
+                $params[] = true;
+            } else {
+                $params[] = false;
+            }
+        }
+        if (version_compare(phpversion(), '4.3.0', '>=')) {
+            $params[] = isset($dsn['client_flags'])
+                        ? $dsn['client_flags'] : null;
+        }
+
+        $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect';
+
+        $ini = ini_get('track_errors');
+        $php_errormsg = '';
+        if ($ini) {
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+        } else {
+            ini_set('track_errors', 1);
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+            ini_set('track_errors', $ini);
+        }
+
+        if (!$this->connection) {
+            if (($err = @mysql_error()) != '') {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null, 
+                                         $err);
+            } else {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $php_errormsg);
+            }
+        }
+
+        if ($dsn['database']) {
+            if (!@mysql_select_db($dsn['database'], $this->connection)) {
+                return $this->mysqlRaiseError();
+            }
+            $this->_db = $dsn['database'];
+        }
+
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @mysql_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * Generally uses mysql_query().  If you want to use
+     * mysql_unbuffered_query() set the "result_buffering" option to 0 using
+     * setOptions().  This option was added in Release 1.7.0.
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        if ($this->_db) {
+            if (!@mysql_select_db($this->_db, $this->connection)) {
+                return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+            }
+        }
+        if (!$this->autocommit && $ismanip) {
+            if ($this->transaction_opcount == 0) {
+                $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection);
+                $result = @mysql_query('BEGIN', $this->connection);
+                if (!$result) {
+                    return $this->mysqlRaiseError();
+                }
+            }
+            $this->transaction_opcount++;
+        }
+        if (!$this->options['result_buffering']) {
+            $result = @mysql_unbuffered_query($query, $this->connection);
+        } else {
+            $result = @mysql_query($query, $this->connection);
+        }
+        if (!$result) {
+            return $this->mysqlRaiseError();
+        }
+        if (is_resource($result)) {
+            return $result;
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal mysql result pointer to the next available result
+     *
+     * This method has not been implemented yet.
+     *
+     * @param a valid sql result resource
+     *
+     * @return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@mysql_data_seek($result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @mysql_fetch_array($result, MYSQL_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @mysql_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            /*
+             * Even though this DBMS already trims output, we do this because
+             * a field might have intentional whitespace at the end that
+             * gets removed by DB_PORTABILITY_RTRIM under another driver.
+             */
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @mysql_free_result($result);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @mysql_num_fields($result);
+        if (!$cols) {
+            return $this->mysqlRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @mysql_num_rows($result);
+        if ($rows === null) {
+            return $this->mysqlRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            if ($this->_db) {
+                if (!@mysql_select_db($this->_db, $this->connection)) {
+                    return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+                }
+            }
+            $result = @mysql_query('COMMIT', $this->connection);
+            $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mysqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            if ($this->_db) {
+                if (!@mysql_select_db($this->_db, $this->connection)) {
+                    return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+                }
+            }
+            $result = @mysql_query('ROLLBACK', $this->connection);
+            $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mysqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (DB::isManip($this->last_query)) {
+            return @mysql_affected_rows($this->connection);
+        } else {
+            return 0;
+        }
+     }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_mysql::createSequence(), DB_mysql::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        do {
+            $repeat = 0;
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query("UPDATE ${seqname} ".
+                                   'SET id=LAST_INSERT_ID(id+1)');
+            $this->popErrorHandling();
+            if ($result === DB_OK) {
+                // COMMON CASE
+                $id = @mysql_insert_id($this->connection);
+                if ($id != 0) {
+                    return $id;
+                }
+                // EMPTY SEQ TABLE
+                // Sequence table must be empty for some reason, so fill
+                // it and return 1 and obtain a user-level lock
+                $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                if ($result == 0) {
+                    // Failed to get the lock
+                    return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
+                }
+
+                // add the default value
+                $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+
+                // Release the lock
+                $result = $this->getOne('SELECT RELEASE_LOCK('
+                                        . "'${seqname}_lock')");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                // We know what the result will be, so no need to try again
+                return 1;
+
+            } elseif ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE)
+            {
+                // ONDEMAND TABLE CREATION
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                } else {
+                    $repeat = 1;
+                }
+
+            } elseif (DB::isError($result) &&
+                      $result->getCode() == DB_ERROR_ALREADY_EXISTS)
+            {
+                // BACKWARDS COMPAT
+                // see _BCsequence() comment
+                $result = $this->_BCsequence($seqname);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                $repeat = 1;
+            }
+        } while ($repeat);
+
+        return $this->raiseError($result);
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_mysql::nextID(), DB_mysql::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $res = $this->query('CREATE TABLE ' . $seqname
+                            . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'
+                            . ' PRIMARY KEY(id))');
+        if (DB::isError($res)) {
+            return $res;
+        }
+        // insert yields value 1, nextId call will generate ID 2
+        $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)");
+        if (DB::isError($res)) {
+            return $res;
+        }
+        // so reset to zero
+        return $this->query("UPDATE ${seqname} SET id = 0");
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_mysql::nextID(), DB_mysql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ _BCsequence()
+
+    /**
+     * Backwards compatibility with old sequence emulation implementation
+     * (clean up the dupes)
+     *
+     * @param string $seqname  the sequence name to clean up
+     *
+     * @return bool  true on success.  A DB_Error object on failure.
+     *
+     * @access private
+     */
+    function _BCsequence($seqname)
+    {
+        // Obtain a user-level lock... this will release any previous
+        // application locks, but unlike LOCK TABLES, it does not abort
+        // the current transaction and is much less frequently used.
+        $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+        if (DB::isError($result)) {
+            return $result;
+        }
+        if ($result == 0) {
+            // Failed to get the lock, can't do the conversion, bail
+            // with a DB_ERROR_NOT_LOCKED error
+            return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
+        }
+
+        $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
+        if (DB::isError($highest_id)) {
+            return $highest_id;
+        }
+        // This should kill all rows except the highest
+        // We should probably do something if $highest_id isn't
+        // numeric, but I'm at a loss as how to handle that...
+        $result = $this->query('DELETE FROM ' . $seqname
+                               . " WHERE id <> $highest_id");
+        if (DB::isError($result)) {
+            return $result;
+        }
+
+        // If another thread has been waiting for this lock,
+        // it will go thru the above procedure, but will have no
+        // real effect
+        $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
+        if (DB::isError($result)) {
+            return $result;
+        }
+        return true;
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * Quotes a string so it can be safely used as a table or column name
+     *
+     * MySQL can't handle the backtick character (<kbd>`</kbd>) in
+     * table or column names.
+     *
+     * @param string $str  identifier name to be quoted
+     *
+     * @return string  quoted identifier string
+     *
+     * @see DB_common::quoteIdentifier()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteIdentifier($str)
+    {
+        return '`' . $str . '`';
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * @deprecated  Deprecated in release 1.6.0
+     */
+    function quote($str)
+    {
+        return $this->quoteSmart($str);
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function escapeSimple($str)
+    {
+        if (function_exists('mysql_real_escape_string')) {
+            return @mysql_real_escape_string($str, $this->connection);
+        } else {
+            return @mysql_escape_string($str);
+        }
+    }
+
+    // }}}
+    // {{{ modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * This little hack lets you know how many rows were deleted
+     * when running a "DELETE FROM table" query.  Only implemented
+     * if the DB_PORTABILITY_DELETE_COUNT portability option is on.
+     *
+     * @param string $query  the query string to modify
+     *
+     * @return string  the modified query string
+     *
+     * @access protected
+     * @see DB_common::setOption()
+     */
+    function modifyQuery($query)
+    {
+        if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
+            // "DELETE FROM table" gives 0 affected rows in MySQL.
+            // This little hack lets you know how many rows were deleted.
+            if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
+                $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
+                                      'DELETE FROM \1 WHERE 1=1', $query);
+            }
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        if (DB::isManip($query)) {
+            return $query . " LIMIT $count";
+        } else {
+            return $query . " LIMIT $from, $count";
+        }
+    }
+
+    // }}}
+    // {{{ mysqlRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_mysql::errorNative(), DB_common::errorCode()
+     */
+    function mysqlRaiseError($errno = null)
+    {
+        if ($errno === null) {
+            if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
+                $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
+                $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
+                $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
+            } else {
+                // Doing this in case mode changes during runtime.
+                $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
+                $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
+                $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
+            }
+            $errno = $this->errorCode(mysql_errno($this->connection));
+        }
+        return $this->raiseError($errno, null, null, null,
+                                 @mysql_errno($this->connection) . ' ** ' .
+                                 @mysql_error($this->connection));
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return int  the DBMS' error code
+     */
+    function errorNative()
+    {
+        return @mysql_errno($this->connection);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @mysql_list_fields($this->dsn['database'],
+                                     $result, $this->connection);
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @mysql_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table' => $case_func(@mysql_field_table($id, $i)),
+                'name'  => $case_func(@mysql_field_name($id, $i)),
+                'type'  => @mysql_field_type($id, $i),
+                'len'   => @mysql_field_len($id, $i),
+                'flags' => @mysql_field_flags($id, $i),
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @mysql_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SHOW TABLES';
+            case 'users':
+                return 'SELECT DISTINCT User FROM mysql.user';
+            case 'databases':
+                return 'SHOW DATABASES';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/mysqli.php b/program/lib/DB/mysqli.php
new file mode 100644 (file)
index 0000000..8fca105
--- /dev/null
@@ -0,0 +1,1076 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's mysqli extension
+ * for interacting with MySQL databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: mysqli.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's mysqli extension
+ * for interacting with MySQL databases
+ *
+ * This is for MySQL versions 4.1 and above.  Requires PHP 5.
+ *
+ * Note that persistent connections no longer exist.
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ * @since      Class functional since Release 1.6.3
+ */
+class DB_mysqli extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'mysqli';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'mysqli';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => false,
+        'prepare'       => false,
+        'ssl'           => true,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+        1004 => DB_ERROR_CANNOT_CREATE,
+        1005 => DB_ERROR_CANNOT_CREATE,
+        1006 => DB_ERROR_CANNOT_CREATE,
+        1007 => DB_ERROR_ALREADY_EXISTS,
+        1008 => DB_ERROR_CANNOT_DROP,
+        1022 => DB_ERROR_ALREADY_EXISTS,
+        1044 => DB_ERROR_ACCESS_VIOLATION,
+        1046 => DB_ERROR_NODBSELECTED,
+        1048 => DB_ERROR_CONSTRAINT,
+        1049 => DB_ERROR_NOSUCHDB,
+        1050 => DB_ERROR_ALREADY_EXISTS,
+        1051 => DB_ERROR_NOSUCHTABLE,
+        1054 => DB_ERROR_NOSUCHFIELD,
+        1061 => DB_ERROR_ALREADY_EXISTS,
+        1062 => DB_ERROR_ALREADY_EXISTS,
+        1064 => DB_ERROR_SYNTAX,
+        1091 => DB_ERROR_NOT_FOUND,
+        1100 => DB_ERROR_NOT_LOCKED,
+        1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
+        1142 => DB_ERROR_ACCESS_VIOLATION,
+        1146 => DB_ERROR_NOSUCHTABLE,
+        1216 => DB_ERROR_CONSTRAINT,
+        1217 => DB_ERROR_CONSTRAINT,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The database specified in the DSN
+     *
+     * It's a fix to allow calls to different databases in the same script.
+     *
+     * @var string
+     * @access private
+     */
+    var $_db = '';
+
+    /**
+     * Array for converting MYSQLI_*_FLAG constants to text values
+     * @var    array
+     * @access public
+     * @since  Property available since Release 1.6.5
+     */
+    var $mysqli_flags = array(
+        MYSQLI_NOT_NULL_FLAG        => 'not_null',
+        MYSQLI_PRI_KEY_FLAG         => 'primary_key',
+        MYSQLI_UNIQUE_KEY_FLAG      => 'unique_key',
+        MYSQLI_MULTIPLE_KEY_FLAG    => 'multiple_key',
+        MYSQLI_BLOB_FLAG            => 'blob',
+        MYSQLI_UNSIGNED_FLAG        => 'unsigned',
+        MYSQLI_ZEROFILL_FLAG        => 'zerofill',
+        MYSQLI_AUTO_INCREMENT_FLAG  => 'auto_increment',
+        MYSQLI_TIMESTAMP_FLAG       => 'timestamp',
+        MYSQLI_SET_FLAG             => 'set',
+        // MYSQLI_NUM_FLAG             => 'numeric',  // unnecessary
+        // MYSQLI_PART_KEY_FLAG        => 'multiple_key',  // duplicatvie
+        MYSQLI_GROUP_FLAG           => 'group_by'
+    );
+
+    /**
+     * Array for converting MYSQLI_TYPE_* constants to text values
+     * @var    array
+     * @access public
+     * @since  Property available since Release 1.6.5
+     */
+    var $mysqli_types = array(
+        MYSQLI_TYPE_DECIMAL     => 'decimal',
+        MYSQLI_TYPE_TINY        => 'tinyint',
+        MYSQLI_TYPE_SHORT       => 'int',
+        MYSQLI_TYPE_LONG        => 'int',
+        MYSQLI_TYPE_FLOAT       => 'float',
+        MYSQLI_TYPE_DOUBLE      => 'double',
+        // MYSQLI_TYPE_NULL        => 'DEFAULT NULL',  // let flags handle it
+        MYSQLI_TYPE_TIMESTAMP   => 'timestamp',
+        MYSQLI_TYPE_LONGLONG    => 'bigint',
+        MYSQLI_TYPE_INT24       => 'mediumint',
+        MYSQLI_TYPE_DATE        => 'date',
+        MYSQLI_TYPE_TIME        => 'time',
+        MYSQLI_TYPE_DATETIME    => 'datetime',
+        MYSQLI_TYPE_YEAR        => 'year',
+        MYSQLI_TYPE_NEWDATE     => 'date',
+        MYSQLI_TYPE_ENUM        => 'enum',
+        MYSQLI_TYPE_SET         => 'set',
+        MYSQLI_TYPE_TINY_BLOB   => 'tinyblob',
+        MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob',
+        MYSQLI_TYPE_LONG_BLOB   => 'longblob',
+        MYSQLI_TYPE_BLOB        => 'blob',
+        MYSQLI_TYPE_VAR_STRING  => 'varchar',
+        MYSQLI_TYPE_STRING      => 'char',
+        MYSQLI_TYPE_GEOMETRY    => 'geometry',
+    );
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_mysqli()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's mysqli driver supports the following extra DSN options:
+     *   + When the 'ssl' $option passed to DB::connect() is true:
+     *     + key      The path to the key file.
+     *     + cert     The path to the certificate file.
+     *     + ca       The path to the certificate authority file.
+     *     + capath   The path to a directory that contains trusted SSL
+     *                 CA certificates in pem format.
+     *     + cipher   The list of allowable ciphers for SSL encryption.
+     *
+     * Example of how to connect using SSL:
+     * <code>
+     * require_once 'DB.php';
+     * 
+     * $dsn = array(
+     *     'phptype'  => 'mysqli',
+     *     'username' => 'someuser',
+     *     'password' => 'apasswd',
+     *     'hostspec' => 'localhost',
+     *     'database' => 'thedb',
+     *     'key'      => 'client-key.pem',
+     *     'cert'     => 'client-cert.pem',
+     *     'ca'       => 'cacert.pem',
+     *     'capath'   => '/path/to/ca/dir',
+     *     'cipher'   => 'AES',
+     * );
+     * 
+     * $options = array(
+     *     'ssl' => true,
+     * );
+     * 
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('mysqli')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $ini = ini_get('track_errors');
+        ini_set('track_errors', 1);
+        $php_errormsg = '';
+
+        if ($this->getOption('ssl') === true) {
+            $init = mysqli_init();
+            mysqli_ssl_set(
+                $init,
+                empty($dsn['key'])    ? null : $dsn['key'],
+                empty($dsn['cert'])   ? null : $dsn['cert'],
+                empty($dsn['ca'])     ? null : $dsn['ca'],
+                empty($dsn['capath']) ? null : $dsn['capath'],
+                empty($dsn['cipher']) ? null : $dsn['cipher']
+            );
+            if ($this->connection = @mysqli_real_connect(
+                    $init,
+                    $dsn['hostspec'],
+                    $dsn['username'],
+                    $dsn['password'],
+                    $dsn['database'],
+                    $dsn['port'],
+                    $dsn['socket']))
+            {
+                $this->connection = $init;
+            }
+        } else {
+            $this->connection = @mysqli_connect(
+                $dsn['hostspec'],
+                $dsn['username'],
+                $dsn['password'],
+                $dsn['database'],
+                $dsn['port'],
+                $dsn['socket']
+            );
+        }
+
+        ini_set('track_errors', $ini);
+
+        if (!$this->connection) {
+            if (($err = @mysqli_connect_error()) != '') {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $err);
+            } else {
+                return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                         null, null, null,
+                                         $php_errormsg);
+            }
+        }
+
+        if ($dsn['database']) {
+            $this->_db = $dsn['database'];
+        }
+
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @mysqli_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        if ($this->_db) {
+            if (!@mysqli_select_db($this->connection, $this->_db)) {
+                return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED);
+            }
+        }
+        if (!$this->autocommit && $ismanip) {
+            if ($this->transaction_opcount == 0) {
+                $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=0');
+                $result = @mysqli_query($this->connection, 'BEGIN');
+                if (!$result) {
+                    return $this->mysqliRaiseError();
+                }
+            }
+            $this->transaction_opcount++;
+        }
+        $result = @mysqli_query($this->connection, $query);
+        if (!$result) {
+            return $this->mysqliRaiseError();
+        }
+        if (is_object($result)) {
+            return $result;
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal mysql result pointer to the next available result.
+     *
+     * This method has not been implemented yet.
+     *
+     * @param resource $result a valid sql result resource
+     * @return false
+     * @access public
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@mysqli_data_seek($result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @mysqli_fetch_array($result, MYSQLI_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @mysqli_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            /*
+             * Even though this DBMS already trims output, we do this because
+             * a field might have intentional whitespace at the end that
+             * gets removed by DB_PORTABILITY_RTRIM under another driver.
+             */
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @mysqli_free_result($result);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @mysqli_num_fields($result);
+        if (!$cols) {
+            return $this->mysqliRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @mysqli_num_rows($result);
+        if ($rows === null) {
+            return $this->mysqliRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            if ($this->_db) {
+                if (!@mysqli_select_db($this->connection, $this->_db)) {
+                    return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED);
+                }
+            }
+            $result = @mysqli_query($this->connection, 'COMMIT');
+            $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1');
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mysqliRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            if ($this->_db) {
+                if (!@mysqli_select_db($this->connection, $this->_db)) {
+                    return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED);
+                }
+            }
+            $result = @mysqli_query($this->connection, 'ROLLBACK');
+            $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1');
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->mysqliRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (DB::isManip($this->last_query)) {
+            return @mysqli_affected_rows($this->connection);
+        } else {
+            return 0;
+        }
+     }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_mysqli::createSequence(), DB_mysqli::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        do {
+            $repeat = 0;
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query('UPDATE ' . $seqname
+                                   . ' SET id = LAST_INSERT_ID(id + 1)');
+            $this->popErrorHandling();
+            if ($result === DB_OK) {
+                // COMMON CASE
+                $id = @mysqli_insert_id($this->connection);
+                if ($id != 0) {
+                    return $id;
+                }
+
+                // EMPTY SEQ TABLE
+                // Sequence table must be empty for some reason,
+                // so fill it and return 1
+                // Obtain a user-level lock
+                $result = $this->getOne('SELECT GET_LOCK('
+                                        . "'${seqname}_lock', 10)");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                if ($result == 0) {
+                    return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED);
+                }
+
+                // add the default value
+                $result = $this->query('REPLACE INTO ' . $seqname
+                                       . ' (id) VALUES (0)');
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+
+                // Release the lock
+                $result = $this->getOne('SELECT RELEASE_LOCK('
+                                        . "'${seqname}_lock')");
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                // We know what the result will be, so no need to try again
+                return 1;
+
+            } elseif ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE)
+            {
+                // ONDEMAND TABLE CREATION
+                $result = $this->createSequence($seq_name);
+
+                // Since createSequence initializes the ID to be 1,
+                // we do not need to retrieve the ID again (or we will get 2)
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                } else {
+                    // First ID of a newly created sequence is 1
+                    return 1;
+                }
+
+            } elseif (DB::isError($result) &&
+                      $result->getCode() == DB_ERROR_ALREADY_EXISTS)
+            {
+                // BACKWARDS COMPAT
+                // see _BCsequence() comment
+                $result = $this->_BCsequence($seqname);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                $repeat = 1;
+            }
+        } while ($repeat);
+
+        return $this->raiseError($result);
+    }
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_mysqli::nextID(), DB_mysqli::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $res = $this->query('CREATE TABLE ' . $seqname
+                            . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'
+                            . ' PRIMARY KEY(id))');
+        if (DB::isError($res)) {
+            return $res;
+        }
+        // insert yields value 1, nextId call will generate ID 2
+        return $this->query("INSERT INTO ${seqname} (id) VALUES (0)");
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_mysql::nextID(), DB_mysql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ _BCsequence()
+
+    /**
+     * Backwards compatibility with old sequence emulation implementation
+     * (clean up the dupes)
+     *
+     * @param string $seqname  the sequence name to clean up
+     *
+     * @return bool  true on success.  A DB_Error object on failure.
+     *
+     * @access private
+     */
+    function _BCsequence($seqname)
+    {
+        // Obtain a user-level lock... this will release any previous
+        // application locks, but unlike LOCK TABLES, it does not abort
+        // the current transaction and is much less frequently used.
+        $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+        if (DB::isError($result)) {
+            return $result;
+        }
+        if ($result == 0) {
+            // Failed to get the lock, can't do the conversion, bail
+            // with a DB_ERROR_NOT_LOCKED error
+            return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED);
+        }
+
+        $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
+        if (DB::isError($highest_id)) {
+            return $highest_id;
+        }
+
+        // This should kill all rows except the highest
+        // We should probably do something if $highest_id isn't
+        // numeric, but I'm at a loss as how to handle that...
+        $result = $this->query('DELETE FROM ' . $seqname
+                               . " WHERE id <> $highest_id");
+        if (DB::isError($result)) {
+            return $result;
+        }
+
+        // If another thread has been waiting for this lock,
+        // it will go thru the above procedure, but will have no
+        // real effect
+        $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
+        if (DB::isError($result)) {
+            return $result;
+        }
+        return true;
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * Quotes a string so it can be safely used as a table or column name
+     *
+     * MySQL can't handle the backtick character (<kbd>`</kbd>) in
+     * table or column names.
+     *
+     * @param string $str  identifier name to be quoted
+     *
+     * @return string  quoted identifier string
+     *
+     * @see DB_common::quoteIdentifier()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteIdentifier($str)
+    {
+        return '`' . $str . '`';
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function escapeSimple($str)
+    {
+        return @mysqli_real_escape_string($this->connection, $str);
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        if (DB::isManip($query)) {
+            return $query . " LIMIT $count";
+        } else {
+            return $query . " LIMIT $from, $count";
+        }
+    }
+
+    // }}}
+    // {{{ mysqliRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_mysqli::errorNative(), DB_common::errorCode()
+     */
+    function mysqliRaiseError($errno = null)
+    {
+        if ($errno === null) {
+            if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
+                $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
+                $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
+                $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
+            } else {
+                // Doing this in case mode changes during runtime.
+                $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
+                $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
+                $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
+            }
+            $errno = $this->errorCode(mysqli_errno($this->connection));
+        }
+        return $this->raiseError($errno, null, null, null,
+                                 @mysqli_errno($this->connection) . ' ** ' .
+                                 @mysqli_error($this->connection));
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return int  the DBMS' error code
+     */
+    function errorNative()
+    {
+        return @mysqli_errno($this->connection);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::setOption()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @mysqli_query($this->connection,
+                                "SELECT * FROM $result LIMIT 0");
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_a($id, 'mysqli_result')) {
+            return $this->mysqliRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @mysqli_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $tmp = @mysqli_fetch_field($id);
+
+            $flags = '';
+            foreach ($this->mysqli_flags as $const => $means) {
+                if ($tmp->flags & $const) {
+                    $flags .= $means . ' ';
+                }
+            }
+            if ($tmp->def) {
+                $flags .= 'default_' . rawurlencode($tmp->def);
+            }
+            $flags = trim($flags);
+
+            $res[$i] = array(
+                'table' => $case_func($tmp->table),
+                'name'  => $case_func($tmp->name),
+                'type'  => isset($this->mysqli_types[$tmp->type])
+                                    ? $this->mysqli_types[$tmp->type]
+                                    : 'unknown',
+                'len'   => $tmp->max_length,
+                'flags' => $flags,
+            );
+
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @mysqli_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SHOW TABLES';
+            case 'users':
+                return 'SELECT DISTINCT User FROM mysql.user';
+            case 'databases':
+                return 'SHOW DATABASES';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/oci8.php b/program/lib/DB/oci8.php
new file mode 100644 (file)
index 0000000..116f08b
--- /dev/null
@@ -0,0 +1,1117 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's oci8 extension
+ * for interacting with Oracle databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     James L. Pine <jlp@valinux.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: oci8.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's oci8 extension
+ * for interacting with Oracle databases
+ *
+ * Definitely works with versions 8 and 9 of Oracle.
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * Be aware...  OCIError() only appears to return anything when given a
+ * statement, so functions return the generic DB_ERROR instead of more
+ * useful errors that have to do with feedback from the database.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     James L. Pine <jlp@valinux.com>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_oci8 extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'oci8';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'oci8';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => '5.0.0',
+        'numrows'       => 'subquery',
+        'pconnect'      => true,
+        'prepare'       => true,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+        1    => DB_ERROR_CONSTRAINT,
+        900  => DB_ERROR_SYNTAX,
+        904  => DB_ERROR_NOSUCHFIELD,
+        913  => DB_ERROR_VALUE_COUNT_ON_ROW,
+        921  => DB_ERROR_SYNTAX,
+        923  => DB_ERROR_SYNTAX,
+        942  => DB_ERROR_NOSUCHTABLE,
+        955  => DB_ERROR_ALREADY_EXISTS,
+        1400 => DB_ERROR_CONSTRAINT_NOT_NULL,
+        1401 => DB_ERROR_INVALID,
+        1407 => DB_ERROR_CONSTRAINT_NOT_NULL,
+        1418 => DB_ERROR_NOT_FOUND,
+        1476 => DB_ERROR_DIVZERO,
+        1722 => DB_ERROR_INVALID_NUMBER,
+        2289 => DB_ERROR_NOSUCHTABLE,
+        2291 => DB_ERROR_CONSTRAINT,
+        2292 => DB_ERROR_CONSTRAINT,
+        2449 => DB_ERROR_CONSTRAINT,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * Stores the $data passed to execute() in the oci8 driver
+     *
+     * Gets reset to array() when simpleQuery() is run.
+     *
+     * Needed in case user wants to call numRows() after prepare/execute
+     * was used.
+     *
+     * @var array
+     * @access private
+     */
+    var $_data = array();
+
+    /**
+     * The result or statement handle from the most recently executed query
+     * @var resource
+     */
+    var $last_stmt;
+
+    /**
+     * Is the given prepared statement a data manipulation query?
+     * @var array
+     * @access private
+     */
+    var $manip_query = array();
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_oci8()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * If PHP is at version 5.0.0 or greater:
+     *   + Generally, oci_connect() or oci_pconnect() are used.
+     *   + But if the new_link DSN option is set to true, oci_new_connect()
+     *     is used.
+     *
+     * When using PHP version 4.x, OCILogon() or OCIPLogon() are used.
+     *
+     * PEAR DB's oci8 driver supports the following extra DSN options:
+     *   + charset       The character set to be used on the connection.
+     *                    Only used if PHP is at version 5.0.0 or greater
+     *                    and the Oracle server is at 9.2 or greater.
+     *                    Available since PEAR DB 1.7.0.
+     *   + new_link      If set to true, causes subsequent calls to
+     *                    connect() to return a new connection link
+     *                    instead of the existing one.  WARNING: this is
+     *                    not portable to other DBMS's.
+     *                    Available since PEAR DB 1.7.0.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('oci8')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        if (function_exists('oci_connect')) {
+            if (isset($dsn['new_link'])
+                && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+            {
+                $connect_function = 'oci_new_connect';
+            } else {
+                $connect_function = $persistent ? 'oci_pconnect'
+                                    : 'oci_connect';
+            }
+
+            // Backwards compatibility with DB < 1.7.0
+            if (empty($dsn['database']) && !empty($dsn['hostspec'])) {
+                $db = $dsn['hostspec'];
+            } else {
+                $db = $dsn['database'];
+            }
+
+            $char = empty($dsn['charset']) ? null : $dsn['charset'];
+            $this->connection = @$connect_function($dsn['username'],
+                                                   $dsn['password'],
+                                                   $db,
+                                                   $char);
+            $error = OCIError();
+            if (!empty($error) && $error['code'] == 12541) {
+                // Couldn't find TNS listener.  Try direct connection.
+                $this->connection = @$connect_function($dsn['username'],
+                                                       $dsn['password'],
+                                                       null,
+                                                       $char);
+            }
+        } else {
+            $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon';
+            if ($dsn['hostspec']) {
+                $this->connection = @$connect_function($dsn['username'],
+                                                       $dsn['password'],
+                                                       $dsn['hostspec']);
+            } elseif ($dsn['username'] || $dsn['password']) {
+                $this->connection = @$connect_function($dsn['username'],
+                                                       $dsn['password']);
+            }
+        }
+
+        if (!$this->connection) {
+            $error = OCIError();
+            $error = (is_array($error)) ? $error['message'] : null;
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     $error);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        if (function_exists('oci_close')) {
+            $ret = @oci_close($this->connection);
+        } else {
+            $ret = @OCILogOff($this->connection);
+        }
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * To determine how many rows of a result set get buffered using
+     * ocisetprefetch(), see the "result_buffering" option in setOptions().
+     * This option was added in Release 1.7.0.
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $this->_data = array();
+        $this->last_parameters = array();
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        $result = @OCIParse($this->connection, $query);
+        if (!$result) {
+            return $this->oci8RaiseError();
+        }
+        if ($this->autocommit) {
+            $success = @OCIExecute($result,OCI_COMMIT_ON_SUCCESS);
+        } else {
+            $success = @OCIExecute($result,OCI_DEFAULT);
+        }
+        if (!$success) {
+            return $this->oci8RaiseError($result);
+        }
+        $this->last_stmt = $result;
+        if (DB::isManip($query)) {
+            return DB_OK;
+        } else {
+            @ocisetprefetch($result, $this->options['result_buffering']);
+            return $result;
+        }
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal oracle result pointer to the next available result
+     *
+     * @param a valid oci8 result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $moredata = @OCIFetchInto($result,$arr,OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE &&
+                $moredata)
+            {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $moredata = OCIFetchInto($result,$arr,OCI_RETURN_NULLS+OCI_RETURN_LOBS);
+        }
+        if (!$moredata) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @OCIFreeStatement($result);
+    }
+
+    /**
+     * Frees the internal resources associated with a prepared query
+     *
+     * @param resource $stmt           the prepared statement's resource
+     * @param bool     $free_resource  should the PHP resource be freed too?
+     *                                  Use false if you need to get data
+     *                                  from the result set later.
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_oci8::prepare()
+     */
+    function freePrepared($stmt, $free_resource = true)
+    {
+        if (!is_resource($stmt)) {
+            return false;
+        }
+        if ($free_resource) {
+            @ocifreestatement($stmt);
+        }
+        if (isset($this->prepare_types[(int)$stmt])) {
+            unset($this->prepare_types[(int)$stmt]);
+            unset($this->manip_query[(int)$stmt]);
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * Only works if the DB_PORTABILITY_NUMROWS portability option
+     * is turned on.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows(), DB_common::setOption()
+     */
+    function numRows($result)
+    {
+        // emulate numRows for Oracle.  yuck.
+        if ($this->options['portability'] & DB_PORTABILITY_NUMROWS &&
+            $result === $this->last_stmt)
+        {
+            $countquery = 'SELECT COUNT(*) FROM ('.$this->last_query.')';
+            $save_query = $this->last_query;
+            $save_stmt = $this->last_stmt;
+
+            if (count($this->_data)) {
+                $smt = $this->prepare('SELECT COUNT(*) FROM ('.$this->last_query.')');
+                $count = $this->execute($smt, $this->_data);
+            } else {
+                $count =& $this->query($countquery);
+            }
+
+            if (DB::isError($count) ||
+                DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED)))
+            {
+                $this->last_query = $save_query;
+                $this->last_stmt = $save_stmt;
+                return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+            }
+            return $row[0];
+        }
+        return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @OCINumCols($result);
+        if (!$cols) {
+            return $this->oci8RaiseError($result);
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ prepare()
+
+    /**
+     * Prepares a query for multiple execution with execute().
+     *
+     * With oci8, this is emulated.
+     *
+     * prepare() requires a generic query as string like <code>
+     *    INSERT INTO numbers VALUES (?, ?, ?)
+     * </code>.  The <kbd>?</kbd> characters are placeholders.
+     *
+     * Three types of placeholders can be used:
+     *   + <kbd>?</kbd>  a quoted scalar value, i.e. strings, integers
+     *   + <kbd>!</kbd>  value is inserted 'as is'
+     *   + <kbd>&</kbd>  requires a file name.  The file's contents get
+     *                     inserted into the query (i.e. saving binary
+     *                     data in a db)
+     *
+     * Use backslashes to escape placeholder characters if you don't want
+     * them to be interpreted as placeholders.  Example: <code>
+     *    "UPDATE foo SET col=? WHERE col='over \& under'"
+     * </code>
+     *
+     * @param string $query  the query to be prepared
+     *
+     * @return mixed  DB statement resource on success. DB_Error on failure.
+     *
+     * @see DB_oci8::execute()
+     */
+    function prepare($query)
+    {
+        $tokens   = preg_split('/((?<!\\\)[&?!])/', $query, -1,
+                               PREG_SPLIT_DELIM_CAPTURE);
+        $binds    = count($tokens) - 1;
+        $token    = 0;
+        $types    = array();
+        $newquery = '';
+
+        foreach ($tokens as $key => $val) {
+            switch ($val) {
+                case '?':
+                    $types[$token++] = DB_PARAM_SCALAR;
+                    unset($tokens[$key]);
+                    break;
+                case '&':
+                    $types[$token++] = DB_PARAM_OPAQUE;
+                    unset($tokens[$key]);
+                    break;
+                case '!':
+                    $types[$token++] = DB_PARAM_MISC;
+                    unset($tokens[$key]);
+                    break;
+                default:
+                    $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
+                    if ($key != $binds) {
+                        $newquery .= $tokens[$key] . ':bind' . $token;
+                    } else {
+                        $newquery .= $tokens[$key];
+                    }
+            }
+        }
+
+        $this->last_query = $query;
+        $newquery = $this->modifyQuery($newquery);
+        if (!$stmt = @OCIParse($this->connection, $newquery)) {
+            return $this->oci8RaiseError();
+        }
+        $this->prepare_types[(int)$stmt] = $types;
+        $this->manip_query[(int)$stmt] = DB::isManip($query);
+        return $stmt;
+    }
+
+    // }}}
+    // {{{ execute()
+
+    /**
+     * Executes a DB statement prepared with prepare().
+     *
+     * To determine how many rows of a result set get buffered using
+     * ocisetprefetch(), see the "result_buffering" option in setOptions().
+     * This option was added in Release 1.7.0.
+     *
+     * @param resource  $stmt  a DB statement resource returned from prepare()
+     * @param mixed  $data  array, string or numeric data to be used in
+     *                      execution of the statement.  Quantity of items
+     *                      passed must match quantity of placeholders in
+     *                      query:  meaning 1 for non-array items or the
+     *                      quantity of elements in the array.
+     *
+     * @return mixed  returns an oic8 result resource for successful SELECT
+     *                queries, DB_OK for other successful queries.
+     *                A DB error object is returned on failure.
+     *
+     * @see DB_oci8::prepare()
+     */
+    function &execute($stmt, $data = array())
+    {
+        $data = (array)$data;
+        $this->last_parameters = $data;
+        $this->_data = $data;
+
+        $types =& $this->prepare_types[(int)$stmt];
+        if (count($types) != count($data)) {
+            $tmp =& $this->raiseError(DB_ERROR_MISMATCH);
+            return $tmp;
+        }
+
+        $i = 0;
+        foreach ($data as $key => $value) {
+            if ($types[$i] == DB_PARAM_MISC) {
+                /*
+                 * Oracle doesn't seem to have the ability to pass a
+                 * parameter along unchanged, so strip off quotes from start
+                 * and end, plus turn two single quotes to one single quote,
+                 * in order to avoid the quotes getting escaped by
+                 * Oracle and ending up in the database.
+                 */
+                $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
+                $data[$key] = str_replace("''", "'", $data[$key]);
+            } elseif ($types[$i] == DB_PARAM_OPAQUE) {
+                $fp = @fopen($data[$key], 'rb');
+                if (!$fp) {
+                    $tmp =& $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
+                    return $tmp;
+                }
+                $data[$key] = fread($fp, filesize($data[$key]));
+                fclose($fp);
+            }
+            if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) {
+                $tmp = $this->oci8RaiseError($stmt);
+                return $tmp;
+            }
+            $i++;
+        }
+        if ($this->autocommit) {
+            $success = @OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS);
+        } else {
+            $success = @OCIExecute($stmt, OCI_DEFAULT);
+        }
+        if (!$success) {
+            $tmp = $this->oci8RaiseError($stmt);
+            return $tmp;
+        }
+        $this->last_stmt = $stmt;
+        if ($this->manip_query[(int)$stmt]) {
+            $tmp = DB_OK;
+        } else {
+            @ocisetprefetch($stmt, $this->options['result_buffering']);
+            $tmp =& new DB_result($this, $stmt);
+        }
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        $this->autocommit = (bool)$onoff;;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        $result = @OCICommit($this->connection);
+        if (!$result) {
+            return $this->oci8RaiseError();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        $result = @OCIRollback($this->connection);
+        if (!$result) {
+            return $this->oci8RaiseError();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if ($this->last_stmt === false) {
+            return $this->oci8RaiseError();
+        }
+        $result = @OCIRowCount($this->last_stmt);
+        if ($result === false) {
+            return $this->oci8RaiseError($this->last_stmt);
+        }
+        return $result;
+    }
+
+    // }}}
+    // {{{ modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle.
+     *
+     * @param string $query  the query string to modify
+     *
+     * @return string  the modified query string
+     *
+     * @access protected
+     */
+    function modifyQuery($query)
+    {
+        if (preg_match('/^\s*SELECT/i', $query) &&
+            !preg_match('/\sFROM\s/i', $query)) {
+            $query .= ' FROM dual';
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        // Let Oracle return the name of the columns instead of
+        // coding a "home" SQL parser
+
+        if (count($params)) {
+            $result = $this->prepare("SELECT * FROM ($query) "
+                                     . 'WHERE NULL = NULL');
+            $tmp =& $this->execute($result, $params);
+        } else {
+            $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL";
+
+            if (!$result = @OCIParse($this->connection, $q_fields)) {
+                $this->last_query = $q_fields;
+                return $this->oci8RaiseError();
+            }
+            if (!@OCIExecute($result, OCI_DEFAULT)) {
+                $this->last_query = $q_fields;
+                return $this->oci8RaiseError($result);
+            }
+        }
+
+        $ncols = OCINumCols($result);
+        $cols  = array();
+        for ( $i = 1; $i <= $ncols; $i++ ) {
+            $cols[] = '"' . OCIColumnName($result, $i) . '"';
+        }
+        $fields = implode(', ', $cols);
+        // XXX Test that (tip by John Lim)
+        //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) {
+        //    // Introduce the FIRST_ROWS Oracle query optimizer
+        //    $query = substr($query, strlen($match[0]), strlen($query));
+        //    $query = "SELECT /* +FIRST_ROWS */ " . $query;
+        //}
+
+        // Construct the query
+        // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2
+        // Perhaps this could be optimized with the use of Unions
+        $query = "SELECT $fields FROM".
+                 "  (SELECT rownum as linenum, $fields FROM".
+                 "      ($query)".
+                 '  WHERE rownum <= '. ($from + $count) .
+                 ') WHERE linenum >= ' . ++$from;
+        return $query;
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_oci8::createSequence(), DB_oci8::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $repeat = 0;
+        do {
+            $this->expectError(DB_ERROR_NOSUCHTABLE);
+            $result =& $this->query("SELECT ${seqname}.nextval FROM dual");
+            $this->popExpect();
+            if ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+                $repeat = 1;
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+            } else {
+                $repeat = 0;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+        $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        return $arr[0];
+    }
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_oci8::nextID(), DB_oci8::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        return $this->query('CREATE SEQUENCE '
+                            . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_oci8::nextID(), DB_oci8::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP SEQUENCE '
+                            . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ oci8RaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_oci8::errorNative(), DB_oci8::errorCode()
+     */
+    function oci8RaiseError($errno = null)
+    {
+        if ($errno === null) {
+            $error = @OCIError($this->connection);
+            return $this->raiseError($this->errorCode($error['code']),
+                                     null, null, null, $error['message']);
+        } elseif (is_resource($errno)) {
+            $error = @OCIError($errno);
+            return $this->raiseError($this->errorCode($error['code']),
+                                     null, null, null, $error['message']);
+        }
+        return $this->raiseError($this->errorCode($errno));
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code produced by the last query
+     *
+     * @return int  the DBMS' error code.  FALSE if the code could not be
+     *               determined
+     */
+    function errorNative()
+    {
+        if (is_resource($this->last_stmt)) {
+            $error = @OCIError($this->last_stmt);
+        } else {
+            $error = @OCIError($this->connection);
+        }
+        if (is_array($error)) {
+            return $error['code'];
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+     * is a table name.
+     *
+     * NOTE: flags won't contain index information.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $res = array();
+
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $result = strtoupper($result);
+            $q_fields = 'SELECT column_name, data_type, data_length, '
+                        . 'nullable '
+                        . 'FROM user_tab_columns '
+                        . "WHERE table_name='$result' ORDER BY column_id";
+
+            $this->last_query = $q_fields;
+
+            if (!$stmt = @OCIParse($this->connection, $q_fields)) {
+                return $this->oci8RaiseError(DB_ERROR_NEED_MORE_DATA);
+            }
+            if (!@OCIExecute($stmt, OCI_DEFAULT)) {
+                return $this->oci8RaiseError($stmt);
+            }
+
+            $i = 0;
+            while (@OCIFetch($stmt)) {
+                $res[$i] = array(
+                    'table' => $case_func($result),
+                    'name'  => $case_func(@OCIResult($stmt, 1)),
+                    'type'  => @OCIResult($stmt, 2),
+                    'len'   => @OCIResult($stmt, 3),
+                    'flags' => (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '',
+                );
+                if ($mode & DB_TABLEINFO_ORDER) {
+                    $res['order'][$res[$i]['name']] = $i;
+                }
+                if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                    $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+                }
+                $i++;
+            }
+
+            if ($mode) {
+                $res['num_fields'] = $i;
+            }
+            @OCIFreeStatement($stmt);
+
+        } else {
+            if (isset($result->result)) {
+                /*
+                 * Probably received a result object.
+                 * Extract the result resource identifier.
+                 */
+                $result = $result->result;
+            }
+
+            $res = array();
+
+            if ($result === $this->last_stmt) {
+                $count = @OCINumCols($result);
+                if ($mode) {
+                    $res['num_fields'] = $count;
+                }
+                for ($i = 0; $i < $count; $i++) {
+                    $res[$i] = array(
+                        'table' => '',
+                        'name'  => $case_func(@OCIColumnName($result, $i+1)),
+                        'type'  => @OCIColumnType($result, $i+1),
+                        'len'   => @OCIColumnSize($result, $i+1),
+                        'flags' => '',
+                    );
+                    if ($mode & DB_TABLEINFO_ORDER) {
+                        $res['order'][$res[$i]['name']] = $i;
+                    }
+                    if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                        $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+                    }
+                }
+            } else {
+                return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+            }
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SELECT table_name FROM user_tables';
+            case 'synonyms':
+                return 'SELECT synonym_name FROM user_synonyms';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/odbc.php b/program/lib/DB/odbc.php
new file mode 100644 (file)
index 0000000..e4d6037
--- /dev/null
@@ -0,0 +1,883 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's odbc extension
+ * for interacting with databases via ODBC connections
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: odbc.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's odbc extension
+ * for interacting with databases via ODBC connections
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * More info on ODBC errors could be found here:
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_odbc extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'odbc';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'sql92';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * NOTE: The feature set of the following drivers are different than
+     * the default:
+     *   + solid: 'transactions' = true
+     *   + navision: 'limit' = false
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'emulate',
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => false,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+        '01004' => DB_ERROR_TRUNCATED,
+        '07001' => DB_ERROR_MISMATCH,
+        '21S01' => DB_ERROR_VALUE_COUNT_ON_ROW,
+        '21S02' => DB_ERROR_MISMATCH,
+        '22001' => DB_ERROR_INVALID,
+        '22003' => DB_ERROR_INVALID_NUMBER,
+        '22005' => DB_ERROR_INVALID_NUMBER,
+        '22008' => DB_ERROR_INVALID_DATE,
+        '22012' => DB_ERROR_DIVZERO,
+        '23000' => DB_ERROR_CONSTRAINT,
+        '23502' => DB_ERROR_CONSTRAINT_NOT_NULL,
+        '23503' => DB_ERROR_CONSTRAINT,
+        '23504' => DB_ERROR_CONSTRAINT,
+        '23505' => DB_ERROR_CONSTRAINT,
+        '24000' => DB_ERROR_INVALID,
+        '34000' => DB_ERROR_INVALID,
+        '37000' => DB_ERROR_SYNTAX,
+        '42000' => DB_ERROR_SYNTAX,
+        '42601' => DB_ERROR_SYNTAX,
+        'IM001' => DB_ERROR_UNSUPPORTED,
+        'S0000' => DB_ERROR_NOSUCHTABLE,
+        'S0001' => DB_ERROR_ALREADY_EXISTS,
+        'S0002' => DB_ERROR_NOSUCHTABLE,
+        'S0011' => DB_ERROR_ALREADY_EXISTS,
+        'S0012' => DB_ERROR_NOT_FOUND,
+        'S0021' => DB_ERROR_ALREADY_EXISTS,
+        'S0022' => DB_ERROR_NOSUCHFIELD,
+        'S1009' => DB_ERROR_INVALID,
+        'S1090' => DB_ERROR_INVALID,
+        'S1C00' => DB_ERROR_NOT_CAPABLE,
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * The number of rows affected by a data manipulation query
+     * @var integer
+     * @access private
+     */
+    var $affected = 0;
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_odbc()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's odbc driver supports the following extra DSN options:
+     *   + cursor  The type of cursor to be used for this connection.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('odbc')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+        switch ($this->dbsyntax) {
+            case 'access':
+            case 'db2':
+            case 'solid':
+                $this->features['transactions'] = true;
+                break;
+            case 'navision':
+                $this->features['limit'] = false;
+        }
+
+        /*
+         * This is hear for backwards compatibility. Should have been using
+         * 'database' all along, but prior to 1.6.0RC3 'hostspec' was used.
+         */
+        if ($dsn['database']) {
+            $odbcdsn = $dsn['database'];
+        } elseif ($dsn['hostspec']) {
+            $odbcdsn = $dsn['hostspec'];
+        } else {
+            $odbcdsn = 'localhost';
+        }
+
+        $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect';
+
+        if (empty($dsn['cursor'])) {
+            $this->connection = @$connect_function($odbcdsn, $dsn['username'],
+                                                   $dsn['password']);
+        } else {
+            $this->connection = @$connect_function($odbcdsn, $dsn['username'],
+                                                   $dsn['password'],
+                                                   $dsn['cursor']);
+        }
+
+        if (!is_resource($this->connection)) {
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     $this->errorNative());
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $err = @odbc_close($this->connection);
+        $this->connection = null;
+        return $err;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        $result = @odbc_exec($this->connection, $query);
+        if (!$result) {
+            return $this->odbcRaiseError(); // XXX ERRORMSG
+        }
+        // Determine which queries that should return data, and which
+        // should return an error code only.
+        if (DB::isManip($query)) {
+            $this->affected = $result; // For affectedRows()
+            return DB_OK;
+        }
+        $this->affected = 0;
+        return $result;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal odbc result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return @odbc_next_result($result);
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        $arr = array();
+        if ($rownum !== null) {
+            $rownum++; // ODBC first row is 1
+            if (version_compare(phpversion(), '4.2.0', 'ge')) {
+                $cols = @odbc_fetch_into($result, $arr, $rownum);
+            } else {
+                $cols = @odbc_fetch_into($result, $rownum, $arr);
+            }
+        } else {
+            $cols = @odbc_fetch_into($result, $arr);
+        }
+        if (!$cols) {
+            return null;
+        }
+        if ($fetchmode !== DB_FETCHMODE_ORDERED) {
+            for ($i = 0; $i < count($arr); $i++) {
+                $colName = @odbc_field_name($result, $i+1);
+                $a[$colName] = $arr[$i];
+            }
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+                $a = array_change_key_case($a, CASE_LOWER);
+            }
+            $arr = $a;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @odbc_free_result($result);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @odbc_num_fields($result);
+        if (!$cols) {
+            return $this->odbcRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (empty($this->affected)) {  // In case of SELECT stms
+            return 0;
+        }
+        $nrows = @odbc_num_rows($this->affected);
+        if ($nrows == -1) {
+            return $this->odbcRaiseError();
+        }
+        return $nrows;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * Not all ODBC drivers support this functionality.  If they don't
+     * a DB_Error object for DB_ERROR_UNSUPPORTED is returned.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $nrows = @odbc_num_rows($result);
+        if ($nrows == -1) {
+            return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED);
+        }
+        if ($nrows === false) {
+            return $this->odbcRaiseError();
+        }
+        return $nrows;
+    }
+
+    // }}}
+    // {{{ quoteIdentifier()
+
+    /**
+     * Quotes a string so it can be safely used as a table or column name
+     *
+     * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked
+     * "Use ANSI quoted identifiers" when setting up the ODBC data source.
+     *
+     * @param string $str  identifier name to be quoted
+     *
+     * @return string  quoted identifier string
+     *
+     * @see DB_common::quoteIdentifier()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteIdentifier($str)
+    {
+        switch ($this->dsn['dbsyntax']) {
+            case 'access':
+                return '[' . $str . ']';
+            case 'mssql':
+            case 'sybase':
+                return '[' . str_replace(']', ']]', $str) . ']';
+            case 'mysql':
+            case 'mysqli':
+                return '`' . $str . '`';
+            default:
+                return '"' . str_replace('"', '""', $str) . '"';
+        }
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * @deprecated  Deprecated in release 1.6.0
+     * @internal
+     */
+    function quote($str)
+    {
+        return $this->quoteSmart($str);
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_odbc::createSequence(), DB_odbc::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $repeat = 0;
+        do {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query("update ${seqname} set id = id + 1");
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+                $repeat = 1;
+                $this->pushErrorHandling(PEAR_ERROR_RETURN);
+                $result = $this->createSequence($seq_name);
+                $this->popErrorHandling();
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+                $result = $this->query("insert into ${seqname} (id) values(0)");
+            } else {
+                $repeat = 0;
+            }
+        } while ($repeat);
+
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+
+        $result = $this->query("select id from ${seqname}");
+        if (DB::isError($result)) {
+            return $result;
+        }
+
+        $row = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        if (DB::isError($row || !$row)) {
+            return $row;
+        }
+
+        return $row[0];
+    }
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_odbc::nextID(), DB_odbc::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        return $this->query('CREATE TABLE '
+                            . $this->getSequenceName($seq_name)
+                            . ' (id integer NOT NULL,'
+                            . ' PRIMARY KEY(id))');
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_odbc::nextID(), DB_odbc::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        if (!@odbc_autocommit($this->connection, $onoff)) {
+            return $this->odbcRaiseError();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if (!@odbc_commit($this->connection)) {
+            return $this->odbcRaiseError();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if (!@odbc_rollback($this->connection)) {
+            return $this->odbcRaiseError();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ odbcRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_odbc::errorNative(), DB_common::errorCode()
+     */
+    function odbcRaiseError($errno = null)
+    {
+        if ($errno === null) {
+            switch ($this->dbsyntax) {
+                case 'access':
+                    if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
+                        $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD;
+                    } else {
+                        // Doing this in case mode changes during runtime.
+                        $this->errorcode_map['07001'] = DB_ERROR_MISMATCH;
+                    }
+
+                    $native_code = odbc_error($this->connection);
+
+                    // S1000 is for "General Error."  Let's be more specific.
+                    if ($native_code == 'S1000') {
+                        $errormsg = odbc_errormsg($this->connection);
+                        static $error_regexps;
+                        if (!isset($error_regexps)) {
+                            $error_regexps = array(
+                                '/includes related records.$/i'  => DB_ERROR_CONSTRAINT,
+                                '/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL,
+                            );
+                        }
+                        foreach ($error_regexps as $regexp => $code) {
+                            if (preg_match($regexp, $errormsg)) {
+                                return $this->raiseError($code,
+                                        null, null, null,
+                                        $native_code . ' ' . $errormsg);
+                            }
+                        }
+                        $errno = DB_ERROR;
+                    } else {
+                        $errno = $this->errorCode($native_code);
+                    }
+                    break;
+                default:
+                    $errno = $this->errorCode(odbc_error($this->connection));
+            }
+        }
+        return $this->raiseError($errno, null, null, null,
+                                 $this->errorNative());
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error code and message produced by the last query
+     *
+     * @return string  the DBMS' error code and message
+     */
+    function errorNative()
+    {
+        if (!is_resource($this->connection)) {
+            return @odbc_error() . ' ' . @odbc_errormsg();
+        }
+        return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection);
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     * @since Method available since Release 1.7.0
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @odbc_exec($this->connection, "SELECT * FROM $result");
+            if (!$id) {
+                return $this->odbcRaiseError();
+            }
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->odbcRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @odbc_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $col = $i + 1;
+            $res[$i] = array(
+                'table' => $got_string ? $case_func($result) : '',
+                'name'  => $case_func(@odbc_field_name($id, $col)),
+                'type'  => @odbc_field_type($id, $col),
+                'len'   => @odbc_field_len($id, $col),
+                'flags' => '',
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @odbc_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com.
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the list of objects requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     * @since Method available since Release 1.7.0
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'databases':
+                if (!function_exists('odbc_data_source')) {
+                    return null;
+                }
+                $res = @odbc_data_source($this->connection, SQL_FETCH_FIRST);
+                if (is_array($res)) {
+                    $out = array($res['server']);
+                    while($res = @odbc_data_source($this->connection,
+                                                   SQL_FETCH_NEXT))
+                    {
+                        $out[] = $res['server'];
+                    }
+                    return $out;
+                } else {
+                    return $this->odbcRaiseError();
+                }
+                break;
+            case 'tables':
+            case 'schema.tables':
+                $keep = 'TABLE';
+                break;
+            case 'views':
+                $keep = 'VIEW';
+                break;
+            default:
+                return null;
+        }
+
+        /*
+         * Removing non-conforming items in the while loop rather than
+         * in the odbc_tables() call because some backends choke on this:
+         *     odbc_tables($this->connection, '', '', '', 'TABLE')
+         */
+        $res  = @odbc_tables($this->connection);
+        if (!$res) {
+            return $this->odbcRaiseError();
+        }
+        $out = array();
+        while ($row = odbc_fetch_array($res)) {
+            if ($row['TABLE_TYPE'] != $keep) {
+                continue;
+            }
+            if ($type == 'schema.tables') {
+                $out[] = $row['TABLE_SCHEM'] . '.' . $row['TABLE_NAME'];
+            } else {
+                $out[] = $row['TABLE_NAME'];
+            }
+        }
+        return $out;
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/pgsql.php b/program/lib/DB/pgsql.php
new file mode 100644 (file)
index 0000000..4491a64
--- /dev/null
@@ -0,0 +1,1097 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's pgsql extension
+ * for interacting with PostgreSQL databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Rui Hirokawa <hirokawa@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: pgsql.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's pgsql extension
+ * for interacting with PostgreSQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Rui Hirokawa <hirokawa@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_pgsql extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'pgsql';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'pgsql';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => '4.3.0',
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => true,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The number of rows affected by a data manipulation query
+     * @var integer
+     */
+    var $affected = 0;
+
+    /**
+     * The current row being looked at in fetchInto()
+     * @var array
+     * @access private
+     */
+    var $row = array();
+
+    /**
+     * The number of rows in a given result set
+     * @var array
+     * @access private
+     */
+    var $_num_rows = array();
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_pgsql()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's pgsql driver supports the following extra DSN options:
+     *   + connect_timeout  How many seconds to wait for a connection to
+     *                       be established.  Available since PEAR DB 1.7.0.
+     *   + new_link         If set to true, causes subsequent calls to
+     *                       connect() to return a new connection link
+     *                       instead of the existing one.  WARNING: this is
+     *                       not portable to other DBMS's.  Available only
+     *                       if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0.
+     *   + options          Command line options to be sent to the server.
+     *                       Available since PEAR DB 1.6.4.
+     *   + service          Specifies a service name in pg_service.conf that
+     *                       holds additional connection parameters.
+     *                       Available since PEAR DB 1.7.0.
+     *   + sslmode          How should SSL be used when connecting?  Values:
+     *                       disable, allow, prefer or require.
+     *                       Available since PEAR DB 1.7.0.
+     *   + tty              This was used to specify where to send server
+     *                       debug output.  Available since PEAR DB 1.6.4.
+     *
+     * Example of connecting to a new link via a socket:
+     * <code>
+     * require_once 'DB.php';
+     * 
+     * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true';
+     * $options = array(
+     *     'portability' => DB_PORTABILITY_ALL,
+     * );
+     * 
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     *
+     * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('pgsql')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp';
+
+        $params = array('');
+        if ($protocol == 'tcp') {
+            if ($dsn['hostspec']) {
+                $params[0] .= 'host=' . $dsn['hostspec'];
+            }
+            if ($dsn['port']) {
+                $params[0] .= ' port=' . $dsn['port'];
+            }
+        } elseif ($protocol == 'unix') {
+            // Allow for pg socket in non-standard locations.
+            if ($dsn['socket']) {
+                $params[0] .= 'host=' . $dsn['socket'];
+            }
+            if ($dsn['port']) {
+                $params[0] .= ' port=' . $dsn['port'];
+            }
+        }
+        if ($dsn['database']) {
+            $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\'';
+        }
+        if ($dsn['username']) {
+            $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\'';
+        }
+        if ($dsn['password']) {
+            $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\'';
+        }
+        if (!empty($dsn['options'])) {
+            $params[0] .= ' options=' . $dsn['options'];
+        }
+        if (!empty($dsn['tty'])) {
+            $params[0] .= ' tty=' . $dsn['tty'];
+        }
+        if (!empty($dsn['connect_timeout'])) {
+            $params[0] .= ' connect_timeout=' . $dsn['connect_timeout'];
+        }
+        if (!empty($dsn['sslmode'])) {
+            $params[0] .= ' sslmode=' . $dsn['sslmode'];
+        }
+        if (!empty($dsn['service'])) {
+            $params[0] .= ' service=' . $dsn['service'];
+        }
+
+        if (isset($dsn['new_link'])
+            && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+        {
+            if (version_compare(phpversion(), '4.3.0', '>=')) {
+                $params[] = PGSQL_CONNECT_FORCE_NEW;
+            }
+        }
+
+        $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect';
+
+        $ini = ini_get('track_errors');
+        $php_errormsg = '';
+        if ($ini) {
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+        } else {
+            ini_set('track_errors', 1);
+            $this->connection = @call_user_func_array($connect_function,
+                                                      $params);
+            ini_set('track_errors', $ini);
+        }
+
+        if (!$this->connection) {
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     $php_errormsg);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @pg_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+        if (!$this->autocommit && $ismanip) {
+            if ($this->transaction_opcount == 0) {
+                $result = @pg_exec($this->connection, 'begin;');
+                if (!$result) {
+                    return $this->pgsqlRaiseError();
+                }
+            }
+            $this->transaction_opcount++;
+        }
+        $result = @pg_exec($this->connection, $query);
+        if (!$result) {
+            return $this->pgsqlRaiseError();
+        }
+        // Determine which queries that should return data, and which
+        // should return an error code only.
+        if ($ismanip) {
+            $this->affected = @pg_affected_rows($result);
+            return DB_OK;
+        } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|SHOW)\s/si', $query)) {
+            /* PostgreSQL commands:
+               ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY,
+               CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH,
+               GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET,
+               REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW,
+               UNLISTEN, UPDATE, VACUUM
+            */
+            $this->row[(int)$result] = 0; // reset the row counter.
+            $numrows = $this->numRows($result);
+            if (is_object($numrows)) {
+                return $numrows;
+            }
+            $this->_num_rows[(int)$result] = $numrows;
+            $this->affected = 0;
+            return $result;
+        } else {
+            $this->affected = 0;
+            return DB_OK;
+        }
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal pgsql result pointer to the next available result
+     *
+     * @param a valid fbsql result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        $result_int = (int)$result;
+        $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int];
+        if ($rownum >= $this->_num_rows[$result_int]) {
+            return null;
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @pg_fetch_row($result, $rownum);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        $this->row[$result_int] = ++$rownum;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        if (is_resource($result)) {
+            unset($this->row[(int)$result]);
+            unset($this->_num_rows[(int)$result]);
+            $this->affected = 0;
+            return @pg_freeresult($result);
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ quote()
+
+    /**
+     * @deprecated  Deprecated in release 1.6.0
+     * @internal
+     */
+    function quote($str)
+    {
+        return $this->quoteSmart($str);
+    }
+
+    // }}}
+    // {{{ quoteSmart()
+
+    /**
+     * Formats input so it can be safely used in a query
+     *
+     * @param mixed $in  the data to be formatted
+     *
+     * @return mixed  the formatted data.  The format depends on the input's
+     *                 PHP type:
+     *                 + null = the string <samp>NULL</samp>
+     *                 + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
+     *                 + integer or double = the unquoted number
+     *                 + other (including strings and numeric strings) =
+     *                   the data escaped according to MySQL's settings
+     *                   then encapsulated between single quotes
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function quoteSmart($in)
+    {
+        if (is_int($in) || is_double($in)) {
+            return $in;
+        } elseif (is_bool($in)) {
+            return $in ? 'TRUE' : 'FALSE';
+        } elseif (is_null($in)) {
+            return 'NULL';
+        } else {
+            return "'" . $this->escapeSimple($in) . "'";
+        }
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * {@internal PostgreSQL treats a backslash as an escape character,
+     * so they are escaped as well.
+     *
+     * Not using pg_escape_string() yet because it requires PostgreSQL
+     * to be at version 7.2 or greater.}}
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @see DB_common::quoteSmart()
+     * @since Method available since Release 1.6.0
+     */
+    function escapeSimple($str)
+    {
+        return str_replace("'", "''", str_replace('\\', '\\\\', $str));
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @pg_numfields($result);
+        if (!$cols) {
+            return $this->pgsqlRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @pg_numrows($result);
+        if ($rows === null) {
+            return $this->pgsqlRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            // (disabled) hack to shut up error messages from libpq.a
+            //@fclose(@fopen("php://stderr", "w"));
+            $result = @pg_exec($this->connection, 'end;');
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->pgsqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            $result = @pg_exec($this->connection, 'abort;');
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->pgsqlRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        return $this->affected;
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_pgsql::createSequence(), DB_pgsql::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $repeat = false;
+        do {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result =& $this->query("SELECT NEXTVAL('${seqname}')");
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result) &&
+                $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+                $repeat = true;
+                $this->pushErrorHandling(PEAR_ERROR_RETURN);
+                $result = $this->createSequence($seq_name);
+                $this->popErrorHandling();
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+            } else {
+                $repeat = false;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+        $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        $result->free();
+        return $arr[0];
+    }
+
+    // }}}
+    // {{{ createSequence()
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_pgsql::nextID(), DB_pgsql::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $result = $this->query("CREATE SEQUENCE ${seqname}");
+        return $result;
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_pgsql::nextID(), DB_pgsql::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP SEQUENCE '
+                            . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        return "$query LIMIT $count OFFSET $from";
+    }
+
+    // }}}
+    // {{{ pgsqlRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_pgsql::errorNative(), DB_pgsql::errorCode()
+     */
+    function pgsqlRaiseError($errno = null)
+    {
+        $native = $this->errorNative();
+        if ($errno === null) {
+            $errno = $this->errorCode($native);
+        }
+        return $this->raiseError($errno, null, null, null, $native);
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error message produced by the last query
+     *
+     * {@internal Error messages are used instead of error codes 
+     * in order to support older versions of PostgreSQL.}}
+     *
+     * @return string  the DBMS' error message
+     */
+    function errorNative()
+    {
+        return @pg_errormessage($this->connection);
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Determines PEAR::DB error code from the database's text error message.
+     *
+     * @param  string  $errormsg  error message returned from the database
+     * @return integer  an error number from a DB error constant
+     */
+    function errorCode($errormsg)
+    {
+        static $error_regexps;
+        if (!isset($error_regexps)) {
+            $error_regexps = array(
+                '/(relation|sequence|table).*does not exist|class .* not found/i'
+                    => DB_ERROR_NOSUCHTABLE,
+                '/index .* does not exist/'
+                    => DB_ERROR_NOT_FOUND,
+                '/column .* does not exist/i'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/relation .* already exists/i'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/(divide|division) by zero$/i'
+                    => DB_ERROR_DIVZERO,
+                '/pg_atoi: error in .*: can\'t parse /i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/invalid input syntax for( type)? (integer|numeric)/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/value .* is out of range for type \w*int/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/integer out of range/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/value too long for type character/i'
+                    => DB_ERROR_INVALID,
+                '/attribute .* not found|relation .* does not have attribute/i'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/column .* specified in USING clause does not exist in (left|right) table/i'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/parser: parse error at or near/i'
+                    => DB_ERROR_SYNTAX,
+                '/syntax error at/'
+                    => DB_ERROR_SYNTAX,
+                '/column reference .* is ambiguous/i'
+                    => DB_ERROR_SYNTAX,
+                '/permission denied/'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/violates not-null constraint/'
+                    => DB_ERROR_CONSTRAINT_NOT_NULL,
+                '/violates [\w ]+ constraint/'
+                    => DB_ERROR_CONSTRAINT,
+                '/referential integrity violation/'
+                    => DB_ERROR_CONSTRAINT,
+                '/more expressions than target columns/i'
+                    => DB_ERROR_VALUE_COUNT_ON_ROW,
+            );
+        }
+        foreach ($error_regexps as $regexp => $code) {
+            if (preg_match($regexp, $errormsg)) {
+                return $code;
+            }
+        }
+        // Fall back to DB_ERROR if there was no mapping.
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+     * is a table name.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0");
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @pg_numfields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $res[$i] = array(
+                'table' => $got_string ? $case_func($result) : '',
+                'name'  => $case_func(@pg_fieldname($id, $i)),
+                'type'  => @pg_fieldtype($id, $i),
+                'len'   => @pg_fieldsize($id, $i),
+                'flags' => $got_string
+                           ? $this->_pgFieldFlags($id, $i, $result)
+                           : '',
+            );
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @pg_freeresult($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ _pgFieldFlags()
+
+    /**
+     * Get a column's flags
+     *
+     * Supports "not_null", "default_value", "primary_key", "unique_key"
+     * and "multiple_key".  The default value is passed through
+     * rawurlencode() in case there are spaces in it.
+     *
+     * @param int $resource   the PostgreSQL result identifier
+     * @param int $num_field  the field number
+     *
+     * @return string  the flags
+     *
+     * @access private
+     */
+    function _pgFieldFlags($resource, $num_field, $table_name)
+    {
+        $field_name = @pg_fieldname($resource, $num_field);
+
+        $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef
+                                FROM pg_attribute f, pg_class tab, pg_type typ
+                                WHERE tab.relname = typ.typname
+                                AND typ.typrelid = f.attrelid
+                                AND f.attname = '$field_name'
+                                AND tab.relname = '$table_name'");
+        if (@pg_numrows($result) > 0) {
+            $row = @pg_fetch_row($result, 0);
+            $flags  = ($row[0] == 't') ? 'not_null ' : '';
+
+            if ($row[1] == 't') {
+                $result = @pg_exec($this->connection, "SELECT a.adsrc
+                                    FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a
+                                    WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid
+                                    AND f.attrelid = a.adrelid AND f.attname = '$field_name'
+                                    AND tab.relname = '$table_name' AND f.attnum = a.adnum");
+                $row = @pg_fetch_row($result, 0);
+                $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]);
+                $flags .= 'default_' . rawurlencode($num) . ' ';
+            }
+        } else {
+            $flags = '';
+        }
+        $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey
+                                FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i
+                                WHERE tab.relname = typ.typname
+                                AND typ.typrelid = f.attrelid
+                                AND f.attrelid = i.indrelid
+                                AND f.attname = '$field_name'
+                                AND tab.relname = '$table_name'");
+        $count = @pg_numrows($result);
+
+        for ($i = 0; $i < $count ; $i++) {
+            $row = @pg_fetch_row($result, $i);
+            $keys = explode(' ', $row[2]);
+
+            if (in_array($num_field + 1, $keys)) {
+                $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : '';
+                $flags .= ($row[1] == 't') ? 'primary_key ' : '';
+                if (count($keys) > 1)
+                    $flags .= 'multiple_key ';
+            }
+        }
+
+        return trim($flags);
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return 'SELECT c.relname AS "Name"'
+                        . ' FROM pg_class c, pg_user u'
+                        . ' WHERE c.relowner = u.usesysid'
+                        . " AND c.relkind = 'r'"
+                        . ' AND NOT EXISTS'
+                        . ' (SELECT 1 FROM pg_views'
+                        . '  WHERE viewname = c.relname)'
+                        . " AND c.relname !~ '^(pg_|sql_)'"
+                        . ' UNION'
+                        . ' SELECT c.relname AS "Name"'
+                        . ' FROM pg_class c'
+                        . " WHERE c.relkind = 'r'"
+                        . ' AND NOT EXISTS'
+                        . ' (SELECT 1 FROM pg_views'
+                        . '  WHERE viewname = c.relname)'
+                        . ' AND NOT EXISTS'
+                        . ' (SELECT 1 FROM pg_user'
+                        . '  WHERE usesysid = c.relowner)'
+                        . " AND c.relname !~ '^pg_'";
+            case 'schema.tables':
+                return "SELECT schemaname || '.' || tablename"
+                        . ' AS "Name"'
+                        . ' FROM pg_catalog.pg_tables'
+                        . ' WHERE schemaname NOT IN'
+                        . " ('pg_catalog', 'information_schema', 'pg_toast')";
+            case 'views':
+                // Table cols: viewname | viewowner | definition
+                return 'SELECT viewname from pg_views WHERE schemaname'
+                        . " NOT IN ('information_schema', 'pg_catalog')";
+            case 'users':
+                // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd  |valuntil
+                return 'SELECT usename FROM pg_user';
+            case 'databases':
+                return 'SELECT datname FROM pg_database';
+            case 'functions':
+            case 'procedures':
+                return 'SELECT proname FROM pg_proc WHERE proowner <> 1';
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/sqlite.php b/program/lib/DB/sqlite.php
new file mode 100644 (file)
index 0000000..3619f59
--- /dev/null
@@ -0,0 +1,942 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's sqlite extension
+ * for interacting with SQLite databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Urs Gehrig <urs@circle.ch>
+ * @author     Mika Tuupola <tuupola@appelsiini.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0 3.0
+ * @version    CVS: $Id: sqlite.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's sqlite extension
+ * for interacting with SQLite databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * NOTICE:  This driver needs PHP's track_errors ini setting to be on.
+ * It is automatically turned on when connecting to the database.
+ * Make sure your scripts don't turn it off.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Urs Gehrig <urs@circle.ch>
+ * @author     Mika Tuupola <tuupola@appelsiini.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_sqlite extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'sqlite';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'sqlite';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'alter',
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => false,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     *
+     * {@internal  Error codes according to sqlite_exec.  See the online
+     * manual at http://sqlite.org/c_interface.html for info.
+     * This error handling based on sqlite_exec is not yet implemented.}}
+     *
+     * @var array
+     */
+    var $errorcode_map = array(
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * SQLite data types
+     *
+     * @link http://www.sqlite.org/datatypes.html
+     *
+     * @var array
+     */
+    var $keywords = array (
+        'BLOB'      => '',
+        'BOOLEAN'   => '',
+        'CHARACTER' => '',
+        'CLOB'      => '',
+        'FLOAT'     => '',
+        'INTEGER'   => '',
+        'KEY'       => '',
+        'NATIONAL'  => '',
+        'NUMERIC'   => '',
+        'NVARCHAR'  => '',
+        'PRIMARY'   => '',
+        'TEXT'      => '',
+        'TIMESTAMP' => '',
+        'UNIQUE'    => '',
+        'VARCHAR'   => '',
+        'VARYING'   => '',
+    );
+
+    /**
+     * The most recent error message from $php_errormsg
+     * @var string
+     * @access private
+     */
+    var $_lasterror = '';
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_sqlite()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's sqlite driver supports the following extra DSN options:
+     *   + mode  The permissions for the database file, in four digit
+     *            chmod octal format (eg "0600").
+     *
+     * Example of connecting to a database in read-only mode:
+     * <code>
+     * require_once 'DB.php';
+     * 
+     * $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400';
+     * $options = array(
+     *     'portability' => DB_PORTABILITY_ALL,
+     * );
+     * 
+     * $db =& DB::connect($dsn, $options);
+     * if (PEAR::isError($db)) {
+     *     die($db->getMessage());
+     * }
+     * </code>
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('sqlite')) {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        if ($dsn['database']) {
+            if (!file_exists($dsn['database'])) {
+                if (!touch($dsn['database'])) {
+                    return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
+                }
+                if (!isset($dsn['mode']) ||
+                    !is_numeric($dsn['mode']))
+                {
+                    $mode = 0644;
+                } else {
+                    $mode = octdec($dsn['mode']);
+                }
+                if (!chmod($dsn['database'], $mode)) {
+                    return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
+                }
+                if (!file_exists($dsn['database'])) {
+                    return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
+                }
+            }
+            if (!is_file($dsn['database'])) {
+                return $this->sqliteRaiseError(DB_ERROR_INVALID);
+            }
+            if (!is_readable($dsn['database'])) {
+                return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
+            }
+        } else {
+            return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
+        }
+
+        $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open';
+
+        // track_errors must remain on for simpleQuery()
+        ini_set('track_errors', 1);
+        $php_errormsg = '';
+
+        if (!$this->connection = @$connect_function($dsn['database'])) {
+            return $this->raiseError(DB_ERROR_NODBSELECTED,
+                                     null, null, null,
+                                     $php_errormsg);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @sqlite_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * NOTICE:  This method needs PHP's track_errors ini setting to be on.
+     * It is automatically turned on when connecting to the database.
+     * Make sure your scripts don't turn it off.
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        $query = $this->modifyQuery($query);
+
+        $php_errormsg = '';
+
+        $result = @sqlite_query($query, $this->connection);
+        $this->_lasterror = $php_errormsg ? $php_errormsg : '';
+
+        $this->result = $result;
+        if (!$this->result) {
+            return $this->sqliteRaiseError(null);
+        }
+
+        // sqlite_query() seems to allways return a resource
+        // so cant use that. Using $ismanip instead
+        if (!$ismanip) {
+            $numRows = $this->numRows($result);
+            if (is_object($numRows)) {
+                // we've got PEAR_Error
+                return $numRows;
+            }
+            return $result;
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal sqlite result pointer to the next available result
+     *
+     * @param resource $result  the valid sqlite result resource
+     *
+     * @return bool  true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@sqlite_seek($this->result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            $arr = @sqlite_fetch_array($result, SQLITE_ASSOC);
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @sqlite_fetch_array($result, SQLITE_NUM);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            /*
+             * Even though this DBMS already trims output, we do this because
+             * a field might have intentional whitespace at the end that
+             * gets removed by DB_PORTABILITY_RTRIM under another driver.
+             */
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult(&$result)
+    {
+        // XXX No native free?
+        if (!is_resource($result)) {
+            return false;
+        }
+        $result = null;
+        return true;
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @sqlite_num_fields($result);
+        if (!$cols) {
+            return $this->sqliteRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @sqlite_num_rows($result);
+        if ($rows === null) {
+            return $this->sqliteRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ affected()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        return @sqlite_changes($this->connection);
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_sqlite::nextID(), DB_sqlite::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_sqlite::nextID(), DB_sqlite::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        $query   = 'CREATE TABLE ' . $seqname .
+                   ' (id INTEGER UNSIGNED PRIMARY KEY) ';
+        $result  = $this->query($query);
+        if (DB::isError($result)) {
+            return($result);
+        }
+        $query   = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname
+                    BEGIN
+                        DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID();
+                    END ";
+        $result  = $this->query($query);
+        if (DB::isError($result)) {
+            return($result);
+        }
+    }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_sqlite::createSequence(), DB_sqlite::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+
+        do {
+            $repeat = 0;
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)");
+            $this->popErrorHandling();
+            if ($result === DB_OK) {
+                $id = @sqlite_last_insert_rowid($this->connection);
+                if ($id != 0) {
+                    return $id;
+                }
+            } elseif ($ondemand && DB::isError($result) &&
+                      $result->getCode() == DB_ERROR_NOSUCHTABLE)
+            {
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                } else {
+                    $repeat = 1;
+                }
+            }
+        } while ($repeat);
+
+        return $this->raiseError($result);
+    }
+
+    // }}}
+    // {{{ getDbFileStats()
+
+    /**
+     * Get the file stats for the current database
+     *
+     * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size,
+     * atime, mtime, ctime, blksize, blocks or a numeric key between
+     * 0 and 12.
+     *
+     * @param string $arg  the array key for stats()
+     *
+     * @return mixed  an array on an unspecified key, integer on a passed
+     *                arg and false at a stats error
+     */
+    function getDbFileStats($arg = '')
+    {
+        $stats = stat($this->dsn['database']);
+        if ($stats == false) {
+            return false;
+        }
+        if (is_array($stats)) {
+            if (is_numeric($arg)) {
+                if (((int)$arg <= 12) & ((int)$arg >= 0)) {
+                    return false;
+                }
+                return $stats[$arg ];
+            }
+            if (array_key_exists(trim($arg), $stats)) {
+                return $stats[$arg ];
+            }
+        }
+        return $stats;
+    }
+
+    // }}}
+    // {{{ escapeSimple()
+
+    /**
+     * Escapes a string according to the current DBMS's standards
+     *
+     * In SQLite, this makes things safe for inserts/updates, but may
+     * cause problems when performing text comparisons against columns
+     * containing binary data. See the
+     * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
+     *
+     * @param string $str  the string to be escaped
+     *
+     * @return string  the escaped string
+     *
+     * @since Method available since Release 1.6.1
+     * @see DB_common::escapeSimple()
+     */
+    function escapeSimple($str)
+    {
+        return @sqlite_escape_string($str);
+    }
+
+    // }}}
+    // {{{ modifyLimitQuery()
+
+    /**
+     * Adds LIMIT clauses to a query string according to current DBMS standards
+     *
+     * @param string $query   the query to modify
+     * @param int    $from    the row to start to fetching (0 = the first row)
+     * @param int    $count   the numbers of rows to fetch
+     * @param mixed  $params  array, string or numeric data to be used in
+     *                         execution of the statement.  Quantity of items
+     *                         passed must match quantity of placeholders in
+     *                         query:  meaning 1 placeholder for non-array
+     *                         parameters or 1 placeholder per array element.
+     *
+     * @return string  the query string with LIMIT clauses added
+     *
+     * @access protected
+     */
+    function modifyLimitQuery($query, $from, $count, $params = array())
+    {
+        return "$query LIMIT $count OFFSET $from";
+    }
+
+    // }}}
+    // {{{ modifyQuery()
+
+    /**
+     * Changes a query string for various DBMS specific reasons
+     *
+     * This little hack lets you know how many rows were deleted
+     * when running a "DELETE FROM table" query.  Only implemented
+     * if the DB_PORTABILITY_DELETE_COUNT portability option is on.
+     *
+     * @param string $query  the query string to modify
+     *
+     * @return string  the modified query string
+     *
+     * @access protected
+     * @see DB_common::setOption()
+     */
+    function modifyQuery($query)
+    {
+        if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
+            if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
+                $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
+                                      'DELETE FROM \1 WHERE 1=1', $query);
+            }
+        }
+        return $query;
+    }
+
+    // }}}
+    // {{{ sqliteRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_sqlite::errorNative(), DB_sqlite::errorCode()
+     */
+    function sqliteRaiseError($errno = null)
+    {
+        $native = $this->errorNative();
+        if ($errno === null) {
+            $errno = $this->errorCode($native);
+        }
+
+        $errorcode = @sqlite_last_error($this->connection);
+        $userinfo = "$errorcode ** $this->last_query";
+
+        return $this->raiseError($errno, null, null, $userinfo, $native);
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error message produced by the last query
+     *
+     * {@internal This is used to retrieve more meaningfull error messages
+     * because sqlite_last_error() does not provide adequate info.}}
+     *
+     * @return string  the DBMS' error message
+     */
+    function errorNative()
+    {
+        return $this->_lasterror;
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Determines PEAR::DB error code from the database's text error message
+     *
+     * @param string $errormsg  the error message returned from the database
+     *
+     * @return integer  the DB error number
+     */
+    function errorCode($errormsg)
+    {
+        static $error_regexps;
+        if (!isset($error_regexps)) {
+            $error_regexps = array(
+                '/^no such table:/' => DB_ERROR_NOSUCHTABLE,
+                '/^no such index:/' => DB_ERROR_NOT_FOUND,
+                '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS,
+                '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT,
+                '/is not unique/' => DB_ERROR_CONSTRAINT,
+                '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT,
+                '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT,
+                '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL,
+                '/^no such column:/' => DB_ERROR_NOSUCHFIELD,
+                '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD,
+                '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX,
+                '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW,
+            );
+        }
+        foreach ($error_regexps as $regexp => $code) {
+            if (preg_match($regexp, $errormsg)) {
+                return $code;
+            }
+        }
+        // Fall back to DB_ERROR if there was no mapping.
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table
+     *
+     * @param string         $result  a string containing the name of a table
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     * @since Method available since Release 1.7.0
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            $id = @sqlite_array_query($this->connection,
+                                      "PRAGMA table_info('$result');",
+                                      SQLITE_ASSOC);
+            $got_string = true;
+        } else {
+            $this->last_query = '';
+            return $this->raiseError(DB_ERROR_NOT_CAPABLE, null, null, null,
+                                     'This DBMS can not obtain tableInfo' .
+                                     ' from result sets');
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = count($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            if (strpos($id[$i]['type'], '(') !== false) {
+                $bits = explode('(', $id[$i]['type']);
+                $type = $bits[0];
+                $len  = rtrim($bits[1],')');
+            } else {
+                $type = $id[$i]['type'];
+                $len  = 0;
+            }
+
+            $flags = '';
+            if ($id[$i]['pk']) {
+                $flags .= 'primary_key ';
+            }
+            if ($id[$i]['notnull']) {
+                $flags .= 'not_null ';
+            }
+            if ($id[$i]['dflt_value'] !== null) {
+                $flags .= 'default_' . rawurlencode($id[$i]['dflt_value']);
+            }
+            $flags = trim($flags);
+
+            $res[$i] = array(
+                'table' => $case_func($result),
+                'name'  => $case_func($id[$i]['name']),
+                'type'  => $type,
+                'len'   => $len,
+                'flags' => $flags,
+            );
+
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        return $res;
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     * @param array  $args  SQLITE DRIVER ONLY: a private array of arguments
+     *                       used by the getSpecialQuery().  Do not use
+     *                       this directly.
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type, $args = array())
+    {
+        if (!is_array($args)) {
+            return $this->raiseError('no key specified', null, null, null,
+                                     'Argument has to be an array.');
+        }
+
+        switch ($type) {
+            case 'master':
+                return 'SELECT * FROM sqlite_master;';
+            case 'tables':
+                return "SELECT name FROM sqlite_master WHERE type='table' "
+                       . 'UNION ALL SELECT name FROM sqlite_temp_master '
+                       . "WHERE type='table' ORDER BY name;";
+            case 'schema':
+                return 'SELECT sql FROM (SELECT * FROM sqlite_master '
+                       . 'UNION ALL SELECT * FROM sqlite_temp_master) '
+                       . "WHERE type!='meta' "
+                       . 'ORDER BY tbl_name, type DESC, name;';
+            case 'schemax':
+            case 'schema_x':
+                /*
+                 * Use like:
+                 * $res = $db->query($db->getSpecialQuery('schema_x',
+                 *                   array('table' => 'table3')));
+                 */
+                return 'SELECT sql FROM (SELECT * FROM sqlite_master '
+                       . 'UNION ALL SELECT * FROM sqlite_temp_master) '
+                       . "WHERE tbl_name LIKE '{$args['table']}' "
+                       . "AND type!='meta' "
+                       . 'ORDER BY type DESC, name;';
+            case 'alter':
+                /*
+                 * SQLite does not support ALTER TABLE; this is a helper query
+                 * to handle this. 'table' represents the table name, 'rows'
+                 * the news rows to create, 'save' the row(s) to keep _with_
+                 * the data.
+                 *
+                 * Use like:
+                 * $args = array(
+                 *     'table' => $table,
+                 *     'rows'  => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT",
+                 *     'save'  => "NULL, titel, content, datetime"
+                 * );
+                 * $res = $db->query( $db->getSpecialQuery('alter', $args));
+                 */
+                $rows = strtr($args['rows'], $this->keywords);
+
+                $q = array(
+                    'BEGIN TRANSACTION',
+                    "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})",
+                    "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}",
+                    "DROP TABLE {$args['table']}",
+                    "CREATE TABLE {$args['table']} ({$args['rows']})",
+                    "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup",
+                    "DROP TABLE {$args['table']}_backup",
+                    'COMMIT',
+                );
+
+                /*
+                 * This is a dirty hack, since the above query will not get
+                 * executed with a single query call so here the query method
+                 * will be called directly and return a select instead.
+                 */
+                foreach ($q as $query) {
+                    $this->query($query);
+                }
+                return "SELECT * FROM {$args['table']};";
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/storage.php b/program/lib/DB/storage.php
new file mode 100644 (file)
index 0000000..b97a4ad
--- /dev/null
@@ -0,0 +1,504 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Provides an object interface to a table row
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <stig@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: storage.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB class so it can be extended from
+ */
+require_once 'DB.php';
+
+/**
+ * Provides an object interface to a table row
+ *
+ * It lets you add, delete and change rows using objects rather than SQL
+ * statements.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Stig Bakken <stig@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_storage extends PEAR
+{
+    // {{{ properties
+
+    /** the name of the table (or view, if the backend database supports
+        updates in views) we hold data from */
+    var $_table = null;
+
+    /** which column(s) in the table contains primary keys, can be a
+        string for single-column primary keys, or an array of strings
+        for multiple-column primary keys */
+    var $_keycolumn = null;
+
+    /** DB connection handle used for all transactions */
+    var $_dbh = null;
+
+    /** an assoc with the names of database fields stored as properties
+        in this object */
+    var $_properties = array();
+
+    /** an assoc with the names of the properties in this object that
+        have been changed since they were fetched from the database */
+    var $_changes = array();
+
+    /** flag that decides if data in this object can be changed.
+        objects that don't have their table's key column in their
+        property lists will be flagged as read-only. */
+    var $_readonly = false;
+
+    /** function or method that implements a validator for fields that
+        are set, this validator function returns true if the field is
+        valid, false if not */
+    var $_validator = null;
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * Constructor
+     *
+     * @param $table string the name of the database table
+     *
+     * @param $keycolumn mixed string with name of key column, or array of
+     * strings if the table has a primary key of more than one column
+     *
+     * @param $dbh object database connection object
+     *
+     * @param $validator mixed function or method used to validate
+     * each new value, called with three parameters: the name of the
+     * field/column that is changing, a reference to the new value and
+     * a reference to this object
+     *
+     */
+    function DB_storage($table, $keycolumn, &$dbh, $validator = null)
+    {
+        $this->PEAR('DB_Error');
+        $this->_table = $table;
+        $this->_keycolumn = $keycolumn;
+        $this->_dbh = $dbh;
+        $this->_readonly = false;
+        $this->_validator = $validator;
+    }
+
+    // }}}
+    // {{{ _makeWhere()
+
+    /**
+     * Utility method to build a "WHERE" clause to locate ourselves in
+     * the table.
+     *
+     * XXX future improvement: use rowids?
+     *
+     * @access private
+     */
+    function _makeWhere($keyval = null)
+    {
+        if (is_array($this->_keycolumn)) {
+            if ($keyval === null) {
+                for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
+                    $keyval[] = $this->{$this->_keycolumn[$i]};
+                }
+            }
+            $whereclause = '';
+            for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
+                if ($i > 0) {
+                    $whereclause .= ' AND ';
+                }
+                $whereclause .= $this->_keycolumn[$i];
+                if (is_null($keyval[$i])) {
+                    // there's not much point in having a NULL key,
+                    // but we support it anyway
+                    $whereclause .= ' IS NULL';
+                } else {
+                    $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
+                }
+            }
+        } else {
+            if ($keyval === null) {
+                $keyval = @$this->{$this->_keycolumn};
+            }
+            $whereclause = $this->_keycolumn;
+            if (is_null($keyval)) {
+                // there's not much point in having a NULL key,
+                // but we support it anyway
+                $whereclause .= ' IS NULL';
+            } else {
+                $whereclause .= ' = ' . $this->_dbh->quote($keyval);
+            }
+        }
+        return $whereclause;
+    }
+
+    // }}}
+    // {{{ setup()
+
+    /**
+     * Method used to initialize a DB_storage object from the
+     * configured table.
+     *
+     * @param $keyval mixed the key[s] of the row to fetch (string or array)
+     *
+     * @return int DB_OK on success, a DB error if not
+     */
+    function setup($keyval)
+    {
+        $whereclause = $this->_makeWhere($keyval);
+        $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
+        $sth = $this->_dbh->query($query);
+        if (DB::isError($sth)) {
+            return $sth;
+        }
+        $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
+        if (DB::isError($row)) {
+            return $row;
+        }
+        if (!$row) {
+            return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+                                     $query, null, true);
+        }
+        foreach ($row as $key => $value) {
+            $this->_properties[$key] = true;
+            $this->$key = $value;
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ insert()
+
+    /**
+     * Create a new (empty) row in the configured table for this
+     * object.
+     */
+    function insert($newpk)
+    {
+        if (is_array($this->_keycolumn)) {
+            $primarykey = $this->_keycolumn;
+        } else {
+            $primarykey = array($this->_keycolumn);
+        }
+        settype($newpk, "array");
+        for ($i = 0; $i < sizeof($primarykey); $i++) {
+            $pkvals[] = $this->_dbh->quote($newpk[$i]);
+        }
+
+        $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
+                                  implode(",", $primarykey) . ") VALUES(" .
+                                  implode(",", $pkvals) . ")");
+        if (DB::isError($sth)) {
+            return $sth;
+        }
+        if (sizeof($newpk) == 1) {
+            $newpk = $newpk[0];
+        }
+        $this->setup($newpk);
+    }
+
+    // }}}
+    // {{{ toString()
+
+    /**
+     * Output a simple description of this DB_storage object.
+     * @return string object description
+     */
+    function toString()
+    {
+        $info = strtolower(get_class($this));
+        $info .= " (table=";
+        $info .= $this->_table;
+        $info .= ", keycolumn=";
+        if (is_array($this->_keycolumn)) {
+            $info .= "(" . implode(",", $this->_keycolumn) . ")";
+        } else {
+            $info .= $this->_keycolumn;
+        }
+        $info .= ", dbh=";
+        if (is_object($this->_dbh)) {
+            $info .= $this->_dbh->toString();
+        } else {
+            $info .= "null";
+        }
+        $info .= ")";
+        if (sizeof($this->_properties)) {
+            $info .= " [loaded, key=";
+            $keyname = $this->_keycolumn;
+            if (is_array($keyname)) {
+                $info .= "(";
+                for ($i = 0; $i < sizeof($keyname); $i++) {
+                    if ($i > 0) {
+                        $info .= ",";
+                    }
+                    $info .= $this->$keyname[$i];
+                }
+                $info .= ")";
+            } else {
+                $info .= $this->$keyname;
+            }
+            $info .= "]";
+        }
+        if (sizeof($this->_changes)) {
+            $info .= " [modified]";
+        }
+        return $info;
+    }
+
+    // }}}
+    // {{{ dump()
+
+    /**
+     * Dump the contents of this object to "standard output".
+     */
+    function dump()
+    {
+        foreach ($this->_properties as $prop => $foo) {
+            print "$prop = ";
+            print htmlentities($this->$prop);
+            print "<br />\n";
+        }
+    }
+
+    // }}}
+    // {{{ &create()
+
+    /**
+     * Static method used to create new DB storage objects.
+     * @param $data assoc. array where the keys are the names
+     *              of properties/columns
+     * @return object a new instance of DB_storage or a subclass of it
+     */
+    function &create($table, &$data)
+    {
+        $classname = strtolower(get_class($this));
+        $obj =& new $classname($table);
+        foreach ($data as $name => $value) {
+            $obj->_properties[$name] = true;
+            $obj->$name = &$value;
+        }
+        return $obj;
+    }
+
+    // }}}
+    // {{{ loadFromQuery()
+
+    /**
+     * Loads data into this object from the given query.  If this
+     * object already contains table data, changes will be saved and
+     * the object re-initialized first.
+     *
+     * @param $query SQL query
+     *
+     * @param $params parameter list in case you want to use
+     * prepare/execute mode
+     *
+     * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
+     * returned object is read-only (because the object's specified
+     * key column was not found among the columns returned by $query),
+     * or another DB error code in case of errors.
+     */
+// XXX commented out for now
+/*
+    function loadFromQuery($query, $params = null)
+    {
+        if (sizeof($this->_properties)) {
+            if (sizeof($this->_changes)) {
+                $this->store();
+                $this->_changes = array();
+            }
+            $this->_properties = array();
+        }
+        $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
+        if (DB::isError($rowdata)) {
+            return $rowdata;
+        }
+        reset($rowdata);
+        $found_keycolumn = false;
+        while (list($key, $value) = each($rowdata)) {
+            if ($key == $this->_keycolumn) {
+                $found_keycolumn = true;
+            }
+            $this->_properties[$key] = true;
+            $this->$key = &$value;
+            unset($value); // have to unset, or all properties will
+                           // refer to the same value
+        }
+        if (!$found_keycolumn) {
+            $this->_readonly = true;
+            return DB_WARNING_READ_ONLY;
+        }
+        return DB_OK;
+    }
+ */
+
+    // }}}
+    // {{{ set()
+
+    /**
+     * Modify an attriute value.
+     */
+    function set($property, $newvalue)
+    {
+        // only change if $property is known and object is not
+        // read-only
+        if ($this->_readonly) {
+            return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
+                                     null, null, null, true);
+        }
+        if (@isset($this->_properties[$property])) {
+            if (empty($this->_validator)) {
+                $valid = true;
+            } else {
+                $valid = @call_user_func($this->_validator,
+                                         $this->_table,
+                                         $property,
+                                         $newvalue,
+                                         $this->$property,
+                                         $this);
+            }
+            if ($valid) {
+                $this->$property = $newvalue;
+                if (empty($this->_changes[$property])) {
+                    $this->_changes[$property] = 0;
+                } else {
+                    $this->_changes[$property]++;
+                }
+            } else {
+                return $this->raiseError(null, DB_ERROR_INVALID, null,
+                                         null, "invalid field: $property",
+                                         null, true);
+            }
+            return true;
+        }
+        return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
+                                 null, "unknown field: $property",
+                                 null, true);
+    }
+
+    // }}}
+    // {{{ &get()
+
+    /**
+     * Fetch an attribute value.
+     *
+     * @param string attribute name
+     *
+     * @return attribute contents, or null if the attribute name is
+     * unknown
+     */
+    function &get($property)
+    {
+        // only return if $property is known
+        if (isset($this->_properties[$property])) {
+            return $this->$property;
+        }
+        $tmp = null;
+        return $tmp;
+    }
+
+    // }}}
+    // {{{ _DB_storage()
+
+    /**
+     * Destructor, calls DB_storage::store() if there are changes
+     * that are to be kept.
+     */
+    function _DB_storage()
+    {
+        if (sizeof($this->_changes)) {
+            $this->store();
+        }
+        $this->_properties = array();
+        $this->_changes = array();
+        $this->_table = null;
+    }
+
+    // }}}
+    // {{{ store()
+
+    /**
+     * Stores changes to this object in the database.
+     *
+     * @return DB_OK or a DB error
+     */
+    function store()
+    {
+        foreach ($this->_changes as $name => $foo) {
+            $params[] = &$this->$name;
+            $vars[] = $name . ' = ?';
+        }
+        if ($vars) {
+            $query = 'UPDATE ' . $this->_table . ' SET ' .
+                implode(', ', $vars) . ' WHERE ' .
+                $this->_makeWhere();
+            $stmt = $this->_dbh->prepare($query);
+            $res = $this->_dbh->execute($stmt, $params);
+            if (DB::isError($res)) {
+                return $res;
+            }
+            $this->_changes = array();
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ remove()
+
+    /**
+     * Remove the row represented by this object from the database.
+     *
+     * @return mixed DB_OK or a DB error
+     */
+    function remove()
+    {
+        if ($this->_readonly) {
+            return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
+                                     null, null, null, true);
+        }
+        $query = 'DELETE FROM ' . $this->_table .' WHERE '.
+            $this->_makeWhere();
+        $res = $this->_dbh->query($query);
+        if (DB::isError($res)) {
+            return $res;
+        }
+        foreach ($this->_properties as $prop => $foo) {
+            unset($this->$prop);
+        }
+        $this->_properties = array();
+        $this->_changes = array();
+        return DB_OK;
+    }
+
+    // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/sybase.php b/program/lib/DB/sybase.php
new file mode 100644 (file)
index 0000000..4bafb41
--- /dev/null
@@ -0,0 +1,907 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's sybase extension
+ * for interacting with Sybase databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Antônio Carlos Venâncio Júnior <floripa@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: sybase.php 12 2005-10-02 11:36:35Z sparc $
+ * @link       http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's sybase extension
+ * for interacting with Sybase databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * WARNING:  This driver may fail with multiple connections under the
+ * same user/pass/host and different databases.
+ *
+ * @category   Database
+ * @package    DB
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Antônio Carlos Venâncio Júnior <floripa@php.net>
+ * @author     Daniel Convissor <danielc@php.net>
+ * @copyright  1997-2005 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/DB
+ */
+class DB_sybase extends DB_common
+{
+    // {{{ properties
+
+    /**
+     * The DB driver type (mysql, oci8, odbc, etc.)
+     * @var string
+     */
+    var $phptype = 'sybase';
+
+    /**
+     * The database syntax variant to be used (db2, access, etc.), if any
+     * @var string
+     */
+    var $dbsyntax = 'sybase';
+
+    /**
+     * The capabilities of this DB implementation
+     *
+     * The 'new_link' element contains the PHP version that first provided
+     * new_link support for this DBMS.  Contains false if it's unsupported.
+     *
+     * Meaning of the 'limit' element:
+     *   + 'emulate' = emulate with fetch row by number
+     *   + 'alter'   = alter the query
+     *   + false     = skip rows
+     *
+     * @var array
+     */
+    var $features = array(
+        'limit'         => 'emulate',
+        'new_link'      => false,
+        'numrows'       => true,
+        'pconnect'      => true,
+        'prepare'       => false,
+        'ssl'           => false,
+        'transactions'  => true,
+    );
+
+    /**
+     * A mapping of native error codes to DB error codes
+     * @var array
+     */
+    var $errorcode_map = array(
+    );
+
+    /**
+     * The raw database connection created by PHP
+     * @var resource
+     */
+    var $connection;
+
+    /**
+     * The DSN information for connecting to a database
+     * @var array
+     */
+    var $dsn = array();
+
+
+    /**
+     * Should data manipulation queries be committed automatically?
+     * @var bool
+     * @access private
+     */
+    var $autocommit = true;
+
+    /**
+     * The quantity of transactions begun
+     *
+     * {@internal  While this is private, it can't actually be designated
+     * private in PHP 5 because it is directly accessed in the test suite.}}
+     *
+     * @var integer
+     * @access private
+     */
+    var $transaction_opcount = 0;
+
+    /**
+     * The database specified in the DSN
+     *
+     * It's a fix to allow calls to different databases in the same script.
+     *
+     * @var string
+     * @access private
+     */
+    var $_db = '';
+
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * This constructor calls <kbd>$this->DB_common()</kbd>
+     *
+     * @return void
+     */
+    function DB_sybase()
+    {
+        $this->DB_common();
+    }
+
+    // }}}
+    // {{{ connect()
+
+    /**
+     * Connect to the database server, log in and open the database
+     *
+     * Don't call this method directly.  Use DB::connect() instead.
+     *
+     * PEAR DB's sybase driver supports the following extra DSN options:
+     *   + appname       The application name to use on this connection.
+     *                   Available since PEAR DB 1.7.0.
+     *   + charset       The character set to use on this connection.
+     *                   Available since PEAR DB 1.7.0.
+     *
+     * @param array $dsn         the data source name
+     * @param bool  $persistent  should the connection be persistent?
+     *
+     * @return int  DB_OK on success. A DB_Error object on failure.
+     */
+    function connect($dsn, $persistent = false)
+    {
+        if (!PEAR::loadExtension('sybase') &&
+            !PEAR::loadExtension('sybase_ct'))
+        {
+            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+        }
+
+        $this->dsn = $dsn;
+        if ($dsn['dbsyntax']) {
+            $this->dbsyntax = $dsn['dbsyntax'];
+        }
+
+        $dsn['hostspec'] = $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost';
+        $dsn['password'] = !empty($dsn['password']) ? $dsn['password'] : false;
+        $dsn['charset'] = isset($dsn['charset']) ? $dsn['charset'] : false;
+        $dsn['appname'] = isset($dsn['appname']) ? $dsn['appname'] : false;
+
+        $connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect';
+
+        if ($dsn['username']) {
+            $this->connection = @$connect_function($dsn['hostspec'],
+                                                   $dsn['username'],
+                                                   $dsn['password'],
+                                                   $dsn['charset'],
+                                                   $dsn['appname']);
+        } else {
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     'The DSN did not contain a username.');
+        }
+
+        if (!$this->connection) {
+            return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+                                     null, null, null,
+                                     @sybase_get_last_message());
+        }
+
+        if ($dsn['database']) {
+            if (!@sybase_select_db($dsn['database'], $this->connection)) {
+                return $this->raiseError(DB_ERROR_NODBSELECTED,
+                                         null, null, null,
+                                         @sybase_get_last_message());
+            }
+            $this->_db = $dsn['database'];
+        }
+
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ disconnect()
+
+    /**
+     * Disconnects from the database server
+     *
+     * @return bool  TRUE on success, FALSE on failure
+     */
+    function disconnect()
+    {
+        $ret = @sybase_close($this->connection);
+        $this->connection = null;
+        return $ret;
+    }
+
+    // }}}
+    // {{{ simpleQuery()
+
+    /**
+     * Sends a query to the database server
+     *
+     * @param string  the SQL query string
+     *
+     * @return mixed  + a PHP result resrouce for successful SELECT queries
+     *                + the DB_OK constant for other successful queries
+     *                + a DB_Error object on failure
+     */
+    function simpleQuery($query)
+    {
+        $ismanip = DB::isManip($query);
+        $this->last_query = $query;
+        if (!@sybase_select_db($this->_db, $this->connection)) {
+            return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+        }
+        $query = $this->modifyQuery($query);
+        if (!$this->autocommit && $ismanip) {
+            if ($this->transaction_opcount == 0) {
+                $result = @sybase_query('BEGIN TRANSACTION', $this->connection);
+                if (!$result) {
+                    return $this->sybaseRaiseError();
+                }
+            }
+            $this->transaction_opcount++;
+        }
+        $result = @sybase_query($query, $this->connection);
+        if (!$result) {
+            return $this->sybaseRaiseError();
+        }
+        if (is_resource($result)) {
+            return $result;
+        }
+        // Determine which queries that should return data, and which
+        // should return an error code only.
+        return $ismanip ? DB_OK : $result;
+    }
+
+    // }}}
+    // {{{ nextResult()
+
+    /**
+     * Move the internal sybase result pointer to the next available result
+     *
+     * @param a valid sybase result resource
+     *
+     * @access public
+     *
+     * @return true if a result is available otherwise return false
+     */
+    function nextResult($result)
+    {
+        return false;
+    }
+
+    // }}}
+    // {{{ fetchInto()
+
+    /**
+     * Places a row from the result set into the given array
+     *
+     * Formating of the array and the data therein are configurable.
+     * See DB_result::fetchInto() for more information.
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::fetchInto() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result    the query result resource
+     * @param array    $arr       the referenced array to put the data in
+     * @param int      $fetchmode how the resulting array should be indexed
+     * @param int      $rownum    the row number to fetch (0 = first row)
+     *
+     * @return mixed  DB_OK on success, NULL when the end of a result set is
+     *                 reached or on failure
+     *
+     * @see DB_result::fetchInto()
+     */
+    function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+    {
+        if ($rownum !== null) {
+            if (!@sybase_data_seek($result, $rownum)) {
+                return null;
+            }
+        }
+        if ($fetchmode & DB_FETCHMODE_ASSOC) {
+            if (function_exists('sybase_fetch_assoc')) {
+                $arr = @sybase_fetch_assoc($result);
+            } else {
+                if ($arr = @sybase_fetch_array($result)) {
+                    foreach ($arr as $key => $value) {
+                        if (is_int($key)) {
+                            unset($arr[$key]);
+                        }
+                    }
+                }
+            }
+            if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+                $arr = array_change_key_case($arr, CASE_LOWER);
+            }
+        } else {
+            $arr = @sybase_fetch_row($result);
+        }
+        if (!$arr) {
+            return null;
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+            $this->_rtrimArrayValues($arr);
+        }
+        if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+            $this->_convertNullArrayValuesToEmpty($arr);
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ freeResult()
+
+    /**
+     * Deletes the result set and frees the memory occupied by the result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::free() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return bool  TRUE on success, FALSE if $result is invalid
+     *
+     * @see DB_result::free()
+     */
+    function freeResult($result)
+    {
+        return @sybase_free_result($result);
+    }
+
+    // }}}
+    // {{{ numCols()
+
+    /**
+     * Gets the number of columns in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numCols() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of columns.  A DB_Error object on failure.
+     *
+     * @see DB_result::numCols()
+     */
+    function numCols($result)
+    {
+        $cols = @sybase_num_fields($result);
+        if (!$cols) {
+            return $this->sybaseRaiseError();
+        }
+        return $cols;
+    }
+
+    // }}}
+    // {{{ numRows()
+
+    /**
+     * Gets the number of rows in a result set
+     *
+     * This method is not meant to be called directly.  Use
+     * DB_result::numRows() instead.  It can't be declared "protected"
+     * because DB_result is a separate object.
+     *
+     * @param resource $result  PHP's query result resource
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     *
+     * @see DB_result::numRows()
+     */
+    function numRows($result)
+    {
+        $rows = @sybase_num_rows($result);
+        if ($rows === false) {
+            return $this->sybaseRaiseError();
+        }
+        return $rows;
+    }
+
+    // }}}
+    // {{{ affectedRows()
+
+    /**
+     * Determines the number of rows affected by a data maniuplation query
+     *
+     * 0 is returned for queries that don't manipulate data.
+     *
+     * @return int  the number of rows.  A DB_Error object on failure.
+     */
+    function affectedRows()
+    {
+        if (DB::isManip($this->last_query)) {
+            $result = @sybase_affected_rows($this->connection);
+        } else {
+            $result = 0;
+        }
+        return $result;
+     }
+
+    // }}}
+    // {{{ nextId()
+
+    /**
+     * Returns the next free id in a sequence
+     *
+     * @param string  $seq_name  name of the sequence
+     * @param boolean $ondemand  when true, the seqence is automatically
+     *                            created if it does not exist
+     *
+     * @return int  the next id number in the sequence.
+     *               A DB_Error object on failure.
+     *
+     * @see DB_common::nextID(), DB_common::getSequenceName(),
+     *      DB_sybase::createSequence(), DB_sybase::dropSequence()
+     */
+    function nextId($seq_name, $ondemand = true)
+    {
+        $seqname = $this->getSequenceName($seq_name);
+        if (!@sybase_select_db($this->_db, $this->connection)) {
+            return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+        }
+        $repeat = 0;
+        do {
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
+            $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
+            $this->popErrorHandling();
+            if ($ondemand && DB::isError($result) &&
+                ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
+            {
+                $repeat = 1;
+                $result = $this->createSequence($seq_name);
+                if (DB::isError($result)) {
+                    return $this->raiseError($result);
+                }
+            } elseif (!DB::isError($result)) {
+                $result =& $this->query("SELECT @@IDENTITY FROM $seqname");
+                $repeat = 0;
+            } else {
+                $repeat = false;
+            }
+        } while ($repeat);
+        if (DB::isError($result)) {
+            return $this->raiseError($result);
+        }
+        $result = $result->fetchRow(DB_FETCHMODE_ORDERED);
+        return $result[0];
+    }
+
+    /**
+     * Creates a new sequence
+     *
+     * @param string $seq_name  name of the new sequence
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::createSequence(), DB_common::getSequenceName(),
+     *      DB_sybase::nextID(), DB_sybase::dropSequence()
+     */
+    function createSequence($seq_name)
+    {
+        return $this->query('CREATE TABLE '
+                            . $this->getSequenceName($seq_name)
+                            . ' (id numeric(10, 0) IDENTITY NOT NULL,'
+                            . ' vapor int NULL)');
+    }
+
+    // }}}
+    // {{{ dropSequence()
+
+    /**
+     * Deletes a sequence
+     *
+     * @param string $seq_name  name of the sequence to be deleted
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     *
+     * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+     *      DB_sybase::nextID(), DB_sybase::createSequence()
+     */
+    function dropSequence($seq_name)
+    {
+        return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+    }
+
+    // }}}
+    // {{{ autoCommit()
+
+    /**
+     * Enables or disables automatic commits
+     *
+     * @param bool $onoff  true turns it on, false turns it off
+     *
+     * @return int  DB_OK on success.  A DB_Error object if the driver
+     *               doesn't support auto-committing transactions.
+     */
+    function autoCommit($onoff = false)
+    {
+        // XXX if $this->transaction_opcount > 0, we should probably
+        // issue a warning here.
+        $this->autocommit = $onoff ? true : false;
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ commit()
+
+    /**
+     * Commits the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function commit()
+    {
+        if ($this->transaction_opcount > 0) {
+            if (!@sybase_select_db($this->_db, $this->connection)) {
+                return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+            }
+            $result = @sybase_query('COMMIT', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->sybaseRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ rollback()
+
+    /**
+     * Reverts the current transaction
+     *
+     * @return int  DB_OK on success.  A DB_Error object on failure.
+     */
+    function rollback()
+    {
+        if ($this->transaction_opcount > 0) {
+            if (!@sybase_select_db($this->_db, $this->connection)) {
+                return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+            }
+            $result = @sybase_query('ROLLBACK', $this->connection);
+            $this->transaction_opcount = 0;
+            if (!$result) {
+                return $this->sybaseRaiseError();
+            }
+        }
+        return DB_OK;
+    }
+
+    // }}}
+    // {{{ sybaseRaiseError()
+
+    /**
+     * Produces a DB_Error object regarding the current problem
+     *
+     * @param int $errno  if the error is being manually raised pass a
+     *                     DB_ERROR* constant here.  If this isn't passed
+     *                     the error information gathered from the DBMS.
+     *
+     * @return object  the DB_Error object
+     *
+     * @see DB_common::raiseError(),
+     *      DB_sybase::errorNative(), DB_sybase::errorCode()
+     */
+    function sybaseRaiseError($errno = null)
+    {
+        $native = $this->errorNative();
+        if ($errno === null) {
+            $errno = $this->errorCode($native);
+        }
+        return $this->raiseError($errno, null, null, null, $native);
+    }
+
+    // }}}
+    // {{{ errorNative()
+
+    /**
+     * Gets the DBMS' native error message produced by the last query
+     *
+     * @return string  the DBMS' error message
+     */
+    function errorNative()
+    {
+        return @sybase_get_last_message();
+    }
+
+    // }}}
+    // {{{ errorCode()
+
+    /**
+     * Determines PEAR::DB error code from the database's text error message.
+     *
+     * @param  string  $errormsg  error message returned from the database
+     * @return integer  an error number from a DB error constant
+     */
+    function errorCode($errormsg)
+    {
+        static $error_regexps;
+        if (!isset($error_regexps)) {
+            $error_regexps = array(
+                '/Incorrect syntax near/'
+                    => DB_ERROR_SYNTAX,
+                '/^Unclosed quote before the character string [\"\'].*[\"\']\./'
+                    => DB_ERROR_SYNTAX,
+                '/Implicit conversion (from datatype|of NUMERIC value)/i'
+                    => DB_ERROR_INVALID_NUMBER,
+                '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./'
+                    => DB_ERROR_NOSUCHTABLE,
+                '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/^.+ permission denied on object .+, database .+, owner .+/'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/^.* permission denied, database .+, owner .+/'
+                    => DB_ERROR_ACCESS_VIOLATION,
+                '/[^.*] not found\./'
+                    => DB_ERROR_NOSUCHTABLE,
+                '/There is already an object named/'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/Invalid column name/'
+                    => DB_ERROR_NOSUCHFIELD,
+                '/does not allow null values/'
+                    => DB_ERROR_CONSTRAINT_NOT_NULL,
+                '/Command has been aborted/'
+                    => DB_ERROR_CONSTRAINT,
+                '/^Cannot drop the index .* because it doesn\'t exist/i'
+                    => DB_ERROR_NOT_FOUND,
+                '/^There is already an index/i'
+                    => DB_ERROR_ALREADY_EXISTS,
+                '/^There are fewer columns in the INSERT statement than values specified/i'
+                    => DB_ERROR_VALUE_COUNT_ON_ROW,
+            );
+        }
+
+        foreach ($error_regexps as $regexp => $code) {
+            if (preg_match($regexp, $errormsg)) {
+                return $code;
+            }
+        }
+        return DB_ERROR;
+    }
+
+    // }}}
+    // {{{ tableInfo()
+
+    /**
+     * Returns information about a table or a result set
+     *
+     * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+     * is a table name.
+     *
+     * @param object|string  $result  DB_result object from a query or a
+     *                                 string containing the name of a table.
+     *                                 While this also accepts a query result
+     *                                 resource identifier, this behavior is
+     *                                 deprecated.
+     * @param int            $mode    a valid tableInfo mode
+     *
+     * @return array  an associative array with the information requested.
+     *                 A DB_Error object on failure.
+     *
+     * @see DB_common::tableInfo()
+     * @since Method available since Release 1.6.0
+     */
+    function tableInfo($result, $mode = null)
+    {
+        if (is_string($result)) {
+            /*
+             * Probably received a table name.
+             * Create a result resource identifier.
+             */
+            if (!@sybase_select_db($this->_db, $this->connection)) {
+                return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+            }
+            $id = @sybase_query("SELECT * FROM $result WHERE 1=0",
+                                $this->connection);
+            $got_string = true;
+        } elseif (isset($result->result)) {
+            /*
+             * Probably received a result object.
+             * Extract the result resource identifier.
+             */
+            $id = $result->result;
+            $got_string = false;
+        } else {
+            /*
+             * Probably received a result resource identifier.
+             * Copy it.
+             * Deprecated.  Here for compatibility only.
+             */
+            $id = $result;
+            $got_string = false;
+        }
+
+        if (!is_resource($id)) {
+            return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA);
+        }
+
+        if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+            $case_func = 'strtolower';
+        } else {
+            $case_func = 'strval';
+        }
+
+        $count = @sybase_num_fields($id);
+        $res   = array();
+
+        if ($mode) {
+            $res['num_fields'] = $count;
+        }
+
+        for ($i = 0; $i < $count; $i++) {
+            $f = @sybase_fetch_field($id, $i);
+            // column_source is often blank
+            $res[$i] = array(
+                'table' => $got_string
+                           ? $case_func($result)
+                           : $case_func($f->column_source),
+                'name'  => $case_func($f->name),
+                'type'  => $f->type,
+                'len'   => $f->max_length,
+                'flags' => '',
+            );
+            if ($res[$i]['table']) {
+                $res[$i]['flags'] = $this->_sybase_field_flags(
+                        $res[$i]['table'], $res[$i]['name']);
+            }
+            if ($mode & DB_TABLEINFO_ORDER) {
+                $res['order'][$res[$i]['name']] = $i;
+            }
+            if ($mode & DB_TABLEINFO_ORDERTABLE) {
+                $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+            }
+        }
+
+        // free the result only if we were called on a table
+        if ($got_string) {
+            @sybase_free_result($id);
+        }
+        return $res;
+    }
+
+    // }}}
+    // {{{ _sybase_field_flags()
+
+    /**
+     * Get the flags for a field
+     *
+     * Currently supports:
+     *  + <samp>unique_key</samp>    (unique index, unique check or primary_key)
+     *  + <samp>multiple_key</samp>  (multi-key index)
+     *
+     * @param string  $table   the table name
+     * @param string  $column  the field name
+     *
+     * @return string  space delimited string of flags.  Empty string if none.
+     *
+     * @access private
+     */
+    function _sybase_field_flags($table, $column)
+    {
+        static $tableName = null;
+        static $flags = array();
+
+        if ($table != $tableName) {
+            $flags = array();
+            $tableName = $table;
+
+            // get unique/primary keys
+            $res = $this->getAll("sp_helpindex $table", DB_FETCHMODE_ASSOC);
+
+            if (!isset($res[0]['index_description'])) {
+                return '';
+            }
+
+            foreach ($res as $val) {
+                $keys = explode(', ', trim($val['index_keys']));
+
+                if (sizeof($keys) > 1) {
+                    foreach ($keys as $key) {
+                        $this->_add_flag($flags[$key], 'multiple_key');
+                    }
+                }
+
+                if (strpos($val['index_description'], 'unique')) {
+                    foreach ($keys as $key) {
+                        $this->_add_flag($flags[$key], 'unique_key');
+                    }
+                }
+            }
+
+        }
+
+        if (array_key_exists($column, $flags)) {
+            return(implode(' ', $flags[$column]));
+        }
+
+        return '';
+    }
+
+    // }}}
+    // {{{ _add_flag()
+
+    /**
+     * Adds a string to the flags array if the flag is not yet in there
+     * - if there is no flag present the array is created
+     *
+     * @param array  $array  reference of flags array to add a value to
+     * @param mixed  $value  value to add to the flag array
+     *
+     * @return void
+     *
+     * @access private
+     */
+    function _add_flag(&$array, $value)
+    {
+        if (!is_array($array)) {
+            $array = array($value);
+        } elseif (!in_array($value, $array)) {
+            array_push($array, $value);
+        }
+    }
+
+    // }}}
+    // {{{ getSpecialQuery()
+
+    /**
+     * Obtains the query string needed for listing a given type of objects
+     *
+     * @param string $type  the kind of objects you want to retrieve
+     *
+     * @return string  the SQL query string or null if the driver doesn't
+     *                  support the object type requested
+     *
+     * @access protected
+     * @see DB_common::getListOf()
+     */
+    function getSpecialQuery($type)
+    {
+        switch ($type) {
+            case 'tables':
+                return "SELECT name FROM sysobjects WHERE type = 'U'"
+                       . ' ORDER BY name';
+            case 'views':
+                return "SELECT name FROM sysobjects WHERE type = 'V'";
+            default:
+                return null;
+        }
+    }
+
+    // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php
new file mode 100644 (file)
index 0000000..74e481d
--- /dev/null
@@ -0,0 +1,912 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+// +-----------------------------------------------------------------------+
+// | Copyright (c) 2002-2003  Richard Heyes                                |
+// | Copyright (c) 2003-2005  The PHP Group                                |
+// | All rights reserved.                                                  |
+// |                                                                       |
+// | Redistribution and use in source and binary forms, with or without    |
+// | modification, are permitted provided that the following conditions    |
+// | are met:                                                              |
+// |                                                                       |
+// | o Redistributions of source code must retain the above copyright      |
+// |   notice, this list of conditions and the following disclaimer.       |
+// | o Redistributions in binary form must reproduce the above copyright   |
+// |   notice, this list of conditions and the following disclaimer in the |
+// |   documentation and/or other materials provided with the distribution.|
+// | o The names of the authors may not be used to endorse or promote      |
+// |   products derived from this software without specific prior written  |
+// |   permission.                                                         |
+// |                                                                       |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
+// |                                                                       |
+// +-----------------------------------------------------------------------+
+// | Author: Richard Heyes <richard@phpguru.org>                           |
+// |         Tomas V.V.Cox <cox@idecnet.com> (port to PEAR)                |
+// +-----------------------------------------------------------------------+
+//
+// $Id: mime.php 260 2006-06-09 16:47:21Z afladmark $
+
+require_once('PEAR.php');
+require_once('Mail/mimePart.php');
+
+/**
+ * Mime mail composer class. Can handle: text and html bodies, embedded html
+ * images and attachments.
+ * Documentation and examples of this class are avaible here:
+ * http://pear.php.net/manual/
+ *
+ * @notes This class is based on HTML Mime Mail class from
+ *   Richard Heyes <richard@phpguru.org> which was based also
+ *   in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it> and
+ *   Sascha Schumann <sascha@schumann.cx>
+ *
+ * @author   Richard Heyes <richard.heyes@heyes-computing.net>
+ * @author   Tomas V.V.Cox <cox@idecnet.com>
+ * @package  Mail
+ * @access   public
+ */
+class Mail_mime
+{
+    /**
+     * Contains the plain text part of the email
+     * @var string
+     */
+    var $_txtbody;
+    /**
+     * Contains the html part of the email
+     * @var string
+     */
+    var $_htmlbody;
+    /**
+     * contains the mime encoded text
+     * @var string
+     */
+    var $_mime;
+    /**
+     * contains the multipart content
+     * @var string
+     */
+    var $_multipart;
+    /**
+     * list of the attached images
+     * @var array
+     */
+    var $_html_images = array();
+    /**
+     * list of the attachements
+     * @var array
+     */
+    var $_parts = array();
+    /**
+     * Build parameters
+     * @var array
+     */
+    var $_build_params = array();
+    /**
+     * Headers for the mail
+     * @var array
+     */
+    var $_headers = array();
+    /**
+     * End Of Line sequence (for serialize)
+     * @var string
+     */
+    var $_eol;
+
+
+    /**
+     * Constructor function
+     *
+     * @access public
+     */
+    function Mail_mime($crlf = "\r\n")
+    {
+        $this->_setEOL($crlf);
+        $this->_build_params = array(
+                                     'head_encoding' => 'quoted-printable',
+                                     'text_encoding' => '7bit',
+                                     'html_encoding' => 'quoted-printable',
+                                     '7bit_wrap'     => 998,
+                                     'html_charset'  => 'ISO-8859-1',
+                                     'text_charset'  => 'ISO-8859-1',
+                                     'head_charset'  => 'ISO-8859-1'
+                                    );
+    }
+
+    /**
+     * Wakeup (unserialize) - re-sets EOL constant
+     *
+     * @access private
+     */
+    function __wakeup()
+    {
+        $this->_setEOL($this->_eol);
+    }
+
+    /**
+     * Accessor function to set the body text. Body text is used if
+     * it's not an html mail being sent or else is used to fill the
+     * text/plain part that emails clients who don't support
+     * html should show.
+     *
+     * @param  string  $data   Either a string or
+     *                         the file name with the contents
+     * @param  bool    $isfile If true the first param should be treated
+     *                         as a file name, else as a string (default)
+     * @param  bool    $append If true the text or file is appended to
+     *                         the existing body, else the old body is
+     *                         overwritten
+     * @return mixed   true on success or PEAR_Error object
+     * @access public
+     */
+    function setTXTBody($data, $isfile = false, $append = false)
+    {
+        if (!$isfile) {
+            if (!$append) {
+                $this->_txtbody = $data;
+            } else {
+                $this->_txtbody .= $data;
+            }
+        } else {
+            $cont = $this->_file2str($data);
+            if (PEAR::isError($cont)) {
+                return $cont;
+            }
+            if (!$append) {
+                $this->_txtbody = $cont;
+            } else {
+                $this->_txtbody .= $cont;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Adds a html part to the mail
+     *
+     * @param  string  $data   Either a string or the file name with the
+     *                         contents
+     * @param  bool    $isfile If true the first param should be treated
+     *                         as a file name, else as a string (default)
+     * @return mixed   true on success or PEAR_Error object
+     * @access public
+     */
+    function setHTMLBody($data, $isfile = false)
+    {
+        if (!$isfile) {
+            $this->_htmlbody = $data;
+        } else {
+            $cont = $this->_file2str($data);
+            if (PEAR::isError($cont)) {
+                return $cont;
+            }
+            $this->_htmlbody = $cont;
+        }
+
+        return true;
+    }
+
+    /**
+     * Adds an image to the list of embedded images.
+     *
+     * @param  string  $file       The image file name OR image data itself
+     * @param  string  $c_type     The content type
+     * @param  string  $name       The filename of the image.
+     *                             Only use if $file is the image data
+     * @param  bool    $isfilename Whether $file is a filename or not
+     *                             Defaults to true
+     * @return mixed   true on success or PEAR_Error object
+     * @access public
+     */
+    function addHTMLImage($file, $c_type='application/octet-stream',
+                          $name = '', $isfilename = true)
+    {
+        $filedata = ($isfilename === true) ? $this->_file2str($file)
+                                           : $file;
+        if ($isfilename === true) {
+            $filename = ($name == '' ? $file : $name);
+        } else {
+            $filename = $name;
+        }
+        if (PEAR::isError($filedata)) {
+            return $filedata;
+        }
+        $this->_html_images[] = array(
+                                      'body'   => $filedata,
+                                      'name'   => $filename,
+                                      'c_type' => $c_type,
+                                      'cid'    => md5(uniqid(time()))
+                                     );
+        return true;
+    }
+
+    /**
+     * Adds a file to the list of attachments.
+     *
+     * @param  string  $file        The file name of the file to attach
+     *                              OR the file contents itself
+     * @param  string  $c_type      The content type
+     * @param  string  $name        The filename of the attachment
+     *                              Only use if $file is the contents
+     * @param  bool    $isFilename  Whether $file is a filename or not
+     *                              Defaults to true
+     * @param  string  $encoding    The type of encoding to use.
+     *                              Defaults to base64.
+     *                              Possible values: 7bit, 8bit, base64, 
+     *                              or quoted-printable.
+     * @param  string  $disposition The content-disposition of this file
+     *                              Defaults to attachment.
+     *                              Possible values: attachment, inline.
+     * @param  string  $charset     The character set used in the filename
+     *                              of this attachment.
+     * @return mixed true on success or PEAR_Error object
+     * @access public
+     */
+    function addAttachment($file, $c_type = 'application/octet-stream',
+                           $name = '', $isfilename = true,
+                           $encoding = 'base64',
+                           $disposition = 'attachment', $charset = '')
+    {
+        $filedata = ($isfilename === true) ? $this->_file2str($file)
+                                           : $file;
+        if ($isfilename === true) {
+            // Force the name the user supplied, otherwise use $file
+            $filename = (!empty($name)) ? $name : $file;
+        } else {
+            $filename = $name;
+        }
+        if (empty($filename)) {
+            $err = PEAR::raiseError(
+              "The supplied filename for the attachment can't be empty"
+            );
+           return $err;
+        }
+        $filename = basename($filename);
+        if (PEAR::isError($filedata)) {
+            return $filedata;
+        }
+
+        $this->_parts[] = array(
+                                'body'        => $filedata,
+                                'name'        => $filename,
+                                'c_type'      => $c_type,
+                                'encoding'    => $encoding,
+                                'charset'     => $charset,
+                                'disposition' => $disposition
+                               );
+        return true;
+    }
+
+    /**
+     * Get the contents of the given file name as string
+     *
+     * @param  string  $file_name  path of file to process
+     * @return string  contents of $file_name
+     * @access private
+     */
+    function &_file2str($file_name)
+    {
+        if (!is_readable($file_name)) {
+            $err = PEAR::raiseError('File is not readable ' . $file_name);
+            return $err;
+        }
+        if (!$fd = fopen($file_name, 'rb')) {
+            $err = PEAR::raiseError('Could not open ' . $file_name);
+            return $err;
+        }
+        $filesize = filesize($file_name);
+        if ($filesize == 0){
+            $cont =  "";
+        }else{
+            if ($magic_quote_setting = get_magic_quotes_runtime()){
+                set_magic_quotes_runtime(0);
+            }
+            $cont = fread($fd, $filesize);
+            if ($magic_quote_setting){
+                set_magic_quotes_runtime($magic_quote_setting);
+            }
+        }
+        fclose($fd);
+        return $cont;
+    }
+
+    /**
+     * Adds a text subpart to the mimePart object and
+     * returns it during the build process.
+     *
+     * @param mixed    The object to add the part to, or
+     *                 null if a new object is to be created.
+     * @param string   The text to add.
+     * @return object  The text mimePart object
+     * @access private
+     */
+    function &_addTextPart(&$obj, $text)
+    {
+        $params['content_type'] = 'text/plain';
+        $params['encoding']     = $this->_build_params['text_encoding'];
+        $params['charset']      = $this->_build_params['text_charset'];
+        if (is_object($obj)) {
+            $ret = $obj->addSubpart($text, $params);
+            return $ret;
+        } else {
+            $ret = new Mail_mimePart($text, $params);
+            return $ret;
+        }
+    }
+
+    /**
+     * Adds a html subpart to the mimePart object and
+     * returns it during the build process.
+     *
+     * @param  mixed   The object to add the part to, or
+     *                 null if a new object is to be created.
+     * @return object  The html mimePart object
+     * @access private
+     */
+    function &_addHtmlPart(&$obj)
+    {
+        $params['content_type'] = 'text/html';
+        $params['encoding']     = $this->_build_params['html_encoding'];
+        $params['charset']      = $this->_build_params['html_charset'];
+        if (is_object($obj)) {
+            $ret = $obj->addSubpart($this->_htmlbody, $params);
+            return $ret;
+        } else {
+            $ret = new Mail_mimePart($this->_htmlbody, $params);
+            return $ret;
+        }
+    }
+
+    /**
+     * Creates a new mimePart object, using multipart/mixed as
+     * the initial content-type and returns it during the
+     * build process.
+     *
+     * @return object  The multipart/mixed mimePart object
+     * @access private
+     */
+    function &_addMixedPart()
+    {
+        $params['content_type'] = 'multipart/mixed';
+        $ret = new Mail_mimePart('', $params);
+        return $ret;
+    }
+
+    /**
+     * Adds a multipart/alternative part to a mimePart
+     * object (or creates one), and returns it during
+     * the build process.
+     *
+     * @param  mixed   The object to add the part to, or
+     *                 null if a new object is to be created.
+     * @return object  The multipart/mixed mimePart object
+     * @access private
+     */
+    function &_addAlternativePart(&$obj)
+    {
+        $params['content_type'] = 'multipart/alternative';
+        if (is_object($obj)) {
+            return $obj->addSubpart('', $params);
+        } else {
+            $ret = new Mail_mimePart('', $params);
+            return $ret;
+        }
+    }
+
+    /**
+     * Adds a multipart/related part to a mimePart
+     * object (or creates one), and returns it during
+     * the build process.
+     *
+     * @param mixed    The object to add the part to, or
+     *                 null if a new object is to be created
+     * @return object  The multipart/mixed mimePart object
+     * @access private
+     */
+    function &_addRelatedPart(&$obj)
+    {
+        $params['content_type'] = 'multipart/related';
+        if (is_object($obj)) {
+            return $obj->addSubpart('', $params);
+        } else {
+            $ret = new Mail_mimePart('', $params);
+            return $ret;
+        }
+    }
+
+    /**
+     * Adds an html image subpart to a mimePart object
+     * and returns it during the build process.
+     *
+     * @param  object  The mimePart to add the image to
+     * @param  array   The image information
+     * @return object  The image mimePart object
+     * @access private
+     */
+    function &_addHtmlImagePart(&$obj, $value)
+    {
+        $params['content_type'] = $value['c_type'] . '; ' .
+                                  'name="' . $value['name'] . '"';
+        $params['encoding']     = 'base64';
+        $params['disposition']  = 'inline';
+        $params['dfilename']    = $value['name'];
+        $params['cid']          = $value['cid'];
+        $ret = $obj->addSubpart($value['body'], $params);
+        return $ret;
+       
+    }
+
+    /**
+     * Adds an attachment subpart to a mimePart object
+     * and returns it during the build process.
+     *
+     * @param  object  The mimePart to add the image to
+     * @param  array   The attachment information
+     * @return object  The image mimePart object
+     * @access private
+     */
+    function &_addAttachmentPart(&$obj, $value)
+    {
+        $params['dfilename']    = $value['name'];
+        $params['encoding']     = $value['encoding'];
+        if ($value['disposition'] != "inline") {
+            $fname = array("fname" => $value['name']);
+            $fname_enc = $this->_encodeHeaders($fname);
+            $params['dfilename'] = $fname_enc['fname'];
+        }
+        if ($value['charset']) {
+            $params['charset'] = $value['charset'];
+        }
+        $params['content_type'] = $value['c_type'] . '; ' .
+                                  'name="' . $params['dfilename'] . '"';
+        $params['disposition']  = isset($value['disposition']) ? 
+                                  $value['disposition'] : 'attachment';
+        $ret = $obj->addSubpart($value['body'], $params);
+        return $ret;
+    }
+
+    /**
+     * Returns the complete e-mail, ready to send using an alternative
+     * mail delivery method. Note that only the mailpart that is made
+     * with Mail_Mime is created. This means that,
+     * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF 
+     * using the $xtra_headers parameter!
+     * 
+     * @param  string $separation   The separation etween these two parts.
+     * @param  array  $build_params The Build parameters passed to the
+     *                              &get() function. See &get for more info.
+     * @param  array  $xtra_headers The extra headers that should be passed
+     *                              to the &headers() function.
+     *                              See that function for more info.
+     * @param  bool   $overwrite    Overwrite the existing headers with new.
+     * @return string The complete e-mail.
+     * @access public
+     */
+    function getMessage($separation = null, $build_params = null, $xtra_headers = null, $overwrite = false)
+    {
+        if ($separation === null)
+        {
+            $separation = MAIL_MIME_CRLF;
+        }
+        $body = $this->get($build_params);
+        $head = $this->txtHeaders($xtra_headers, $overwrite);
+        $mail = $head . $separation . $body;
+        return $mail;
+    }
+
+
+    /**
+     * Builds the multipart message from the list ($this->_parts) and
+     * returns the mime content.
+     *
+     * @param  array  Build parameters that change the way the email
+     *                is built. Should be associative. Can contain:
+     *                head_encoding  -  What encoding to use for the headers. 
+     *                                  Options: quoted-printable or base64
+     *                                  Default is quoted-printable
+     *                text_encoding  -  What encoding to use for plain text
+     *                                  Options: 7bit, 8bit, base64, or quoted-printable
+     *                                  Default is 7bit
+     *                html_encoding  -  What encoding to use for html
+     *                                  Options: 7bit, 8bit, base64, or quoted-printable
+     *                                  Default is quoted-printable
+     *                7bit_wrap      -  Number of characters before text is
+     *                                  wrapped in 7bit encoding
+     *                                  Default is 998
+     *                html_charset   -  The character set to use for html.
+     *                                  Default is iso-8859-1
+     *                text_charset   -  The character set to use for text.
+     *                                  Default is iso-8859-1
+     *                head_charset   -  The character set to use for headers.
+     *                                  Default is iso-8859-1
+     * @return string The mime content
+     * @access public
+     */
+    function &get($build_params = null)
+    {
+        if (isset($build_params)) {
+            while (list($key, $value) = each($build_params)) {
+                $this->_build_params[$key] = $value;
+            }
+        }
+
+        if (!empty($this->_html_images) AND isset($this->_htmlbody)) {
+            foreach ($this->_html_images as $key => $value) {
+                $regex = array();
+                $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' .
+                            preg_quote($value['name'], '#') . '\3#';
+                $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' .
+                            preg_quote($value['name'], '#') . '\1\s*\)#';
+                $rep = array();
+                $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3';
+                $rep[] = 'url(\1cid:' . $value['cid'] . '\2)';
+                $this->_htmlbody = preg_replace($regex, $rep,
+                                       $this->_htmlbody
+                                   );
+                $this->_html_images[$key]['name'] = basename($this->_html_images[$key]['name']);
+            }
+        }
+
+        $null        = null;
+        $attachments = !empty($this->_parts)                ? true : false;
+        $html_images = !empty($this->_html_images)          ? true : false;
+        $html        = !empty($this->_htmlbody)             ? true : false;
+        $text        = (!$html AND !empty($this->_txtbody)) ? true : false;
+
+        switch (true) {
+        case $text AND !$attachments:
+            $message =& $this->_addTextPart($null, $this->_txtbody);
+            break;
+
+        case !$text AND !$html AND $attachments:
+            $message =& $this->_addMixedPart();
+            for ($i = 0; $i < count($this->_parts); $i++) {
+                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            }
+            break;
+
+        case $text AND $attachments:
+            $message =& $this->_addMixedPart();
+            $this->_addTextPart($message, $this->_txtbody);
+            for ($i = 0; $i < count($this->_parts); $i++) {
+                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            }
+            break;
+
+        case $html AND !$attachments AND !$html_images:
+            if (isset($this->_txtbody)) {
+                $message =& $this->_addAlternativePart($null);
+                $this->_addTextPart($message, $this->_txtbody);
+                $this->_addHtmlPart($message);
+            } else {
+                $message =& $this->_addHtmlPart($null);
+            }
+            break;
+
+        case $html AND !$attachments AND $html_images:
+            if (isset($this->_txtbody)) {
+                $message =& $this->_addAlternativePart($null);
+                $this->_addTextPart($message, $this->_txtbody);
+                $related =& $this->_addRelatedPart($message);
+            } else {
+                $message =& $this->_addRelatedPart($null);
+                $related =& $message;
+            }
+            $this->_addHtmlPart($related);
+            for ($i = 0; $i < count($this->_html_images); $i++) {
+                $this->_addHtmlImagePart($related, $this->_html_images[$i]);
+            }
+            break;
+
+        case $html AND $attachments AND !$html_images:
+            $message =& $this->_addMixedPart();
+            if (isset($this->_txtbody)) {
+                $alt =& $this->_addAlternativePart($message);
+                $this->_addTextPart($alt, $this->_txtbody);
+                $this->_addHtmlPart($alt);
+            } else {
+                $this->_addHtmlPart($message);
+            }
+            for ($i = 0; $i < count($this->_parts); $i++) {
+                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            }
+            break;
+
+        case $html AND $attachments AND $html_images:
+            $message =& $this->_addMixedPart();
+            if (isset($this->_txtbody)) {
+                $alt =& $this->_addAlternativePart($message);
+                $this->_addTextPart($alt, $this->_txtbody);
+                $rel =& $this->_addRelatedPart($alt);
+            } else {
+                $rel =& $this->_addRelatedPart($message);
+            }
+            $this->_addHtmlPart($rel);
+            for ($i = 0; $i < count($this->_html_images); $i++) {
+                $this->_addHtmlImagePart($rel, $this->_html_images[$i]);
+            }
+            for ($i = 0; $i < count($this->_parts); $i++) {
+                $this->_addAttachmentPart($message, $this->_parts[$i]);
+            }
+            break;
+
+        }
+
+        if (isset($message)) {
+            $output = $message->encode();
+            $this->_headers = array_merge($this->_headers,
+                                          $output['headers']);
+            $body = $output['body'];
+            return $body;
+
+        } else {
+            $ret = false;
+            return $ret;
+        }
+    }
+
+    /**
+     * Returns an array with the headers needed to prepend to the email
+     * (MIME-Version and Content-Type). Format of argument is:
+     * $array['header-name'] = 'header-value';
+     *
+     * @param  array $xtra_headers Assoc array with any extra headers.
+     *                             Optional.
+     * @param  bool  $overwrite    Overwrite already existing headers.
+     * @return array Assoc array with the mime headers
+     * @access public
+     */
+    function &headers($xtra_headers = null, $overwrite = false)
+    {
+        // Content-Type header should already be present,
+        // So just add mime version header
+        $headers['MIME-Version'] = '1.0';
+        if (isset($xtra_headers)) {
+            $headers = array_merge($headers, $xtra_headers);
+        }
+        if ($overwrite){
+            $this->_headers = array_merge($this->_headers, $headers);
+        }else{
+            $this->_headers = array_merge($headers, $this->_headers);
+        }
+
+        $encodedHeaders = $this->_encodeHeaders($this->_headers);
+        return $encodedHeaders;
+    }
+
+    /**
+     * Get the text version of the headers
+     * (usefull if you want to use the PHP mail() function)
+     *
+     * @param  array   $xtra_headers Assoc array with any extra headers.
+     *                               Optional.
+     * @param  bool    $overwrite    Overwrite the existing heaers with new.
+     * @return string  Plain text headers
+     * @access public
+     */
+    function txtHeaders($xtra_headers = null, $overwrite = false)
+    {
+        $headers = $this->headers($xtra_headers, $overwrite);
+        $ret = '';
+        foreach ($headers as $key => $val) {
+            $ret .= "$key: $val" . MAIL_MIME_CRLF;
+        }
+        return $ret;
+    }
+
+    /**
+     * Sets the Subject header
+     *
+     * @param  string $subject String to set the subject to
+     * access  public
+     */
+    function setSubject($subject)
+    {
+        $this->_headers['Subject'] = $subject;
+    }
+
+    /**
+     * Set an email to the From (the sender) header
+     *
+     * @param  string $email The email direction to add
+     * @access public
+     */
+    function setFrom($email)
+    {
+        $this->_headers['From'] = $email;
+    }
+
+    /**
+     * Add an email to the Cc (carbon copy) header
+     * (multiple calls to this method are allowed)
+     *
+     * @param  string $email The email direction to add
+     * @access public
+     */
+    function addCc($email)
+    {
+        if (isset($this->_headers['Cc'])) {
+            $this->_headers['Cc'] .= ", $email";
+        } else {
+            $this->_headers['Cc'] = $email;
+        }
+    }
+
+    /**
+     * Add an email to the Bcc (blank carbon copy) header
+     * (multiple calls to this method are allowed)
+     *
+     * @param  string $email The email direction to add
+     * @access public
+     */
+    function addBcc($email)
+    {
+        if (isset($this->_headers['Bcc'])) {
+            $this->_headers['Bcc'] .= ", $email";
+        } else {
+            $this->_headers['Bcc'] = $email;
+        }
+    }
+
+    /**
+     * Since the PHP send function requires you to specifiy 
+     * recipients (To: header) separately from the other
+     * headers, the To: header is not properly encoded.
+     * To fix this, you can use this public method to 
+     * encode your recipients before sending to the send
+     * function
+     *
+     * @param  string $recipients A comma-delimited list of recipients
+     * @return string Encoded data
+     * @access public
+     */
+    function encodeRecipients($recipients)
+    {
+        $input = array("To" => $recipients);
+        $retval = $this->_encodeHeaders($input);
+        return $retval["To"] ;
+    }
+
+    /**
+     * Encodes a header as per RFC2047
+     *
+     * @param  array $input The header data to encode
+     * @return array Encoded data
+     * @access private
+     */
+    function _encodeHeaders($input)
+    {
+        foreach ($input as $hdr_name => $hdr_value) {
+            if (function_exists('iconv_mime_encode') && preg_match('#[\x80-\xFF]{1}#', $hdr_value)){
+                $imePref = array();
+                if ($this->_build_params['head_encoding'] == 'base64'){
+                    $imePrefs['scheme'] = 'B';
+                }else{
+                    $imePrefs['scheme'] = 'Q';
+                }
+                $imePrefs['input-charset']  = $this->_build_params['head_charset'];
+                $imePrefs['output-charset'] = $this->_build_params['head_charset'];
+                $hdr_value = iconv_mime_encode($hdr_name, $hdr_value, $imePrefs);
+                $hdr_value = preg_replace("#^{$hdr_name}\:\ #", "", $hdr_value);
+            }elseif (preg_match('#[\x80-\xFF]{1}#', $hdr_value)){
+                //This header contains non ASCII chars and should be encoded.
+                switch ($this->_build_params['head_encoding']) {
+                case 'base64':
+                    //Base64 encoding has been selected.
+                    
+                    //Generate the header using the specified params and dynamicly 
+                    //determine the maximum length of such strings.
+                    //75 is the value specified in the RFC. The -2 is there so 
+                    //the later regexp doesn't break any of the translated chars.
+                    $prefix = '=?' . $this->_build_params['head_charset'] . '?B?';
+                    $suffix = '?=';
+                    $maxLength = 75 - strlen($prefix . $suffix) - 2;
+                    $maxLength1stLine = $maxLength - strlen($hdr_name);
+                    
+                    //Base64 encode the entire string
+                    $hdr_value = base64_encode($hdr_value);
+
+                    //This regexp will break base64-encoded text at every 
+                    //$maxLength but will not break any encoded letters.
+                    $reg1st = "|.{0,$maxLength1stLine}[^\=][^\=]|";
+                    $reg2nd = "|.{0,$maxLength}[^\=][^\=]|";
+                    break;
+                case 'quoted-printable':
+                default:
+                    //quoted-printable encoding has been selected
+                    
+                    //Generate the header using the specified params and dynamicly 
+                    //determine the maximum length of such strings.
+                    //75 is the value specified in the RFC. The -2 is there so 
+                    //the later regexp doesn't break any of the translated chars.
+                    $prefix = '=?' . $this->_build_params['head_charset'] . '?Q?';
+                    $suffix = '?=';
+                    $maxLength = 75 - strlen($prefix . $suffix) - 2;
+                    $maxLength1stLine = $maxLength - strlen($hdr_name);
+                    
+                    //Replace all special characters used by the encoder.
+                    $search  = array("=",   "_",   "?",   " ");
+                    $replace = array("=3D", "=5F", "=3F", "_");
+                    $hdr_value = str_replace($search, $replace, $hdr_value);
+                    
+                    //Replace all extended characters (\x80-xFF) with their
+                    //ASCII values.
+                    $hdr_value = preg_replace(
+                        '#([\x80-\xFF])#e',
+                        '"=" . strtoupper(dechex(ord("\1")))',
+                        $hdr_value
+                    );
+                    //This regexp will break QP-encoded text at every $maxLength
+                    //but will not break any encoded letters.
+                    $reg1st = "|(.{0,$maxLength})[^\=]|";
+                    $reg2nd = "|(.{0,$maxLength})[^\=]|";
+                    break;
+                }
+                //Begin with the regexp for the first line.
+                $reg = $reg1st;
+                $output = "";
+                while ($hdr_value) {
+                    //Split translated string at every $maxLength
+                    //But make sure not to break any translated chars.
+                    $found = preg_match($reg, $hdr_value, $matches);
+                    
+                    //After this first line, we need to use a different
+                    //regexp for the first line.
+                    $reg = $reg2nd;
+
+                    //Save the found part and encapsulate it in the
+                    //prefix & suffix. Then remove the part from the
+                    //$hdr_value variable.
+                    if ($found){
+                        $part = $matches[0];
+                        $hdr_value = substr($hdr_value, strlen($matches[0]));
+                    }else{
+                        $part = $hdr_value;
+                        $hdr_value = "";
+                    }
+                    
+                    //RFC 2047 specifies that any split header should be seperated
+                    //by a CRLF SPACE. 
+                    if ($output){
+                        $output .=  "\r\n ";
+                    }
+                    $output .= $prefix . $part . $suffix;
+                }
+                $hdr_value = $output;
+            }
+            $input[$hdr_name] = $hdr_value;
+        }
+
+        return $input;
+    }
+
+    /**
+     * Set the object's end-of-line and define the constant if applicable
+     *
+     * @param string $eol End Of Line sequence
+     * @access private
+     */
+    function _setEOL($eol)
+    {
+        $this->_eol = $eol;
+        if (!defined('MAIL_MIME_CRLF')) {
+            define('MAIL_MIME_CRLF', $this->_eol, true);
+        }
+    }
+
+    
+
+} // End of class
+?>
+
diff --git a/program/lib/Mail/mimeDecode.php b/program/lib/Mail/mimeDecode.php
new file mode 100644 (file)
index 0000000..283faae
--- /dev/null
@@ -0,0 +1,837 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+// +-----------------------------------------------------------------------+
+// | Copyright (c) 2002-2003  Richard Heyes                                |
+// | Copyright (c) 2003-2005  The PHP Group                                |
+// | All rights reserved.                                                  |
+// |                                                                       |
+// | Redistribution and use in source and binary forms, with or without    |
+// | modification, are permitted provided that the following conditions    |
+// | are met:                                                              |
+// |                                                                       |
+// | o Redistributions of source code must retain the above copyright      |
+// |   notice, this list of conditions and the following disclaimer.       |
+// | o Redistributions in binary form must reproduce the above copyright   |
+// |   notice, this list of conditions and the following disclaimer in the |
+// |   documentation and/or other materials provided with the distribution.|
+// | o The names of the authors may not be used to endorse or promote      |
+// |   products derived from this software without specific prior written  |
+// |   permission.                                                         |
+// |                                                                       |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
+// |                                                                       |
+// +-----------------------------------------------------------------------+
+// | Author: Richard Heyes <richard@phpguru.org>                           |
+// +-----------------------------------------------------------------------+
+
+require_once 'PEAR.php';
+
+/**
+*  +----------------------------- IMPORTANT ------------------------------+
+*  | Usage of this class compared to native php extensions such as        |
+*  | mailparse or imap, is slow and may be feature deficient. If available|
+*  | you are STRONGLY recommended to use the php extensions.              |
+*  +----------------------------------------------------------------------+
+*
+* Mime Decoding class
+*
+* This class will parse a raw mime email and return
+* the structure. Returned structure is similar to
+* that returned by imap_fetchstructure().
+*
+* USAGE: (assume $input is your raw email)
+*
+* $decode = new Mail_mimeDecode($input, "\r\n");
+* $structure = $decode->decode();
+* print_r($structure);
+*
+* Or statically:
+*
+* $params['input'] = $input;
+* $structure = Mail_mimeDecode::decode($params);
+* print_r($structure);
+*
+* TODO:
+*  o UTF8: ???
+
+               > 4. We have also found a solution for decoding the UTF-8 
+               > headers. Therefore I made the following function:
+               > 
+               > function decode_utf8($txt) {
+               > $trans=array("Å&#8216;"=>"õ","ű"=>"û","Å\90"=>"Ã&#8226;","Å°"
+               =>"Ã&#8250;");
+               > $txt=strtr($txt,$trans);
+               > return(utf8_decode($txt));
+               > }
+               > 
+               > And I have inserted the following line to the class:
+               > 
+               > if (strtolower($charset)=="utf-8") $text=decode_utf8($text);
+               > 
+               > ... before the following one in the "_decodeHeader" function:
+               > 
+               > $input = str_replace($encoded, $text, $input);
+               > 
+               > This way from now on it can easily decode the UTF-8 headers too.
+
+*
+* @author  Richard Heyes <richard@phpguru.org>
+* @version $Revision: 202 $
+* @package Mail
+*/
+class Mail_mimeDecode extends PEAR
+{
+    /**
+     * The raw email to decode
+     * @var    string
+     */
+    var $_input;
+
+    /**
+     * The header part of the input
+     * @var    string
+     */
+    var $_header;
+
+    /**
+     * The body part of the input
+     * @var    string
+     */
+    var $_body;
+
+    /**
+     * If an error occurs, this is used to store the message
+     * @var    string
+     */
+    var $_error;
+
+    /**
+     * Flag to determine whether to include bodies in the
+     * returned object.
+     * @var    boolean
+     */
+    var $_include_bodies;
+
+    /**
+     * Flag to determine whether to decode bodies
+     * @var    boolean
+     */
+    var $_decode_bodies;
+
+    /**
+     * Flag to determine whether to decode headers
+     * @var    boolean
+     */
+    var $_decode_headers;
+
+    /**
+     * Constructor.
+     *
+     * Sets up the object, initialise the variables, and splits and
+     * stores the header and body of the input.
+     *
+     * @param string The input to decode
+     * @access public
+     */
+    function Mail_mimeDecode($input)
+    {
+        list($header, $body)   = $this->_splitBodyHeader($input);
+
+        $this->_input          = $input;
+        $this->_header         = $header;
+        $this->_body           = $body;
+        $this->_decode_bodies  = false;
+        $this->_include_bodies = true;
+    }
+
+    /**
+     * Begins the decoding process. If called statically
+     * it will create an object and call the decode() method
+     * of it.
+     *
+     * @param array An array of various parameters that determine
+     *              various things:
+     *              include_bodies - Whether to include the body in the returned
+     *                               object.
+     *              decode_bodies  - Whether to decode the bodies
+     *                               of the parts. (Transfer encoding)
+     *              decode_headers - Whether to decode headers
+     *              input          - If called statically, this will be treated
+     *                               as the input
+     * @return object Decoded results
+     * @access public
+     */
+    function decode($params = null)
+    {
+        // determine if this method has been called statically
+        $isStatic = !(isset($this) && get_class($this) == __CLASS__);
+
+        // Have we been called statically?
+       // If so, create an object and pass details to that.
+        if ($isStatic AND isset($params['input'])) {
+
+            $obj = new Mail_mimeDecode($params['input']);
+            $structure = $obj->decode($params);
+
+        // Called statically but no input
+        } elseif ($isStatic) {
+            return PEAR::raiseError('Called statically and no input given');
+
+        // Called via an object
+        } else {
+            $this->_include_bodies = isset($params['include_bodies']) ?
+                                    $params['include_bodies'] : false;
+            $this->_decode_bodies  = isset($params['decode_bodies']) ?
+                                    $params['decode_bodies']  : false;
+            $this->_decode_headers = isset($params['decode_headers']) ?
+                                    $params['decode_headers'] : false;
+
+            $structure = $this->_decode($this->_header, $this->_body);
+            if ($structure === false) {
+                $structure = $this->raiseError($this->_error);
+            }
+        }
+
+        return $structure;
+    }
+
+    /**
+     * Performs the decoding. Decodes the body string passed to it
+     * If it finds certain content-types it will call itself in a
+     * recursive fashion
+     *
+     * @param string Header section
+     * @param string Body section
+     * @return object Results of decoding process
+     * @access private
+     */
+    function _decode($headers, $body, $default_ctype = 'text/plain')
+    {
+        $return = new stdClass;
+        $return->headers = array();
+        $headers = $this->_parseHeaders($headers);
+
+        foreach ($headers as $value) {
+            if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
+                $return->headers[strtolower($value['name'])]   = array($return->headers[strtolower($value['name'])]);
+                $return->headers[strtolower($value['name'])][] = $value['value'];
+
+            } elseif (isset($return->headers[strtolower($value['name'])])) {
+                $return->headers[strtolower($value['name'])][] = $value['value'];
+
+            } else {
+                $return->headers[strtolower($value['name'])] = $value['value'];
+            }
+        }
+
+        reset($headers);
+        while (list($key, $value) = each($headers)) {
+            $headers[$key]['name'] = strtolower($headers[$key]['name']);
+            switch ($headers[$key]['name']) {
+
+                case 'content-type':
+                    $content_type = $this->_parseHeaderValue($headers[$key]['value']);
+
+                    if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
+                        $return->ctype_primary   = $regs[1];
+                        $return->ctype_secondary = $regs[2];
+                    }
+
+                    if (isset($content_type['other'])) {
+                        while (list($p_name, $p_value) = each($content_type['other'])) {
+                            $return->ctype_parameters[$p_name] = $p_value;
+                        }
+                    }
+                    break;
+
+                case 'content-disposition':
+                    $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
+                    $return->disposition   = $content_disposition['value'];
+                    if (isset($content_disposition['other'])) {
+                        while (list($p_name, $p_value) = each($content_disposition['other'])) {
+                            $return->d_parameters[$p_name] = $p_value;
+                        }
+                    }
+                    break;
+
+                case 'content-transfer-encoding':
+                    $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
+                    break;
+            }
+        }
+
+        if (isset($content_type)) {
+            switch (strtolower($content_type['value'])) {
+                case 'text/plain':
+                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
+                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
+                    break;
+
+                case 'text/html':
+                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
+                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
+                    break;
+
+                case 'multipart/parallel':
+                case 'multipart/appledouble': // Appledouble mail
+                case 'multipart/report': // RFC1892
+                case 'multipart/signed': // PGP
+                case 'multipart/digest':
+                case 'multipart/alternative':
+                case 'multipart/related':
+                case 'multipart/mixed':
+                    if(!isset($content_type['other']['boundary'])){
+                        $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
+                        return false;
+                    }
+
+                    $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
+
+                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
+                    for ($i = 0; $i < count($parts); $i++) {
+                        list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
+                        $part = $this->_decode($part_header, $part_body, $default_ctype);
+                        if($part === false)
+                            $part = $this->raiseError($this->_error);
+                        $return->parts[] = $part;
+                    }
+                    break;
+
+                case 'message/rfc822':
+                    $obj = &new Mail_mimeDecode($body);
+                    $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
+                                                                             'decode_bodies'  => $this->_decode_bodies,
+                                                                                                                 'decode_headers' => $this->_decode_headers));
+                    unset($obj);
+                    break;
+
+                default:
+                    if(!isset($content_transfer_encoding['value']))
+                        $content_transfer_encoding['value'] = '7bit';
+                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
+                    break;
+            }
+
+        } else {
+            $ctype = explode('/', $default_ctype);
+            $return->ctype_primary   = $ctype[0];
+            $return->ctype_secondary = $ctype[1];
+            $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
+        }
+
+        return $return;
+    }
+
+    /**
+     * Given the output of the above function, this will return an
+     * array of references to the parts, indexed by mime number.
+     *
+     * @param  object $structure   The structure to go through
+     * @param  string $mime_number Internal use only.
+     * @return array               Mime numbers
+     */
+    function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
+    {
+        $return = array();
+        if (!empty($structure->parts)) {
+            if ($mime_number != '') {
+                $structure->mime_id = $prepend . $mime_number;
+                $return[$prepend . $mime_number] = &$structure;
+            }
+            for ($i = 0; $i < count($structure->parts); $i++) {
+
+            
+                if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
+                    $prepend      = $prepend . $mime_number . '.';
+                    $_mime_number = '';
+                } else {
+                    $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
+                }
+
+                $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
+                foreach ($arr as $key => $val) {
+                    $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
+                }
+            }
+        } else {
+            if ($mime_number == '') {
+                $mime_number = '1';
+            }
+            $structure->mime_id = $prepend . $mime_number;
+            $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
+        }
+        
+        return $return;
+    }
+
+    /**
+     * Given a string containing a header and body
+     * section, this function will split them (at the first
+     * blank line) and return them.
+     *
+     * @param string Input to split apart
+     * @return array Contains header and body section
+     * @access private
+     */
+    function _splitBodyHeader($input)
+    {
+        if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
+            return array($match[1], $match[2]);
+        }
+        $this->_error = 'Could not split header and body';
+        return false;
+    }
+
+    /**
+     * Parse headers given in $input and return
+     * as assoc array.
+     *
+     * @param string Headers to parse
+     * @return array Contains parsed headers
+     * @access private
+     */
+    function _parseHeaders($input)
+    {
+
+        if ($input !== '') {
+            // Unfold the input
+            $input   = preg_replace("/\r?\n/", "\r\n", $input);
+            $input   = preg_replace("/\r\n(\t| )+/", ' ', $input);
+            $headers = explode("\r\n", trim($input));
+
+            foreach ($headers as $value) {
+                $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
+                $hdr_value = substr($value, $pos+1);
+                if($hdr_value[0] == ' ')
+                    $hdr_value = substr($hdr_value, 1);
+
+                $return[] = array(
+                                  'name'  => $hdr_name,
+                                  'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
+                                 );
+            }
+        } else {
+            $return = array();
+        }
+
+        return $return;
+    }
+
+    /**
+     * Function to parse a header value,
+     * extract first part, and any secondary
+     * parts (after ;) This function is not as
+     * robust as it could be. Eg. header comments
+     * in the wrong place will probably break it.
+     *
+     * @param string Header value to parse
+     * @return array Contains parsed result
+     * @access private
+     */
+    function _parseHeaderValue($input)
+    {
+
+        if (($pos = strpos($input, ';')) !== false) {
+
+            $return['value'] = trim(substr($input, 0, $pos));
+            $input = trim(substr($input, $pos+1));
+
+            if (strlen($input) > 0) {
+
+                // This splits on a semi-colon, if there's no preceeding backslash
+                // Now works with quoted values; had to glue the \; breaks in PHP
+                // the regex is already bordering on incomprehensible
+                $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
+                preg_match_all($splitRegex, $input, $matches);
+                $parameters = array();
+                for ($i=0; $i<count($matches[0]); $i++) {
+                    $param = $matches[0][$i];
+                    while (substr($param, -2) == '\;') {
+                        $param .= $matches[0][++$i];
+                    }
+                    $parameters[] = $param;
+                }
+
+                for ($i = 0; $i < count($parameters); $i++) {
+                    $param_name  = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
+                    $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
+                    if ($param_value[0] == '"') {
+                        $param_value = substr($param_value, 1, -1);
+                    }
+                    $return['other'][$param_name] = $param_value;
+                    $return['other'][strtolower($param_name)] = $param_value;
+                }
+            }
+        } else {
+            $return['value'] = trim($input);
+        }
+
+        return $return;
+    }
+
+    /**
+     * This function splits the input based
+     * on the given boundary
+     *
+     * @param string Input to parse
+     * @return array Contains array of resulting mime parts
+     * @access private
+     */
+    function _boundarySplit($input, $boundary)
+    {
+        $parts = array();
+
+        $bs_possible = substr($boundary, 2, -2);
+        $bs_check = '\"' . $bs_possible . '\"';
+
+        if ($boundary == $bs_check) {
+            $boundary = $bs_possible;
+        }
+
+        $tmp = explode('--' . $boundary, $input);
+
+        for ($i = 1; $i < count($tmp) - 1; $i++) {
+            $parts[] = $tmp[$i];
+        }
+
+        return $parts;
+    }
+
+    /**
+     * Given a header, this function will decode it
+     * according to RFC2047. Probably not *exactly*
+     * conformant, but it does pass all the given
+     * examples (in RFC2047).
+     *
+     * @param string Input header value to decode
+     * @return string Decoded header value
+     * @access private
+     */
+    function _decodeHeader($input)
+    {
+        // Remove white space between encoded-words
+        $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
+
+        // For each encoded-word...
+        while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
+
+            $encoded  = $matches[1];
+            $charset  = $matches[2];
+            $encoding = $matches[3];
+            $text     = $matches[4];
+
+            switch (strtolower($encoding)) {
+                case 'b':
+                    $text = base64_decode($text);
+                    break;
+
+                case 'q':
+                    $text = str_replace('_', ' ', $text);
+                    preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
+                    foreach($matches[1] as $value)
+                        $text = str_replace('='.$value, chr(hexdec($value)), $text);
+                    break;
+            }
+
+            $input = str_replace($encoded, $text, $input);
+        }
+
+        return $input;
+    }
+
+    /**
+     * Given a body string and an encoding type,
+     * this function will decode and return it.
+     *
+     * @param  string Input body to decode
+     * @param  string Encoding type to use.
+     * @return string Decoded body
+     * @access private
+     */
+    function _decodeBody($input, $encoding = '7bit')
+    {
+        switch (strtolower($encoding)) {
+            case '7bit':
+                return $input;
+                break;
+
+            case 'quoted-printable':
+                return $this->_quotedPrintableDecode($input);
+                break;
+
+            case 'base64':
+                return base64_decode($input);
+                break;
+
+            default:
+                return $input;
+        }
+    }
+
+    /**
+     * Given a quoted-printable string, this
+     * function will decode and return it.
+     *
+     * @param  string Input body to decode
+     * @return string Decoded body
+     * @access private
+     */
+    function _quotedPrintableDecode($input)
+    {
+        // Remove soft line breaks
+        $input = preg_replace("/=\r?\n/", '', $input);
+
+        // Replace encoded characters
+               $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
+
+        return $input;
+    }
+
+    /**
+     * Checks the input for uuencoded files and returns
+     * an array of them. Can be called statically, eg:
+     *
+     * $files =& Mail_mimeDecode::uudecode($some_text);
+     *
+     * It will check for the begin 666 ... end syntax
+     * however and won't just blindly decode whatever you
+     * pass it.
+     *
+     * @param  string Input body to look for attahcments in
+     * @return array  Decoded bodies, filenames and permissions
+     * @access public
+     * @author Unknown
+     */
+    function &uudecode($input)
+    {
+        // Find all uuencoded sections
+        preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
+
+        for ($j = 0; $j < count($matches[3]); $j++) {
+
+            $str      = $matches[3][$j];
+            $filename = $matches[2][$j];
+            $fileperm = $matches[1][$j];
+
+            $file = '';
+            $str = preg_split("/\r?\n/", trim($str));
+            $strlen = count($str);
+
+            for ($i = 0; $i < $strlen; $i++) {
+                $pos = 1;
+                $d = 0;
+                $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
+
+                while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
+                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+                    $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
+                    $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
+                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
+
+                    $file .= chr(((($c2 - ' ') & 077) << 6) |  (($c3 - ' ') & 077));
+
+                    $pos += 4;
+                    $d += 3;
+                }
+
+                if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
+                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+                    $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
+                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
+
+                    $pos += 3;
+                    $d += 2;
+                }
+
+                if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
+                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+                }
+            }
+            $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
+        }
+
+        return $files;
+    }
+
+    /**
+     * getSendArray() returns the arguments required for Mail::send()
+     * used to build the arguments for a mail::send() call 
+     *
+     * Usage:
+     * $mailtext = Full email (for example generated by a template)
+     * $decoder = new Mail_mimeDecode($mailtext);
+     * $parts =  $decoder->getSendArray();
+     * if (!PEAR::isError($parts) {
+     *     list($recipents,$headers,$body) = $parts;
+     *     $mail = Mail::factory('smtp');
+     *     $mail->send($recipents,$headers,$body);
+     * } else {
+     *     echo $parts->message;
+     * }
+     * @return mixed   array of recipeint, headers,body or Pear_Error
+     * @access public
+     * @author Alan Knowles <alan@akbkhome.com>
+     */
+    function getSendArray()
+    {
+        // prevent warning if this is not set
+        $this->_decode_headers = FALSE;
+        $headerlist =$this->_parseHeaders($this->_header);
+        $to = "";
+        if (!$headerlist) {
+            return $this->raiseError("Message did not contain headers");
+        }
+        foreach($headerlist as $item) {
+            $header[$item['name']] = $item['value'];
+            switch (strtolower($item['name'])) {
+                case "to":
+                case "cc":
+                case "bcc":
+                    $to = ",".$item['value'];
+                default:
+                   break;
+            }
+        }
+        if ($to == "") {
+            return $this->raiseError("Message did not contain any recipents");
+        }
+        $to = substr($to,1);
+        return array($to,$header,$this->_body);
+    } 
+
+    /**
+     * Returns a xml copy of the output of
+     * Mail_mimeDecode::decode. Pass the output in as the
+     * argument. This function can be called statically. Eg:
+     *
+     * $output = $obj->decode();
+     * $xml    = Mail_mimeDecode::getXML($output);
+     *
+     * The DTD used for this should have been in the package. Or
+     * alternatively you can get it from cvs, or here:
+     * http://www.phpguru.org/xmail/xmail.dtd.
+     *
+     * @param  object Input to convert to xml. This should be the
+     *                output of the Mail_mimeDecode::decode function
+     * @return string XML version of input
+     * @access public
+     */
+    function getXML($input)
+    {
+        $crlf    =  "\r\n";
+        $output  = '<?xml version=\'1.0\'?>' . $crlf .
+                   '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
+                   '<email>' . $crlf .
+                   Mail_mimeDecode::_getXML($input) .
+                   '</email>';
+
+        return $output;
+    }
+
+    /**
+     * Function that does the actual conversion to xml. Does a single
+     * mimepart at a time.
+     *
+     * @param  object  Input to convert to xml. This is a mimepart object.
+     *                 It may or may not contain subparts.
+     * @param  integer Number of tabs to indent
+     * @return string  XML version of input
+     * @access private
+     */
+    function _getXML($input, $indent = 1)
+    {
+        $htab    =  "\t";
+        $crlf    =  "\r\n";
+        $output  =  '';
+        $headers = @(array)$input->headers;
+
+        foreach ($headers as $hdr_name => $hdr_value) {
+
+            // Multiple headers with this name
+            if (is_array($headers[$hdr_name])) {
+                for ($i = 0; $i < count($hdr_value); $i++) {
+                    $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
+                }
+
+            // Only one header of this sort
+            } else {
+                $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
+            }
+        }
+
+        if (!empty($input->parts)) {
+            for ($i = 0; $i < count($input->parts); $i++) {
+                $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
+                           Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
+                           str_repeat($htab, $indent) . '</mimepart>' . $crlf;
+            }
+        } elseif (isset($input->body)) {
+            $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
+                       $input->body . ']]></body>' . $crlf;
+        }
+
+        return $output;
+    }
+
+    /**
+     * Helper function to _getXML(). Returns xml of a header.
+     *
+     * @param  string  Name of header
+     * @param  string  Value of header
+     * @param  integer Number of tabs to indent
+     * @return string  XML version of input
+     * @access private
+     */
+    function _getXML_helper($hdr_name, $hdr_value, $indent)
+    {
+        $htab   = "\t";
+        $crlf   = "\r\n";
+        $return = '';
+
+        $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
+        $new_hdr_name  = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
+
+        // Sort out any parameters
+        if (!empty($new_hdr_value['other'])) {
+            foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
+                $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
+                            str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
+                            str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
+                            str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
+            }
+
+            $params = implode('', $params);
+        } else {
+            $params = '';
+        }
+
+        $return = str_repeat($htab, $indent) . '<header>' . $crlf .
+                  str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
+                  str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
+                  $params .
+                  str_repeat($htab, $indent) . '</header>' . $crlf;
+
+        return $return;
+    }
+
+} // End of class
+?>
diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php
new file mode 100644 (file)
index 0000000..1ffdb00
--- /dev/null
@@ -0,0 +1,351 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Copyright (c) 2002-2003  Richard Heyes                                     |
+// | All rights reserved.                                                  |
+// |                                                                       |
+// | Redistribution and use in source and binary forms, with or without    |
+// | modification, are permitted provided that the following conditions    |
+// | are met:                                                              |
+// |                                                                       |
+// | o Redistributions of source code must retain the above copyright      |
+// |   notice, this list of conditions and the following disclaimer.       |
+// | o Redistributions in binary form must reproduce the above copyright   |
+// |   notice, this list of conditions and the following disclaimer in the |
+// |   documentation and/or other materials provided with the distribution.|
+// | o The names of the authors may not be used to endorse or promote      |
+// |   products derived from this software without specific prior written  |
+// |   permission.                                                         |
+// |                                                                       |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
+// |                                                                       |
+// +-----------------------------------------------------------------------+
+// | Author: Richard Heyes <richard@phpguru.org>                           |
+// +-----------------------------------------------------------------------+
+
+/**
+*
+*  Raw mime encoding class
+*
+* What is it?
+*   This class enables you to manipulate and build
+*   a mime email from the ground up.
+*
+* Why use this instead of mime.php?
+*   mime.php is a userfriendly api to this class for
+*   people who aren't interested in the internals of
+*   mime mail. This class however allows full control
+*   over the email.
+*
+* Eg.
+*
+* // Since multipart/mixed has no real body, (the body is
+* // the subpart), we set the body argument to blank.
+*
+* $params['content_type'] = 'multipart/mixed';
+* $email = new Mail_mimePart('', $params);
+*
+* // Here we add a text part to the multipart we have
+* // already. Assume $body contains plain text.
+*
+* $params['content_type'] = 'text/plain';
+* $params['encoding']     = '7bit';
+* $text = $email->addSubPart($body, $params);
+*
+* // Now add an attachment. Assume $attach is
+* the contents of the attachment
+*
+* $params['content_type'] = 'application/zip';
+* $params['encoding']     = 'base64';
+* $params['disposition']  = 'attachment';
+* $params['dfilename']    = 'example.zip';
+* $attach =& $email->addSubPart($body, $params);
+*
+* // Now build the email. Note that the encode
+* // function returns an associative array containing two
+* // elements, body and headers. You will need to add extra
+* // headers, (eg. Mime-Version) before sending.
+*
+* $email = $message->encode();
+* $email['headers'][] = 'Mime-Version: 1.0';
+*
+*
+* Further examples are available at http://www.phpguru.org
+*
+* TODO:
+*  - Set encode() to return the $obj->encoded if encode()
+*    has already been run. Unless a flag is passed to specifically
+*    re-build the message.
+*
+* @author  Richard Heyes <richard@phpguru.org>
+* @version $Revision: 202 $
+* @package Mail
+*/
+
+class Mail_mimePart {
+
+   /**
+    * The encoding type of this part
+    * @var string
+    */
+    var $_encoding;
+
+   /**
+    * An array of subparts
+    * @var array
+    */
+    var $_subparts;
+
+   /**
+    * The output of this part after being built
+    * @var string
+    */
+    var $_encoded;
+
+   /**
+    * Headers for this part
+    * @var array
+    */
+    var $_headers;
+
+   /**
+    * The body of this part (not encoded)
+    * @var string
+    */
+    var $_body;
+
+    /**
+     * Constructor.
+     *
+     * Sets up the object.
+     *
+     * @param $body   - The body of the mime part if any.
+     * @param $params - An associative array of parameters:
+     *                  content_type - The content type for this part eg multipart/mixed
+     *                  encoding     - The encoding to use, 7bit, 8bit, base64, or quoted-printable
+     *                  cid          - Content ID to apply
+     *                  disposition  - Content disposition, inline or attachment
+     *                  dfilename    - Optional filename parameter for content disposition
+     *                  description  - Content description
+     *                  charset      - Character set to use
+     * @access public
+     */
+    function Mail_mimePart($body = '', $params = array())
+    {
+        if (!defined('MAIL_MIMEPART_CRLF')) {
+            define('MAIL_MIMEPART_CRLF', defined('MAIL_MIME_CRLF') ? MAIL_MIME_CRLF : "\r\n", TRUE);
+        }
+
+        foreach ($params as $key => $value) {
+            switch ($key) {
+                case 'content_type':
+                    $headers['Content-Type'] = $value . (isset($charset) ? '; charset="' . $charset . '"' : '');
+                    break;
+
+                case 'encoding':
+                    $this->_encoding = $value;
+                    $headers['Content-Transfer-Encoding'] = $value;
+                    break;
+
+                case 'cid':
+                    $headers['Content-ID'] = '<' . $value . '>';
+                    break;
+
+                case 'disposition':
+                    $headers['Content-Disposition'] = $value . (isset($dfilename) ? '; filename="' . $dfilename . '"' : '');
+                    break;
+
+                case 'dfilename':
+                    if (isset($headers['Content-Disposition'])) {
+                        $headers['Content-Disposition'] .= '; filename="' . $value . '"';
+                    } else {
+                        $dfilename = $value;
+                    }
+                    break;
+
+                case 'description':
+                    $headers['Content-Description'] = $value;
+                    break;
+
+                case 'charset':
+                    if (isset($headers['Content-Type'])) {
+                        $headers['Content-Type'] .= '; charset="' . $value . '"';
+                    } else {
+                        $charset = $value;
+                    }
+                    break;
+            }
+        }
+
+        // Default content-type
+        if (!isset($headers['Content-Type'])) {
+            $headers['Content-Type'] = 'text/plain';
+        }
+
+        //Default encoding
+        if (!isset($this->_encoding)) {
+            $this->_encoding = '7bit';
+        }
+
+        // Assign stuff to member variables
+        $this->_encoded  = array();
+        $this->_headers  = $headers;
+        $this->_body     = $body;
+    }
+
+    /**
+     * encode()
+     *
+     * Encodes and returns the email. Also stores
+     * it in the encoded member variable
+     *
+     * @return An associative array containing two elements,
+     *         body and headers. The headers element is itself
+     *         an indexed array.
+     * @access public
+     */
+    function encode()
+    {
+        $encoded =& $this->_encoded;
+
+        if (!empty($this->_subparts)) {
+            srand((double)microtime()*1000000);
+            $boundary = '=_' . md5(rand() . microtime());
+            $this->_headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF . "\t" . 'boundary="' . $boundary . '"';
+
+            // Add body parts to $subparts
+            for ($i = 0; $i < count($this->_subparts); $i++) {
+                $headers = array();
+                $tmp = $this->_subparts[$i]->encode();
+                foreach ($tmp['headers'] as $key => $value) {
+                    $headers[] = $key . ': ' . $value;
+                }
+                $subparts[] = implode(MAIL_MIMEPART_CRLF, $headers) . MAIL_MIMEPART_CRLF . MAIL_MIMEPART_CRLF . $tmp['body'];
+            }
+
+            $encoded['body'] = '--' . $boundary . MAIL_MIMEPART_CRLF .
+                               implode('--' . $boundary . MAIL_MIMEPART_CRLF, $subparts) .
+                               '--' . $boundary.'--' . MAIL_MIMEPART_CRLF;
+
+        } else {
+            $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding) . MAIL_MIMEPART_CRLF;
+        }
+
+        // Add headers to $encoded
+        $encoded['headers'] =& $this->_headers;
+
+        return $encoded;
+    }
+
+    /**
+     * &addSubPart()
+     *
+     * Adds a subpart to current mime part and returns
+     * a reference to it
+     *
+     * @param $body   The body of the subpart, if any.
+     * @param $params The parameters for the subpart, same
+     *                as the $params argument for constructor.
+     * @return A reference to the part you just added. It is
+     *         crucial if using multipart/* in your subparts that
+     *         you use =& in your script when calling this function,
+     *         otherwise you will not be able to add further subparts.
+     * @access public
+     */
+    function &addSubPart($body, $params)
+    {
+        $this->_subparts[] = new Mail_mimePart($body, $params);
+        return $this->_subparts[count($this->_subparts) - 1];
+    }
+
+    /**
+     * _getEncodedData()
+     *
+     * Returns encoded data based upon encoding passed to it
+     *
+     * @param $data     The data to encode.
+     * @param $encoding The encoding type to use, 7bit, base64,
+     *                  or quoted-printable.
+     * @access private
+     */
+    function _getEncodedData($data, $encoding)
+    {
+        switch ($encoding) {
+            case '8bit':
+            case '7bit':
+                return $data;
+                break;
+
+            case 'quoted-printable':
+                return $this->_quotedPrintableEncode($data);
+                break;
+
+            case 'base64':
+                return rtrim(chunk_split(base64_encode($data), 76, MAIL_MIMEPART_CRLF));
+                break;
+
+            default:
+                return $data;
+        }
+    }
+
+    /**
+     * quoteadPrintableEncode()
+     *
+     * Encodes data to quoted-printable standard.
+     *
+     * @param $input    The data to encode
+     * @param $line_max Optional max line length. Should
+     *                  not be more than 76 chars
+     *
+     * @access private
+     */
+    function _quotedPrintableEncode($input , $line_max = 76)
+    {
+        $lines  = preg_split("/\r?\n/", $input);
+        $eol    = MAIL_MIMEPART_CRLF;
+        $escape = '=';
+        $output = '';
+
+        while(list(, $line) = each($lines)){
+
+            $linlen     = strlen($line);
+            $newline = '';
+
+            for ($i = 0; $i < $linlen; $i++) {
+                $char = substr($line, $i, 1);
+                $dec  = ord($char);
+
+                if (($dec == 32) AND ($i == ($linlen - 1))){    // convert space at eol only
+                    $char = '=20';
+
+                } elseif(($dec == 9) AND ($i == ($linlen - 1))) {  // convert tab at eol only
+                    $char = '=09';
+                } elseif($dec == 9) {
+                    ; // Do nothing if a tab.
+                } elseif(($dec == 61) OR ($dec < 32 ) OR ($dec > 126)) {
+                    $char = $escape . strtoupper(sprintf('%02s', dechex($dec)));
+                }
+
+                if ((strlen($newline) + strlen($char)) >= $line_max) {        // MAIL_MIMEPART_CRLF is not counted
+                    $output  .= $newline . $escape . $eol;                    // soft line break; " =\r\n" is okay
+                    $newline  = '';
+                }
+                $newline .= $char;
+            } // end of for
+            $output .= $newline . $eol;
+        }
+        $output = substr($output, 0, -1 * strlen($eol)); // Don't want last crlf
+        return $output;
+    }
+} // End of class
+?>
diff --git a/program/lib/Net/SMTP.php b/program/lib/Net/SMTP.php
new file mode 100644 (file)
index 0000000..fc30dde
--- /dev/null
@@ -0,0 +1,991 @@
+<?php
+/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license,      |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/2_02.txt.                                 |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Authors: Chuck Hagenbuch <chuck@horde.org>                           |
+// |          Jon Parise <jon@php.net>                                    |
+// |          Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>      |
+// +----------------------------------------------------------------------+
+//
+// $Id: SMTP.php 17 2005-10-03 20:25:31Z roundcube $
+
+require_once 'PEAR.php';
+require_once 'Net/Socket.php';
+
+/**
+ * Provides an implementation of the SMTP protocol using PEAR's
+ * Net_Socket:: class.
+ *
+ * @package Net_SMTP
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Jon Parise <jon@php.net>
+ * @author  Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
+ *
+ * @example basic.php   A basic implementation of the Net_SMTP package.
+ */
+class Net_SMTP
+{
+
+    /**
+     * The server to connect to.
+     * @var string
+     * @access public
+     */
+    var $host = 'localhost';
+
+    /**
+     * The port to connect to.
+     * @var int
+     * @access public
+     */
+    var $port = 25;
+
+    /**
+     * The value to give when sending EHLO or HELO.
+     * @var string
+     * @access public
+     */
+    var $localhost = 'localhost';
+
+    /**
+     * List of supported authentication methods, in preferential order.
+     * @var array
+     * @access public
+     */
+    var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
+
+    /**
+     * Should debugging output be enabled?
+     * @var boolean
+     * @access private
+     */
+    var $_debug = false;
+
+    /**
+     * The socket resource being used to connect to the SMTP server.
+     * @var resource
+     * @access private
+     */
+    var $_socket = null;
+
+    /**
+     * The most recent server response code.
+     * @var int
+     * @access private
+     */
+    var $_code = -1;
+
+    /**
+     * The most recent server response arguments.
+     * @var array
+     * @access private
+     */
+    var $_arguments = array();
+
+    /**
+     * Stores detected features of the SMTP server.
+     * @var array
+     * @access private
+     */
+    var $_esmtp = array();
+
+    /**
+     * Instantiates a new Net_SMTP object, overriding any defaults
+     * with parameters that are passed in.
+     *
+     * If you have SSL support in PHP, you can connect to a server
+     * over SSL using an 'ssl://' prefix:
+     *
+     *   // 465 is a common smtps port.
+     *   $smtp = new Net_SMTP('ssl://mail.host.com', 465);
+     *   $smtp->connect();
+     *
+     * @param string  $host       The server to connect to.
+     * @param integer $port       The port to connect to.
+     * @param string  $localhost  The value to give when sending EHLO or HELO.
+     *
+     * @access  public
+     * @since   1.0
+     */
+    function Net_SMTP($host = null, $port = null, $localhost = null)
+    {
+        if (isset($host)) $this->host = $host;
+        if (isset($port)) $this->port = $port;
+        if (isset($localhost)) $this->localhost = $localhost;
+
+        $this->_socket = &new Net_Socket();
+
+        /*
+         * Include the Auth_SASL package.  If the package is not available,
+         * we disable the authentication methods that depend upon it.
+         */
+        if ((@include_once 'Auth/SASL.php') === false) {
+            $pos = array_search('DIGEST-MD5', $this->auth_methods);
+            unset($this->auth_methods[$pos]);
+            $pos = array_search('CRAM-MD5', $this->auth_methods);
+            unset($this->auth_methods[$pos]);
+        }
+    }
+
+    /**
+     * Set the value of the debugging flag.
+     *
+     * @param   boolean $debug      New value for the debugging flag.
+     *
+     * @access  public
+     * @since   1.1.0
+     */
+    function setDebug($debug)
+    {
+        $this->_debug = $debug;
+    }
+
+    /**
+     * Send the given string of data to the server.
+     *
+     * @param   string  $data       The string of data to send.
+     *
+     * @return  mixed   True on success or a PEAR_Error object on failure.
+     *
+     * @access  private
+     * @since   1.1.0
+     */
+    function _send($data)
+    {
+        if ($this->_debug) {
+            echo "DEBUG: Send: $data\n";
+        }
+
+        if (PEAR::isError($error = $this->_socket->write($data))) {
+            return PEAR::raiseError('Failed to write to socket: ' .
+                                    $error->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Send a command to the server with an optional string of
+     * arguments.  A carriage return / linefeed (CRLF) sequence will
+     * be appended to each command string before it is sent to the
+     * SMTP server - an error will be thrown if the command string
+     * already contains any newline characters. Use _send() for
+     * commands that must contain newlines.
+     *
+     * @param   string  $command    The SMTP command to send to the server.
+     * @param   string  $args       A string of optional arguments to append
+     *                              to the command.
+     *
+     * @return  mixed   The result of the _send() call.
+     *
+     * @access  private
+     * @since   1.1.0
+     */
+    function _put($command, $args = '')
+    {
+        if (!empty($args)) {
+            $command .= ' ' . $args;
+        }
+
+        if (strcspn($command, "\r\n") !== strlen($command)) {
+            return PEAR::raiseError('Commands cannot contain newlines');
+        }
+
+        return $this->_send($command . "\r\n");
+    }
+
+    /**
+     * Read a reply from the SMTP server.  The reply consists of a response
+     * code and a response message.
+     *
+     * @param   mixed   $valid      The set of valid response codes.  These
+     *                              may be specified as an array of integer
+     *                              values or as a single integer value.
+     *
+     * @return  mixed   True if the server returned a valid response code or
+     *                  a PEAR_Error object is an error condition is reached.
+     *
+     * @access  private
+     * @since   1.1.0
+     *
+     * @see     getResponse
+     */
+    function _parseResponse($valid)
+    {
+        $this->_code = -1;
+        $this->_arguments = array();
+
+        while ($line = $this->_socket->readLine()) {
+            if ($this->_debug) {
+                echo "DEBUG: Recv: $line\n";
+            }
+
+            /* If we receive an empty line, the connection has been closed. */
+            if (empty($line)) {
+                $this->disconnect();
+                return PEAR::raiseError('Connection was unexpectedly closed');
+            }
+
+            /* Read the code and store the rest in the arguments array. */
+            $code = substr($line, 0, 3);
+            $this->_arguments[] = trim(substr($line, 4));
+
+            /* Check the syntax of the response code. */
+            if (is_numeric($code)) {
+                $this->_code = (int)$code;
+            } else {
+                $this->_code = -1;
+                break;
+            }
+
+            /* If this is not a multiline response, we're done. */
+            if (substr($line, 3, 1) != '-') {
+                break;
+            }
+        }
+
+        /* Compare the server's response code with the valid code. */
+        if (is_int($valid) && ($this->_code === $valid)) {
+            return true;
+        }
+
+        /* If we were given an array of valid response codes, check each one. */
+        if (is_array($valid)) {
+            foreach ($valid as $valid_code) {
+                if ($this->_code === $valid_code) {
+                    return true;
+                }
+            }
+        }
+
+        return PEAR::raiseError('Invalid response code received from server');
+    }
+
+    /**
+     * Return a 2-tuple containing the last response from the SMTP server.
+     *
+     * @return  array   A two-element array: the first element contains the
+     *                  response code as an integer and the second element
+     *                  contains the response's arguments as a string.
+     *
+     * @access  public
+     * @since   1.1.0
+     */
+    function getResponse()
+    {
+        return array($this->_code, join("\n", $this->_arguments));
+    }
+
+    /**
+     * Attempt to connect to the SMTP server.
+     *
+     * @param   int     $timeout    The timeout value (in seconds) for the
+     *                              socket connection.
+     * @param   bool    $persistent Should a persistent socket connection
+     *                              be used?
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function connect($timeout = null, $persistent = false)
+    {
+        $result = $this->_socket->connect($this->host, $this->port,
+                                          $persistent, $timeout);
+        if (PEAR::isError($result)) {
+            return PEAR::raiseError('Failed to connect socket: ' .
+                                    $result->getMessage());
+        }
+
+        if (PEAR::isError($error = $this->_parseResponse(220))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_negotiate())) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempt to disconnect from the SMTP server.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function disconnect()
+    {
+        if (PEAR::isError($error = $this->_put('QUIT'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(221))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_socket->disconnect())) {
+            return PEAR::raiseError('Failed to disconnect socket: ' .
+                                    $error->getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempt to send the EHLO command and obtain a list of ESMTP
+     * extensions available, and failing that just send HELO.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access private
+     * @since  1.1.0
+     */
+    function _negotiate()
+    {
+        if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
+            return $error;
+        }
+
+        if (PEAR::isError($this->_parseResponse(250))) {
+            /* If we receive a 503 response, we're already authenticated. */
+            if ($this->_code === 503) {
+                return true;
+            }
+
+            /* If the EHLO failed, try the simpler HELO command. */
+            if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
+                return $error;
+            }
+            if (PEAR::isError($this->_parseResponse(250))) {
+                return PEAR::raiseError('HELO was not accepted: ', $this->_code);
+            }
+
+            return true;
+        }
+
+        foreach ($this->_arguments as $argument) {
+            $verb = strtok($argument, ' ');
+            $arguments = substr($argument, strlen($verb) + 1,
+                                strlen($argument) - strlen($verb) - 1);
+            $this->_esmtp[$verb] = $arguments;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the name of the best authentication method that the server
+     * has advertised.
+     *
+     * @return mixed    Returns a string containing the name of the best
+     *                  supported authentication method or a PEAR_Error object
+     *                  if a failure condition is encountered.
+     * @access private
+     * @since  1.1.0
+     */
+    function _getBestAuthMethod()
+    {
+        $available_methods = explode(' ', $this->_esmtp['AUTH']);
+
+        foreach ($this->auth_methods as $method) {
+            if (in_array($method, $available_methods)) {
+                return $method;
+            }
+        }
+
+        return PEAR::raiseError('No supported authentication methods');
+    }
+
+    /**
+     * Attempt to do SMTP authentication.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     * @param string The requested authentication method.  If none is
+     *               specified, the best supported method will be used.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function auth($uid, $pwd , $method = '')
+    {
+        if (empty($this->_esmtp['AUTH'])) {
+            return PEAR::raiseError('SMTP server does no support authentication');
+        }
+
+        /* If no method has been specified, get the name of the best
+         * supported method advertised by the SMTP server. */
+        if (empty($method)) {
+            if (PEAR::isError($method = $this->_getBestAuthMethod())) {
+                /* Return the PEAR_Error object from _getBestAuthMethod(). */
+                return $method;
+            }
+        } else {
+            $method = strtoupper($method);
+            if (!in_array($method, $this->auth_methods)) {
+                return PEAR::raiseError("$method is not a supported authentication method");
+            }
+        }
+
+        switch ($method) {
+            case 'DIGEST-MD5':
+                $result = $this->_authDigest_MD5($uid, $pwd);
+                break;
+            case 'CRAM-MD5':
+                $result = $this->_authCRAM_MD5($uid, $pwd);
+                break;
+            case 'LOGIN':
+                $result = $this->_authLogin($uid, $pwd);
+                break;
+            case 'PLAIN':
+                $result = $this->_authPlain($uid, $pwd);
+                break;
+            default:
+                $result = PEAR::raiseError("$method is not a supported authentication method");
+                break;
+        }
+
+        /* If an error was encountered, return the PEAR_Error object. */
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        return true;
+    }
+
+    /**
+     * Authenticates the user using the DIGEST-MD5 method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authDigest_MD5($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $challenge = base64_decode($this->_arguments[0]);
+        $digest = &Auth_SASL::factory('digestmd5');
+        $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
+                                                       $this->host, "smtp"));
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            return $error;
+        }
+
+        /* We don't use the protocol's third step because SMTP doesn't
+         * allow subsequent authentication, so we just silently ignore
+         * it. */
+        if (PEAR::isError($error = $this->_put(' '))) {
+            return $error;
+        }
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+    }
+
+    /**
+     * Authenticates the user using the CRAM-MD5 method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authCRAM_MD5($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $challenge = base64_decode($this->_arguments[0]);
+        $cram = &Auth_SASL::factory('crammd5');
+        $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+    }
+
+    /**
+     * Authenticates the user using the LOGIN method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authLogin($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            return $error;
+        }
+
+        if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Authenticates the user using the PLAIN method.
+     *
+     * @param string The userid to authenticate as.
+     * @param string The password to authenticate with.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access private
+     * @since  1.1.0
+     */
+    function _authPlain($uid, $pwd)
+    {
+        if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
+            return $error;
+        }
+        /* 334: Continue authentication request */
+        if (PEAR::isError($error = $this->_parseResponse(334))) {
+            /* 503: Error: already authenticated */
+            if ($this->_code === 503) {
+                return true;
+            }
+            return $error;
+        }
+
+        $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
+
+        if (PEAR::isError($error = $this->_put($auth_str))) {
+            return $error;
+        }
+
+        /* 235: Authentication successful */
+        if (PEAR::isError($error = $this->_parseResponse(235))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the HELO command.
+     *
+     * @param string The domain name to say we are.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function helo($domain)
+    {
+        if (PEAR::isError($error = $this->_put('HELO', $domain))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the MAIL FROM: command.
+     *
+     * @param string The sender (reverse path) to set.
+     *
+     * @param array optional arguments. Currently supported:
+     *        verp   boolean or string. If true or string
+     *               verp is enabled. If string the characters
+     *               are considered verp separators.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function mailFrom($sender, $args = array())
+    {
+        $argstr = '';
+
+        if (isset($args['verp'])) {
+            /* XVERP */
+            if ($args['verp'] === true) {
+                $argstr .= ' XVERP';
+
+            /* XVERP=something */
+            } elseif (trim($args['verp'])) {
+                $argstr .= ' XVERP=' . $args['verp'];
+            }
+        }
+
+        if (PEAR::isError($error = $this->_put('MAIL', "FROM:<$sender>$argstr"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the RCPT TO: command.
+     *
+     * @param string The recipient (forward path) to add.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function rcptTo($recipient)
+    {
+        if (PEAR::isError($error = $this->_put('RCPT', "TO:<$recipient>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(array(250, 251)))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Quote the data so that it meets SMTP standards.
+     *
+     * This is provided as a separate public function to facilitate
+     * easier overloading for the cases where it is desirable to
+     * customize the quoting behavior.
+     *
+     * @param string $data  The message text to quote. The string must be passed
+     *                      by reference, and the text will be modified in place.
+     *
+     * @access public
+     * @since  1.2
+     */
+    function quotedata(&$data)
+    {
+        /* Change Unix (\n) and Mac (\r) linefeeds into
+         * Internet-standard CRLF (\r\n) linefeeds. */
+        $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
+
+        /* Because a single leading period (.) signifies an end to the
+         * data, legitimate leading periods need to be "doubled"
+         * (e.g. '..'). */
+        $data = str_replace("\n.", "\n..", $data);
+    }
+
+    /**
+     * Send the DATA command.
+     *
+     * @param string $data  The message body to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function data($data)
+    {
+        /* RFC 1870, section 3, subsection 3 states "a value of zero
+         * indicates that no fixed maximum message size is in force".
+         * Furthermore, it says that if "the parameter is omitted no
+         * information is conveyed about the server's fixed maximum
+         * message size". */
+        if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
+            if (strlen($data) >= $this->_esmtp['SIZE']) {
+                $this->disconnect();
+                return PEAR::raiseError('Message size excedes the server limit');
+            }
+        }
+
+        /* Quote the data based on the SMTP standards. */
+        $this->quotedata($data);
+
+        if (PEAR::isError($error = $this->_put('DATA'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(354))) {
+            return $error;
+        }
+
+        if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) {
+            return $result;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the SEND FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function sendFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for sendFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function send_from($path)
+    {
+        return sendFrom($path);
+    }
+
+    /**
+     * Send the SOML FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function somlFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for somlFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function soml_from($path)
+    {
+        return somlFrom($path);
+    }
+
+    /**
+     * Send the SAML FROM: command.
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.2.6
+     */
+    function samlFrom($path)
+    {
+        if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility wrapper for samlFrom().
+     *
+     * @param string The reverse path to send.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     *
+     * @access      public
+     * @since       1.0
+     * @deprecated  1.2.6
+     */
+    function saml_from($path)
+    {
+        return samlFrom($path);
+    }
+
+    /**
+     * Send the RSET command.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function rset()
+    {
+        if (PEAR::isError($error = $this->_put('RSET'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the VRFY command.
+     *
+     * @param string The string to verify
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function vrfy($string)
+    {
+        /* Note: 251 is also a valid response code */
+        if (PEAR::isError($error = $this->_put('VRFY', $string))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Send the NOOP command.
+     *
+     * @return mixed Returns a PEAR_Error with an error message on any
+     *               kind of failure, or true on success.
+     * @access public
+     * @since  1.0
+     */
+    function noop()
+    {
+        if (PEAR::isError($error = $this->_put('NOOP'))) {
+            return $error;
+        }
+        if (PEAR::isError($error = $this->_parseResponse(250))) {
+            return $error;
+        }
+
+        return true;
+    }
+
+    /**
+     * Backwards-compatibility method.  identifySender()'s functionality is
+     * now handled internally.
+     *
+     * @return  boolean     This method always return true.
+     *
+     * @access  public
+     * @since   1.0
+     */
+    function identifySender()
+    {
+        return true;
+    }
+
+}
diff --git a/program/lib/Net/Socket.php b/program/lib/Net/Socket.php
new file mode 100644 (file)
index 0000000..c47eea8
--- /dev/null
@@ -0,0 +1,456 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.0 of the PHP license,       |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/2_02.txt.                                 |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Authors: Stig Bakken <ssb@php.net>                                   |
+// |          Chuck Hagenbuch <chuck@horde.org>                           |
+// +----------------------------------------------------------------------+
+//
+// $Id: Socket.php 17 2005-10-03 20:25:31Z roundcube $
+//
+
+require_once 'PEAR.php';
+
+/**
+ * Generalized Socket class. More docs to be written.
+ *
+ * @version 1.0
+ * @author Stig Bakken <ssb@php.net>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ */
+class Net_Socket extends PEAR {
+    // {{{ properties
+
+    /** Socket file pointer. */
+    var $fp = null;
+
+    /** Whether the socket is blocking. */
+    var $blocking = true;
+
+    /** Whether the socket is persistent. */
+    var $persistent = false;
+
+    /** The IP address to connect to. */
+    var $addr = '';
+
+    /** The port number to connect to. */
+    var $port = 0;
+
+    /** Number of seconds to wait on socket connections before
+        assuming there's no more data. */
+    var $timeout = false;
+
+    /** Number of bytes to read at a time in readLine() and
+        readAll(). */
+    var $lineLength = 2048;
+    // }}}
+
+    // {{{ constructor
+    /**
+     * Constructs a new Net_Socket object.
+     *
+     * @access public
+     */
+    function Net_Socket()
+    {
+        $this->PEAR();
+    }
+    // }}}
+
+    // {{{ connect()
+    /**
+     * Connect to the specified port. If called when the socket is
+     * already connected, it disconnects and connects again.
+     *
+     * @param $addr string IP address or host name
+     * @param $port int TCP port number
+     * @param $persistent bool (optional) whether the connection is
+     *        persistent (kept open between requests by the web server)
+     * @param $timeout int (optional) how long to wait for data
+     * @param $options array see options for stream_context_create
+     * @access public
+     * @return mixed true on success or error object
+     */
+    function connect($addr, $port, $persistent = null, $timeout = null, $options = null)
+    {
+        if (is_resource($this->fp)) {
+            @fclose($this->fp);
+            $this->fp = null;
+        }
+
+        if (strspn($addr, '.0123456789') == strlen($addr)) {
+            $this->addr = $addr;
+        } else {
+            $this->addr = gethostbyname($addr);
+        }
+        $this->port = $port % 65536;
+        if ($persistent !== null) {
+            $this->persistent = $persistent;
+        }
+        if ($timeout !== null) {
+            $this->timeout = $timeout;
+        }
+        $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
+        $errno = 0;
+        $errstr = '';
+        if ($options && function_exists('stream_context_create')) {
+            if ($this->timeout) {
+                $timeout = $this->timeout;
+            } else {
+                $timeout = 0;
+            }
+            $context = stream_context_create($options);
+            $fp = $openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context);
+        } else {
+            if ($this->timeout) {
+                $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout);
+            } else {
+                $fp = @$openfunc($this->addr, $this->port, $errno, $errstr);
+            }
+        }
+
+        if (!$fp) {
+            return $this->raiseError($errstr, $errno);
+        }
+
+        $this->fp = $fp;
+
+        return $this->setBlocking($this->blocking);
+    }
+    // }}}
+
+    // {{{ disconnect()
+    /**
+     * Disconnects from the peer, closes the socket.
+     *
+     * @access public
+     * @return mixed true on success or an error object otherwise
+     */
+    function disconnect()
+    {
+        if (is_resource($this->fp)) {
+            fclose($this->fp);
+            $this->fp = null;
+            return true;
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ isBlocking()
+    /**
+     * Find out if the socket is in blocking mode.
+     *
+     * @access public
+     * @return bool the current blocking mode.
+     */
+    function isBlocking()
+    {
+        return $this->blocking;
+    }
+    // }}}
+
+    // {{{ setBlocking()
+    /**
+     * Sets whether the socket connection should be blocking or
+     * not. A read call to a non-blocking socket will return immediately
+     * if there is no data available, whereas it will block until there
+     * is data for blocking sockets.
+     *
+     * @param $mode bool true for blocking sockets, false for nonblocking
+     * @access public
+     * @return mixed true on success or an error object otherwise
+     */
+    function setBlocking($mode)
+    {
+        if (is_resource($this->fp)) {
+            $this->blocking = $mode;
+            socket_set_blocking($this->fp, $this->blocking);
+            return true;
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ setTimeout()
+    /**
+     * Sets the timeout value on socket descriptor,
+     * expressed in the sum of seconds and microseconds
+     *
+     * @param $seconds int seconds
+     * @param $microseconds int microseconds
+     * @access public
+     * @return mixed true on success or an error object otherwise
+     */
+    function setTimeout($seconds, $microseconds)
+    {
+        if (is_resource($this->fp)) {
+            socket_set_timeout($this->fp, $seconds, $microseconds);
+            return true;
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ getStatus()
+    /**
+     * Returns information about an existing socket resource.
+     * Currently returns four entries in the result array:
+     *
+     * <p>
+     * timed_out (bool) - The socket timed out waiting for data<br>
+     * blocked (bool) - The socket was blocked<br>
+     * eof (bool) - Indicates EOF event<br>
+     * unread_bytes (int) - Number of bytes left in the socket buffer<br>
+     * </p>
+     *
+     * @access public
+     * @return mixed Array containing information about existing socket resource or an error object otherwise
+     */
+    function getStatus()
+    {
+        if (is_resource($this->fp)) {
+            return socket_get_status($this->fp);
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ gets()
+    /**
+     * Get a specified line of data
+     *
+     * @access public
+     * @return $size bytes of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function gets($size)
+    {
+        if (is_resource($this->fp)) {
+            return fgets($this->fp, $size);
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ read()
+    /**
+     * Read a specified amount of data. This is guaranteed to return,
+     * and has the added benefit of getting everything in one fread()
+     * chunk; if you know the size of the data you're getting
+     * beforehand, this is definitely the way to go.
+     *
+     * @param $size The number of bytes to read from the socket.
+     * @access public
+     * @return $size bytes of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function read($size)
+    {
+        if (is_resource($this->fp)) {
+            return fread($this->fp, $size);
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ write()
+    /**
+     * Write a specified amount of data.
+     *
+     * @access public
+     * @return mixed true on success or an error object otherwise
+     */
+    function write($data)
+    {
+        if (is_resource($this->fp)) {
+            return fwrite($this->fp, $data);
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ writeLine()
+    /**
+     * Write a line of data to the socket, followed by a trailing "\r\n".
+     *
+     * @access public
+     * @return mixed fputs result, or an error
+     */
+    function writeLine ($data)
+    {
+        if (is_resource($this->fp)) {
+            return $this->write($data . "\r\n");
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ eof()
+    /**
+     * Tests for end-of-file on a socket descriptor
+     *
+     * @access public
+     * @return bool
+     */
+    function eof()
+    {
+        return (is_resource($this->fp) && feof($this->fp));
+    }
+    // }}}
+
+    // {{{ readByte()
+    /**
+     * Reads a byte of data
+     *
+     * @access public
+     * @return 1 byte of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function readByte()
+    {
+        if (is_resource($this->fp)) {
+            return ord($this->read(1));
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ readWord()
+    /**
+     * Reads a word of data
+     *
+     * @access public
+     * @return 1 word of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function readWord()
+    {
+        if (is_resource($this->fp)) {
+            $buf = $this->read(2);
+            return (ord($buf[0]) + (ord($buf[1]) << 8));
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ readInt()
+    /**
+     * Reads an int of data
+     *
+     * @access public
+     * @return 1 int of data from the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function readInt()
+    {
+        if (is_resource($this->fp)) {
+            $buf = $this->read(4);
+            return (ord($buf[0]) + (ord($buf[1]) << 8) +
+                    (ord($buf[2]) << 16) + (ord($buf[3]) << 24));
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ readString()
+    /**
+     * Reads a zeroterminated string of data
+     *
+     * @access public
+     * @return string, or a PEAR_Error if
+     *         not connected.
+     */
+    function readString()
+    {
+        if (is_resource($this->fp)) {
+            $string = '';
+            while (($char = $this->read(1)) != "\x00")  {
+                $string .= $char;
+            }
+            return $string;
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ readIPAddress()
+    /**
+     * Reads an IP Address and returns it in a dot formated string
+     *
+     * @access public
+     * @return Dot formated string, or a PEAR_Error if
+     *         not connected.
+     */
+    function readIPAddress()
+    {
+        if (is_resource($this->fp)) {
+            $buf = $this->read(4);
+            return sprintf("%s.%s.%s.%s", ord($buf[0]), ord($buf[1]),
+                           ord($buf[2]), ord($buf[3]));
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ readLine()
+    /**
+     * Read until either the end of the socket or a newline, whichever
+     * comes first. Strips the trailing newline from the returned data.
+     *
+     * @access public
+     * @return All available data up to a newline, without that
+     *         newline, or until the end of the socket, or a PEAR_Error if
+     *         not connected.
+     */
+    function readLine()
+    {
+        if (is_resource($this->fp)) {
+            $line = '';
+            $timeout = time() + $this->timeout;
+            while (!$this->eof() && (!$this->timeout || time() < $timeout)) {
+                $line .= $this->gets($this->lineLength);
+                if (substr($line, -2) == "\r\n" ||
+                    substr($line, -1) == "\n") {
+                    return rtrim($line, "\r\n");
+                }
+            }
+            return $line;
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+    // {{{ readAll()
+    /**
+     * Read until the socket closes. THIS FUNCTION WILL NOT EXIT if the
+     * socket is in blocking mode until the socket closes.
+     *
+     * @access public
+     * @return All data until the socket closes, or a PEAR_Error if
+     *         not connected.
+     */
+    function readAll()
+    {
+        if (is_resource($this->fp)) {
+            $data = '';
+            while (!$this->eof())
+                $data .= $this->read($this->lineLength);
+            return $data;
+        }
+        return $this->raiseError("not connected");
+    }
+    // }}}
+
+}
diff --git a/program/lib/PEAR.php b/program/lib/PEAR.php
new file mode 100644 (file)
index 0000000..0c0a51b
--- /dev/null
@@ -0,0 +1,1101 @@
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * PEAR class and PEAR_Error class
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Sterling Hughes <sterling@php.net>
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: PEAR.php 157 2006-03-03 16:15:07Z roundcube $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**#@+
+ * ERROR constants
+ */
+define('PEAR_ERROR_RETURN',     1);
+define('PEAR_ERROR_PRINT',      2);
+define('PEAR_ERROR_TRIGGER',    4);
+define('PEAR_ERROR_DIE',        8);
+define('PEAR_ERROR_CALLBACK',  16);
+/**
+ * WARNING: obsolete
+ * @deprecated
+ */
+define('PEAR_ERROR_EXCEPTION', 32);
+/**#@-*/
+define('PEAR_ZE2', (function_exists('version_compare') &&
+                    version_compare(zend_version(), "2-dev", "ge")));
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+    define('OS_WINDOWS', true);
+    define('OS_UNIX',    false);
+    define('PEAR_OS',    'Windows');
+} else {
+    define('OS_WINDOWS', false);
+    define('OS_UNIX',    true);
+    define('PEAR_OS',    'Unix'); // blatant assumption
+}
+
+// instant backwards compatibility
+if (!defined('PATH_SEPARATOR')) {
+    if (OS_WINDOWS) {
+        define('PATH_SEPARATOR', ';');
+    } else {
+        define('PATH_SEPARATOR', ':');
+    }
+}
+
+$GLOBALS['_PEAR_default_error_mode']     = PEAR_ERROR_RETURN;
+$GLOBALS['_PEAR_default_error_options']  = E_USER_NOTICE;
+$GLOBALS['_PEAR_destructor_object_list'] = array();
+$GLOBALS['_PEAR_shutdown_funcs']         = array();
+$GLOBALS['_PEAR_error_handler_stack']    = array();
+
+@ini_set('track_errors', true);
+
+/**
+ * Base class for other PEAR classes.  Provides rudimentary
+ * emulation of destructors.
+ *
+ * If you want a destructor in your class, inherit PEAR and make a
+ * destructor method called _yourclassname (same name as the
+ * constructor, but with a "_" prefix).  Also, in your constructor you
+ * have to call the PEAR constructor: $this->PEAR();.
+ * The destructor method will be called without parameters.  Note that
+ * at in some SAPI implementations (such as Apache), any output during
+ * the request shutdown (in which destructors are called) seems to be
+ * discarded.  If you need to get any debug information from your
+ * destructor, use error_log(), syslog() or something similar.
+ *
+ * IMPORTANT! To use the emulated destructors you need to create the
+ * objects by reference: $obj =& new PEAR_child;
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: 1.4.7
+ * @link       http://pear.php.net/package/PEAR
+ * @see        PEAR_Error
+ * @since      Class available since PHP 4.0.2
+ * @link        http://pear.php.net/manual/en/core.pear.php#core.pear.pear
+ */
+class PEAR
+{
+    // {{{ properties
+
+    /**
+     * Whether to enable internal debug messages.
+     *
+     * @var     bool
+     * @access  private
+     */
+    var $_debug = false;
+
+    /**
+     * Default error mode for this object.
+     *
+     * @var     int
+     * @access  private
+     */
+    var $_default_error_mode = null;
+
+    /**
+     * Default error options used for this object when error mode
+     * is PEAR_ERROR_TRIGGER.
+     *
+     * @var     int
+     * @access  private
+     */
+    var $_default_error_options = null;
+
+    /**
+     * Default error handler (callback) for this object, if error mode is
+     * PEAR_ERROR_CALLBACK.
+     *
+     * @var     string
+     * @access  private
+     */
+    var $_default_error_handler = '';
+
+    /**
+     * Which class to use for error objects.
+     *
+     * @var     string
+     * @access  private
+     */
+    var $_error_class = 'PEAR_Error';
+
+    /**
+     * An array of expected errors.
+     *
+     * @var     array
+     * @access  private
+     */
+    var $_expected_errors = array();
+
+    // }}}
+
+    // {{{ constructor
+
+    /**
+     * Constructor.  Registers this object in
+     * $_PEAR_destructor_object_list for destructor emulation if a
+     * destructor object exists.
+     *
+     * @param string $error_class  (optional) which class to use for
+     *        error objects, defaults to PEAR_Error.
+     * @access public
+     * @return void
+     */
+    function PEAR($error_class = null)
+    {
+        $classname = strtolower(get_class($this));
+        if ($this->_debug) {
+            print "PEAR constructor called, class=$classname\n";
+        }
+        if ($error_class !== null) {
+            $this->_error_class = $error_class;
+        }
+        while ($classname && strcasecmp($classname, "pear")) {
+            $destructor = "_$classname";
+            if (method_exists($this, $destructor)) {
+                global $_PEAR_destructor_object_list;
+                $_PEAR_destructor_object_list[] = &$this;
+                if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+                    register_shutdown_function("_PEAR_call_destructors");
+                    $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+                }
+                break;
+            } else {
+                $classname = get_parent_class($classname);
+            }
+        }
+    }
+
+    // }}}
+    // {{{ destructor
+
+    /**
+     * Destructor (the emulated type of...).  Does nothing right now,
+     * but is included for forward compatibility, so subclass
+     * destructors should always call it.
+     *
+     * See the note in the class desciption about output from
+     * destructors.
+     *
+     * @access public
+     * @return void
+     */
+    function _PEAR() {
+        if ($this->_debug) {
+            printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
+        }
+    }
+
+    // }}}
+    // {{{ getStaticProperty()
+
+    /**
+    * If you have a class that's mostly/entirely static, and you need static
+    * properties, you can use this method to simulate them. Eg. in your method(s)
+    * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
+    * You MUST use a reference, or they will not persist!
+    *
+    * @access public
+    * @param  string $class  The calling classname, to prevent clashes
+    * @param  string $var    The variable to retrieve.
+    * @return mixed   A reference to the variable. If not set it will be
+    *                 auto initialised to NULL.
+    */
+    function &getStaticProperty($class, $var)
+    {
+        static $properties;
+        return $properties[$class][$var];
+    }
+
+    // }}}
+    // {{{ registerShutdownFunc()
+
+    /**
+    * Use this function to register a shutdown method for static
+    * classes.
+    *
+    * @access public
+    * @param  mixed $func  The function name (or array of class/method) to call
+    * @param  mixed $args  The arguments to pass to the function
+    * @return void
+    */
+    function registerShutdownFunc($func, $args = array())
+    {
+        // if we are called statically, there is a potential
+        // that no shutdown func is registered.  Bug #6445
+        if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+            register_shutdown_function("_PEAR_call_destructors");
+            $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+        }
+        $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
+    }
+
+    // }}}
+    // {{{ isError()
+
+    /**
+     * Tell whether a value is a PEAR error.
+     *
+     * @param   mixed $data   the value to test
+     * @param   int   $code   if $data is an error object, return true
+     *                        only if $code is a string and
+     *                        $obj->getMessage() == $code or
+     *                        $code is an integer and $obj->getCode() == $code
+     * @access  public
+     * @return  bool    true if parameter is an error
+     */
+    function isError($data, $code = null)
+    {
+        if (is_a($data, 'PEAR_Error')) {
+            if (is_null($code)) {
+                return true;
+            } elseif (is_string($code)) {
+                return $data->getMessage() == $code;
+            } else {
+                return $data->getCode() == $code;
+            }
+        }
+        return false;
+    }
+
+    // }}}
+    // {{{ setErrorHandling()
+
+    /**
+     * Sets how errors generated by this object should be handled.
+     * Can be invoked both in objects and statically.  If called
+     * statically, setErrorHandling sets the default behaviour for all
+     * PEAR objects.  If called in an object, setErrorHandling sets
+     * the default behaviour for that object.
+     *
+     * @param int $mode
+     *        One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+     *        PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+     *        PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
+     *
+     * @param mixed $options
+     *        When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
+     *        of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+     *
+     *        When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
+     *        to be the callback function or method.  A callback
+     *        function is a string with the name of the function, a
+     *        callback method is an array of two elements: the element
+     *        at index 0 is the object, and the element at index 1 is
+     *        the name of the method to call in the object.
+     *
+     *        When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
+     *        a printf format string used when printing the error
+     *        message.
+     *
+     * @access public
+     * @return void
+     * @see PEAR_ERROR_RETURN
+     * @see PEAR_ERROR_PRINT
+     * @see PEAR_ERROR_TRIGGER
+     * @see PEAR_ERROR_DIE
+     * @see PEAR_ERROR_CALLBACK
+     * @see PEAR_ERROR_EXCEPTION
+     *
+     * @since PHP 4.0.5
+     */
+
+    function setErrorHandling($mode = null, $options = null)
+    {
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $setmode     = &$this->_default_error_mode;
+            $setoptions  = &$this->_default_error_options;
+        } else {
+            $setmode     = &$GLOBALS['_PEAR_default_error_mode'];
+            $setoptions  = &$GLOBALS['_PEAR_default_error_options'];
+        }
+
+        switch ($mode) {
+            case PEAR_ERROR_EXCEPTION:
+            case PEAR_ERROR_RETURN:
+            case PEAR_ERROR_PRINT:
+            case PEAR_ERROR_TRIGGER:
+            case PEAR_ERROR_DIE:
+            case null:
+                $setmode = $mode;
+                $setoptions = $options;
+                break;
+
+            case PEAR_ERROR_CALLBACK:
+                $setmode = $mode;
+                // class/object method callback
+                if (is_callable($options)) {
+                    $setoptions = $options;
+                } else {
+                    trigger_error("invalid error callback", E_USER_WARNING);
+                }
+                break;
+
+            default:
+                trigger_error("invalid error mode", E_USER_WARNING);
+                break;
+        }
+    }
+
+    // }}}
+    // {{{ expectError()
+
+    /**
+     * This method is used to tell which errors you expect to get.
+     * Expected errors are always returned with error mode
+     * PEAR_ERROR_RETURN.  Expected error codes are stored in a stack,
+     * and this method pushes a new element onto it.  The list of
+     * expected errors are in effect until they are popped off the
+     * stack with the popExpect() method.
+     *
+     * Note that this method can not be called statically
+     *
+     * @param mixed $code a single error code or an array of error codes to expect
+     *
+     * @return int     the new depth of the "expected errors" stack
+     * @access public
+     */
+    function expectError($code = '*')
+    {
+        if (is_array($code)) {
+            array_push($this->_expected_errors, $code);
+        } else {
+            array_push($this->_expected_errors, array($code));
+        }
+        return sizeof($this->_expected_errors);
+    }
+
+    // }}}
+    // {{{ popExpect()
+
+    /**
+     * This method pops one element off the expected error codes
+     * stack.
+     *
+     * @return array   the list of error codes that were popped
+     */
+    function popExpect()
+    {
+        return array_pop($this->_expected_errors);
+    }
+
+    // }}}
+    // {{{ _checkDelExpect()
+
+    /**
+     * This method checks unsets an error code if available
+     *
+     * @param mixed error code
+     * @return bool true if the error code was unset, false otherwise
+     * @access private
+     * @since PHP 4.3.0
+     */
+    function _checkDelExpect($error_code)
+    {
+        $deleted = false;
+
+        foreach ($this->_expected_errors AS $key => $error_array) {
+            if (in_array($error_code, $error_array)) {
+                unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
+                $deleted = true;
+            }
+
+            // clean up empty arrays
+            if (0 == count($this->_expected_errors[$key])) {
+                unset($this->_expected_errors[$key]);
+            }
+        }
+        return $deleted;
+    }
+
+    // }}}
+    // {{{ delExpect()
+
+    /**
+     * This method deletes all occurences of the specified element from
+     * the expected error codes stack.
+     *
+     * @param  mixed $error_code error code that should be deleted
+     * @return mixed list of error codes that were deleted or error
+     * @access public
+     * @since PHP 4.3.0
+     */
+    function delExpect($error_code)
+    {
+        $deleted = false;
+
+        if ((is_array($error_code) && (0 != count($error_code)))) {
+            // $error_code is a non-empty array here;
+            // we walk through it trying to unset all
+            // values
+            foreach($error_code as $key => $error) {
+                if ($this->_checkDelExpect($error)) {
+                    $deleted =  true;
+                } else {
+                    $deleted = false;
+                }
+            }
+            return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+        } elseif (!empty($error_code)) {
+            // $error_code comes alone, trying to unset it
+            if ($this->_checkDelExpect($error_code)) {
+                return true;
+            } else {
+                return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+            }
+        } else {
+            // $error_code is empty
+            return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
+        }
+    }
+
+    // }}}
+    // {{{ raiseError()
+
+    /**
+     * This method is a wrapper that returns an instance of the
+     * configured error class with this object's default error
+     * handling applied.  If the $mode and $options parameters are not
+     * specified, the object's defaults are used.
+     *
+     * @param mixed $message a text error message or a PEAR error object
+     *
+     * @param int $code      a numeric error code (it is up to your class
+     *                  to define these if you want to use codes)
+     *
+     * @param int $mode      One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+     *                  PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+     *                  PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
+     *
+     * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
+     *                  specifies the PHP-internal error level (one of
+     *                  E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+     *                  If $mode is PEAR_ERROR_CALLBACK, this
+     *                  parameter specifies the callback function or
+     *                  method.  In other error modes this parameter
+     *                  is ignored.
+     *
+     * @param string $userinfo If you need to pass along for example debug
+     *                  information, this parameter is meant for that.
+     *
+     * @param string $error_class The returned error object will be
+     *                  instantiated from this class, if specified.
+     *
+     * @param bool $skipmsg If true, raiseError will only pass error codes,
+     *                  the error message parameter will be dropped.
+     *
+     * @access public
+     * @return object   a PEAR error object
+     * @see PEAR::setErrorHandling
+     * @since PHP 4.0.5
+     */
+    function &raiseError($message = null,
+                         $code = null,
+                         $mode = null,
+                         $options = null,
+                         $userinfo = null,
+                         $error_class = null,
+                         $skipmsg = false)
+    {
+        // The error is yet a PEAR error object
+        if (is_object($message)) {
+            $code        = $message->getCode();
+            $userinfo    = $message->getUserInfo();
+            $error_class = $message->getType();
+            $message->error_message_prefix = '';
+            $message     = $message->getMessage();
+        }
+
+        if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) {
+            if ($exp[0] == "*" ||
+                (is_int(reset($exp)) && in_array($code, $exp)) ||
+                (is_string(reset($exp)) && in_array($message, $exp))) {
+                $mode = PEAR_ERROR_RETURN;
+            }
+        }
+        // No mode given, try global ones
+        if ($mode === null) {
+            // Class error handler
+            if (isset($this) && isset($this->_default_error_mode)) {
+                $mode    = $this->_default_error_mode;
+                $options = $this->_default_error_options;
+            // Global error handler
+            } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
+                $mode    = $GLOBALS['_PEAR_default_error_mode'];
+                $options = $GLOBALS['_PEAR_default_error_options'];
+            }
+        }
+
+        if ($error_class !== null) {
+            $ec = $error_class;
+        } elseif (isset($this) && isset($this->_error_class)) {
+            $ec = $this->_error_class;
+        } else {
+            $ec = 'PEAR_Error';
+        }
+        if ($skipmsg) {
+            $a = &new $ec($code, $mode, $options, $userinfo);
+            return $a;
+        } else {
+            $a = &new $ec($message, $code, $mode, $options, $userinfo);
+            return $a;
+        }
+    }
+
+    // }}}
+    // {{{ throwError()
+
+    /**
+     * Simpler form of raiseError with fewer options.  In most cases
+     * message, code and userinfo are enough.
+     *
+     * @param string $message
+     *
+     */
+    function &throwError($message = null,
+                         $code = null,
+                         $userinfo = null)
+    {
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $a = &$this->raiseError($message, $code, null, null, $userinfo);
+            return $a;
+        } else {
+            $a = &PEAR::raiseError($message, $code, null, null, $userinfo);
+            return $a;
+        }
+    }
+
+    // }}}
+    function staticPushErrorHandling($mode, $options = null)
+    {
+        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+        $def_mode    = &$GLOBALS['_PEAR_default_error_mode'];
+        $def_options = &$GLOBALS['_PEAR_default_error_options'];
+        $stack[] = array($def_mode, $def_options);
+        switch ($mode) {
+            case PEAR_ERROR_EXCEPTION:
+            case PEAR_ERROR_RETURN:
+            case PEAR_ERROR_PRINT:
+            case PEAR_ERROR_TRIGGER:
+            case PEAR_ERROR_DIE:
+            case null:
+                $def_mode = $mode;
+                $def_options = $options;
+                break;
+
+            case PEAR_ERROR_CALLBACK:
+                $def_mode = $mode;
+                // class/object method callback
+                if (is_callable($options)) {
+                    $def_options = $options;
+                } else {
+                    trigger_error("invalid error callback", E_USER_WARNING);
+                }
+                break;
+
+            default:
+                trigger_error("invalid error mode", E_USER_WARNING);
+                break;
+        }
+        $stack[] = array($mode, $options);
+        return true;
+    }
+
+    function staticPopErrorHandling()
+    {
+        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+        $setmode     = &$GLOBALS['_PEAR_default_error_mode'];
+        $setoptions  = &$GLOBALS['_PEAR_default_error_options'];
+        array_pop($stack);
+        list($mode, $options) = $stack[sizeof($stack) - 1];
+        array_pop($stack);
+        switch ($mode) {
+            case PEAR_ERROR_EXCEPTION:
+            case PEAR_ERROR_RETURN:
+            case PEAR_ERROR_PRINT:
+            case PEAR_ERROR_TRIGGER:
+            case PEAR_ERROR_DIE:
+            case null:
+                $setmode = $mode;
+                $setoptions = $options;
+                break;
+
+            case PEAR_ERROR_CALLBACK:
+                $setmode = $mode;
+                // class/object method callback
+                if (is_callable($options)) {
+                    $setoptions = $options;
+                } else {
+                    trigger_error("invalid error callback", E_USER_WARNING);
+                }
+                break;
+
+            default:
+                trigger_error("invalid error mode", E_USER_WARNING);
+                break;
+        }
+        return true;
+    }
+
+    // {{{ pushErrorHandling()
+
+    /**
+     * Push a new error handler on top of the error handler options stack. With this
+     * you can easily override the actual error handler for some code and restore
+     * it later with popErrorHandling.
+     *
+     * @param mixed $mode (same as setErrorHandling)
+     * @param mixed $options (same as setErrorHandling)
+     *
+     * @return bool Always true
+     *
+     * @see PEAR::setErrorHandling
+     */
+    function pushErrorHandling($mode, $options = null)
+    {
+        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $def_mode    = &$this->_default_error_mode;
+            $def_options = &$this->_default_error_options;
+        } else {
+            $def_mode    = &$GLOBALS['_PEAR_default_error_mode'];
+            $def_options = &$GLOBALS['_PEAR_default_error_options'];
+        }
+        $stack[] = array($def_mode, $def_options);
+
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $this->setErrorHandling($mode, $options);
+        } else {
+            PEAR::setErrorHandling($mode, $options);
+        }
+        $stack[] = array($mode, $options);
+        return true;
+    }
+
+    // }}}
+    // {{{ popErrorHandling()
+
+    /**
+    * Pop the last error handler used
+    *
+    * @return bool Always true
+    *
+    * @see PEAR::pushErrorHandling
+    */
+    function popErrorHandling()
+    {
+        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+        array_pop($stack);
+        list($mode, $options) = $stack[sizeof($stack) - 1];
+        array_pop($stack);
+        if (isset($this) && is_a($this, 'PEAR')) {
+            $this->setErrorHandling($mode, $options);
+        } else {
+            PEAR::setErrorHandling($mode, $options);
+        }
+        return true;
+    }
+
+    // }}}
+    // {{{ loadExtension()
+
+    /**
+    * OS independant PHP extension load. Remember to take care
+    * on the correct extension name for case sensitive OSes.
+    *
+    * @param string $ext The extension name
+    * @return bool Success or not on the dl() call
+    */
+    function loadExtension($ext)
+    {
+        if (!extension_loaded($ext)) {
+            // if either returns true dl() will produce a FATAL error, stop that
+            if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
+                return false;
+            }
+            if (OS_WINDOWS) {
+                $suffix = '.dll';
+            } elseif (PHP_OS == 'HP-UX') {
+                $suffix = '.sl';
+            } elseif (PHP_OS == 'AIX') {
+                $suffix = '.a';
+            } elseif (PHP_OS == 'OSX') {
+                $suffix = '.bundle';
+            } else {
+                $suffix = '.so';
+            }
+            return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+        }
+        return true;
+    }
+
+    // }}}
+}
+
+// {{{ _PEAR_call_destructors()
+
+function _PEAR_call_destructors()
+{
+    global $_PEAR_destructor_object_list;
+    if (is_array($_PEAR_destructor_object_list) &&
+        sizeof($_PEAR_destructor_object_list))
+    {
+        reset($_PEAR_destructor_object_list);
+        if (@PEAR::getStaticProperty('PEAR', 'destructlifo')) {
+            $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
+        }
+        while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
+            $classname = get_class($objref);
+            while ($classname) {
+                $destructor = "_$classname";
+                if (method_exists($objref, $destructor)) {
+                    $objref->$destructor();
+                    break;
+                } else {
+                    $classname = get_parent_class($classname);
+                }
+            }
+        }
+        // Empty the object list to ensure that destructors are
+        // not called more than once.
+        $_PEAR_destructor_object_list = array();
+    }
+
+    // Now call the shutdown functions
+    if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) {
+        foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
+            call_user_func_array($value[0], $value[1]);
+        }
+    }
+}
+
+// }}}
+/**
+ * Standard PEAR error class for PHP 4
+ *
+ * This class is supserseded by {@link PEAR_Exception} in PHP 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Gregory Beaver <cellog@php.net>
+ * @copyright  1997-2006 The PHP Group
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: 1.4.7
+ * @link       http://pear.php.net/manual/en/core.pear.pear-error.php
+ * @see        PEAR::raiseError(), PEAR::throwError()
+ * @since      Class available since PHP 4.0.2
+ */
+class PEAR_Error
+{
+    // {{{ properties
+
+    var $error_message_prefix = '';
+    var $mode                 = PEAR_ERROR_RETURN;
+    var $level                = E_USER_NOTICE;
+    var $code                 = -1;
+    var $message              = '';
+    var $userinfo             = '';
+    var $backtrace            = null;
+
+    // }}}
+    // {{{ constructor
+
+    /**
+     * PEAR_Error constructor
+     *
+     * @param string $message  message
+     *
+     * @param int $code     (optional) error code
+     *
+     * @param int $mode     (optional) error mode, one of: PEAR_ERROR_RETURN,
+     * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
+     * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
+     *
+     * @param mixed $options   (optional) error level, _OR_ in the case of
+     * PEAR_ERROR_CALLBACK, the callback function or object/method
+     * tuple.
+     *
+     * @param string $userinfo (optional) additional user/debug info
+     *
+     * @access public
+     *
+     */
+    function PEAR_Error($message = 'unknown error', $code = null,
+                        $mode = null, $options = null, $userinfo = null)
+    {
+        if ($mode === null) {
+            $mode = PEAR_ERROR_RETURN;
+        }
+        $this->message   = $message;
+        $this->code      = $code;
+        $this->mode      = $mode;
+        $this->userinfo  = $userinfo;
+        if (function_exists("debug_backtrace")) {
+            if (@!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) {
+                $this->backtrace = debug_backtrace();
+            }
+        }
+        if ($mode & PEAR_ERROR_CALLBACK) {
+            $this->level = E_USER_NOTICE;
+            $this->callback = $options;
+        } else {
+            if ($options === null) {
+                $options = E_USER_NOTICE;
+            }
+            $this->level = $options;
+            $this->callback = null;
+        }
+        if ($this->mode & PEAR_ERROR_PRINT) {
+            if (is_null($options) || is_int($options)) {
+                $format = "%s";
+            } else {
+                $format = $options;
+            }
+            printf($format, $this->getMessage());
+        }
+        if ($this->mode & PEAR_ERROR_TRIGGER) {
+            trigger_error($this->getMessage(), $this->level);
+        }
+        if ($this->mode & PEAR_ERROR_DIE) {
+            $msg = $this->getMessage();
+            if (is_null($options) || is_int($options)) {
+                $format = "%s";
+                if (substr($msg, -1) != "\n") {
+                    $msg .= "\n";
+                }
+            } else {
+                $format = $options;
+            }
+            die(sprintf($format, $msg));
+        }
+        if ($this->mode & PEAR_ERROR_CALLBACK) {
+            if (is_callable($this->callback)) {
+                call_user_func($this->callback, $this);
+            }
+        }
+        if ($this->mode & PEAR_ERROR_EXCEPTION) {
+            trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
+            eval('$e = new Exception($this->message, $this->code);throw($e);');
+        }
+    }
+
+    // }}}
+    // {{{ getMode()
+
+    /**
+     * Get the error mode from an error object.
+     *
+     * @return int error mode
+     * @access public
+     */
+    function getMode() {
+        return $this->mode;
+    }
+
+    // }}}
+    // {{{ getCallback()
+
+    /**
+     * Get the callback function/method from an error object.
+     *
+     * @return mixed callback function or object/method array
+     * @access public
+     */
+    function getCallback() {
+        return $this->callback;
+    }
+
+    // }}}
+    // {{{ getMessage()
+
+
+    /**
+     * Get the error message from an error object.
+     *
+     * @return  string  full error message
+     * @access public
+     */
+    function getMessage()
+    {
+        return ($this->error_message_prefix . $this->message);
+    }
+
+
+    // }}}
+    // {{{ getCode()
+
+    /**
+     * Get error code from an error object
+     *
+     * @return int error code
+     * @access public
+     */
+     function getCode()
+     {
+        return $this->code;
+     }
+
+    // }}}
+    // {{{ getType()
+
+    /**
+     * Get the name of this error/exception.
+     *
+     * @return string error/exception name (type)
+     * @access public
+     */
+    function getType()
+    {
+        return get_class($this);
+    }
+
+    // }}}
+    // {{{ getUserInfo()
+
+    /**
+     * Get additional user-supplied information.
+     *
+     * @return string user-supplied information
+     * @access public
+     */
+    function getUserInfo()
+    {
+        return $this->userinfo;
+    }
+
+    // }}}
+    // {{{ getDebugInfo()
+
+    /**
+     * Get additional debug information supplied by the application.
+     *
+     * @return string debug information
+     * @access public
+     */
+    function getDebugInfo()
+    {
+        return $this->getUserInfo();
+    }
+
+    // }}}
+    // {{{ getBacktrace()
+
+    /**
+     * Get the call backtrace from where the error was generated.
+     * Supported with PHP 4.3.0 or newer.
+     *
+     * @param int $frame (optional) what frame to fetch
+     * @return array Backtrace, or NULL if not available.
+     * @access public
+     */
+    function getBacktrace($frame = null)
+    {
+        if (defined('PEAR_IGNORE_BACKTRACE')) {
+            return null;
+        }
+        if ($frame === null) {
+            return $this->backtrace;
+        }
+        return $this->backtrace[$frame];
+    }
+
+    // }}}
+    // {{{ addUserInfo()
+
+    function addUserInfo($info)
+    {
+        if (empty($this->userinfo)) {
+            $this->userinfo = $info;
+        } else {
+            $this->userinfo .= " ** $info";
+        }
+    }
+
+    // }}}
+    // {{{ toString()
+
+    /**
+     * Make a string representation of this object.
+     *
+     * @return string a string with an object summary
+     * @access public
+     */
+    function toString() {
+        $modes = array();
+        $levels = array(E_USER_NOTICE  => 'notice',
+                        E_USER_WARNING => 'warning',
+                        E_USER_ERROR   => 'error');
+        if ($this->mode & PEAR_ERROR_CALLBACK) {
+            if (is_array($this->callback)) {
+                $callback = (is_object($this->callback[0]) ?
+                    strtolower(get_class($this->callback[0])) :
+                    $this->callback[0]) . '::' .
+                    $this->callback[1];
+            } else {
+                $callback = $this->callback;
+            }
+            return sprintf('[%s: message="%s" code=%d mode=callback '.
+                           'callback=%s prefix="%s" info="%s"]',
+                           strtolower(get_class($this)), $this->message, $this->code,
+                           $callback, $this->error_message_prefix,
+                           $this->userinfo);
+        }
+        if ($this->mode & PEAR_ERROR_PRINT) {
+            $modes[] = 'print';
+        }
+        if ($this->mode & PEAR_ERROR_TRIGGER) {
+            $modes[] = 'trigger';
+        }
+        if ($this->mode & PEAR_ERROR_DIE) {
+            $modes[] = 'die';
+        }
+        if ($this->mode & PEAR_ERROR_RETURN) {
+            $modes[] = 'return';
+        }
+        return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
+                       'prefix="%s" info="%s"]',
+                       strtolower(get_class($this)), $this->message, $this->code,
+                       implode("|", $modes), $levels[$this->level],
+                       $this->error_message_prefix,
+                       $this->userinfo);
+    }
+
+    // }}}
+}
+
+/*
+ * Local Variables:
+ * mode: php
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+?>
diff --git a/program/lib/encoding/CP1250.map b/program/lib/encoding/CP1250.map
new file mode 100644 (file)
index 0000000..081d776
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1250 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          04/15/98
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1250 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1250 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81           #UNDEFINED
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83           #UNDEFINED
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88           #UNDEFINED
+0x89   0x2030  #PER MILLE SIGN
+0x8A   0x0160  #LATIN CAPITAL LETTER S WITH CARON
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C   0x015A  #LATIN CAPITAL LETTER S WITH ACUTE
+0x8D   0x0164  #LATIN CAPITAL LETTER T WITH CARON
+0x8E   0x017D  #LATIN CAPITAL LETTER Z WITH CARON
+0x8F   0x0179  #LATIN CAPITAL LETTER Z WITH ACUTE
+0x90           #UNDEFINED
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98           #UNDEFINED
+0x99   0x2122  #TRADE MARK SIGN
+0x9A   0x0161  #LATIN SMALL LETTER S WITH CARON
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C   0x015B  #LATIN SMALL LETTER S WITH ACUTE
+0x9D   0x0165  #LATIN SMALL LETTER T WITH CARON
+0x9E   0x017E  #LATIN SMALL LETTER Z WITH CARON
+0x9F   0x017A  #LATIN SMALL LETTER Z WITH ACUTE
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1   0x02C7  #CARON
+0xA2   0x02D8  #BREVE
+0xA3   0x0141  #LATIN CAPITAL LETTER L WITH STROKE
+0xA4   0x00A4  #CURRENCY SIGN
+0xA5   0x0104  #LATIN CAPITAL LETTER A WITH OGONEK
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00A8  #DIAERESIS
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA   0x015E  #LATIN CAPITAL LETTER S WITH CEDILLA
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x017B  #LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x02DB  #OGONEK
+0xB3   0x0142  #LATIN SMALL LETTER L WITH STROKE
+0xB4   0x00B4  #ACUTE ACCENT
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x00B8  #CEDILLA
+0xB9   0x0105  #LATIN SMALL LETTER A WITH OGONEK
+0xBA   0x015F  #LATIN SMALL LETTER S WITH CEDILLA
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x013D  #LATIN CAPITAL LETTER L WITH CARON
+0xBD   0x02DD  #DOUBLE ACUTE ACCENT
+0xBE   0x013E  #LATIN SMALL LETTER L WITH CARON
+0xBF   0x017C  #LATIN SMALL LETTER Z WITH DOT ABOVE
+0xC0   0x0154  #LATIN CAPITAL LETTER R WITH ACUTE
+0xC1   0x00C1  #LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x0102  #LATIN CAPITAL LETTER A WITH BREVE
+0xC4   0x00C4  #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x0139  #LATIN CAPITAL LETTER L WITH ACUTE
+0xC6   0x0106  #LATIN CAPITAL LETTER C WITH ACUTE
+0xC7   0x00C7  #LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x010C  #LATIN CAPITAL LETTER C WITH CARON
+0xC9   0x00C9  #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x0118  #LATIN CAPITAL LETTER E WITH OGONEK
+0xCB   0x00CB  #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x011A  #LATIN CAPITAL LETTER E WITH CARON
+0xCD   0x00CD  #LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x010E  #LATIN CAPITAL LETTER D WITH CARON
+0xD0   0x0110  #LATIN CAPITAL LETTER D WITH STROKE
+0xD1   0x0143  #LATIN CAPITAL LETTER N WITH ACUTE
+0xD2   0x0147  #LATIN CAPITAL LETTER N WITH CARON
+0xD3   0x00D3  #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x0150  #LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0xD6   0x00D6  #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #MULTIPLICATION SIGN
+0xD8   0x0158  #LATIN CAPITAL LETTER R WITH CARON
+0xD9   0x016E  #LATIN CAPITAL LETTER U WITH RING ABOVE
+0xDA   0x00DA  #LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x0170  #LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0xDC   0x00DC  #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x00DD  #LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE   0x0162  #LATIN CAPITAL LETTER T WITH CEDILLA
+0xDF   0x00DF  #LATIN SMALL LETTER SHARP S
+0xE0   0x0155  #LATIN SMALL LETTER R WITH ACUTE
+0xE1   0x00E1  #LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x0103  #LATIN SMALL LETTER A WITH BREVE
+0xE4   0x00E4  #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x013A  #LATIN SMALL LETTER L WITH ACUTE
+0xE6   0x0107  #LATIN SMALL LETTER C WITH ACUTE
+0xE7   0x00E7  #LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x010D  #LATIN SMALL LETTER C WITH CARON
+0xE9   0x00E9  #LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x0119  #LATIN SMALL LETTER E WITH OGONEK
+0xEB   0x00EB  #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x011B  #LATIN SMALL LETTER E WITH CARON
+0xED   0x00ED  #LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x010F  #LATIN SMALL LETTER D WITH CARON
+0xF0   0x0111  #LATIN SMALL LETTER D WITH STROKE
+0xF1   0x0144  #LATIN SMALL LETTER N WITH ACUTE
+0xF2   0x0148  #LATIN SMALL LETTER N WITH CARON
+0xF3   0x00F3  #LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x0151  #LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0xF6   0x00F6  #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #DIVISION SIGN
+0xF8   0x0159  #LATIN SMALL LETTER R WITH CARON
+0xF9   0x016F  #LATIN SMALL LETTER U WITH RING ABOVE
+0xFA   0x00FA  #LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x0171  #LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0xFC   0x00FC  #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x00FD  #LATIN SMALL LETTER Y WITH ACUTE
+0xFE   0x0163  #LATIN SMALL LETTER T WITH CEDILLA
+0xFF   0x02D9  #DOT ABOVE
diff --git a/program/lib/encoding/CP1251.map b/program/lib/encoding/CP1251.map
new file mode 100644 (file)
index 0000000..e7d4f2c
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1251 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          04/15/98
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1251 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1251 order
+#
+0x00    0x0000  #NULL
+0x01    0x0001  #START OF HEADING
+0x02    0x0002  #START OF TEXT
+0x03    0x0003  #END OF TEXT
+0x04    0x0004  #END OF TRANSMISSION
+0x05    0x0005  #ENQUIRY
+0x06    0x0006  #ACKNOWLEDGE
+0x07    0x0007  #BELL
+0x08    0x0008  #BACKSPACE
+0x09    0x0009  #HORIZONTAL TABULATION
+0x0A    0x000A  #LINE FEED
+0x0B    0x000B  #VERTICAL TABULATION
+0x0C    0x000C  #FORM FEED
+0x0D    0x000D  #CARRIAGE RETURN
+0x0E    0x000E  #SHIFT OUT
+0x0F    0x000F  #SHIFT IN
+0x10    0x0010  #DATA LINK ESCAPE
+0x11    0x0011  #DEVICE CONTROL ONE
+0x12    0x0012  #DEVICE CONTROL TWO
+0x13    0x0013  #DEVICE CONTROL THREE
+0x14    0x0014  #DEVICE CONTROL FOUR
+0x15    0x0015  #NEGATIVE ACKNOWLEDGE
+0x16    0x0016  #SYNCHRONOUS IDLE
+0x17    0x0017  #END OF TRANSMISSION BLOCK
+0x18    0x0018  #CANCEL
+0x19    0x0019  #END OF MEDIUM
+0x1A    0x001A  #SUBSTITUTE
+0x1B    0x001B  #ESCAPE
+0x1C    0x001C  #FILE SEPARATOR
+0x1D    0x001D  #GROUP SEPARATOR
+0x1E    0x001E  #RECORD SEPARATOR
+0x1F    0x001F  #UNIT SEPARATOR
+0x20    0x0020  #SPACE
+0x21    0x0021  #EXCLAMATION MARK
+0x22    0x0022  #QUOTATION MARK
+0x23    0x0023  #NUMBER SIGN
+0x24    0x0024  #DOLLAR SIGN
+0x25    0x0025  #PERCENT SIGN
+0x26    0x0026  #AMPERSAND
+0x27    0x0027  #APOSTROPHE
+0x28    0x0028  #LEFT PARENTHESIS
+0x29    0x0029  #RIGHT PARENTHESIS
+0x2A    0x002A  #ASTERISK
+0x2B    0x002B  #PLUS SIGN
+0x2C    0x002C  #COMMA
+0x2D    0x002D  #HYPHEN-MINUS
+0x2E    0x002E  #FULL STOP
+0x2F    0x002F  #SOLIDUS
+0x30    0x0030  #DIGIT ZERO
+0x31    0x0031  #DIGIT ONE
+0x32    0x0032  #DIGIT TWO
+0x33    0x0033  #DIGIT THREE
+0x34    0x0034  #DIGIT FOUR
+0x35    0x0035  #DIGIT FIVE
+0x36    0x0036  #DIGIT SIX
+0x37    0x0037  #DIGIT SEVEN
+0x38    0x0038  #DIGIT EIGHT
+0x39    0x0039  #DIGIT NINE
+0x3A    0x003A  #COLON
+0x3B    0x003B  #SEMICOLON
+0x3C    0x003C  #LESS-THAN SIGN
+0x3D    0x003D  #EQUALS SIGN
+0x3E    0x003E  #GREATER-THAN SIGN
+0x3F    0x003F  #QUESTION MARK
+0x40    0x0040  #COMMERCIAL AT
+0x41    0x0041  #LATIN CAPITAL LETTER A
+0x42    0x0042  #LATIN CAPITAL LETTER B
+0x43    0x0043  #LATIN CAPITAL LETTER C
+0x44    0x0044  #LATIN CAPITAL LETTER D
+0x45    0x0045  #LATIN CAPITAL LETTER E
+0x46    0x0046  #LATIN CAPITAL LETTER F
+0x47    0x0047  #LATIN CAPITAL LETTER G
+0x48    0x0048  #LATIN CAPITAL LETTER H
+0x49    0x0049  #LATIN CAPITAL LETTER I
+0x4A    0x004A  #LATIN CAPITAL LETTER J
+0x4B    0x004B  #LATIN CAPITAL LETTER K
+0x4C    0x004C  #LATIN CAPITAL LETTER L
+0x4D    0x004D  #LATIN CAPITAL LETTER M
+0x4E    0x004E  #LATIN CAPITAL LETTER N
+0x4F    0x004F  #LATIN CAPITAL LETTER O
+0x50    0x0050  #LATIN CAPITAL LETTER P
+0x51    0x0051  #LATIN CAPITAL LETTER Q
+0x52    0x0052  #LATIN CAPITAL LETTER R
+0x53    0x0053  #LATIN CAPITAL LETTER S
+0x54    0x0054  #LATIN CAPITAL LETTER T
+0x55    0x0055  #LATIN CAPITAL LETTER U
+0x56    0x0056  #LATIN CAPITAL LETTER V
+0x57    0x0057  #LATIN CAPITAL LETTER W
+0x58    0x0058  #LATIN CAPITAL LETTER X
+0x59    0x0059  #LATIN CAPITAL LETTER Y
+0x5A    0x005A  #LATIN CAPITAL LETTER Z
+0x5B    0x005B  #LEFT SQUARE BRACKET
+0x5C    0x005C  #REVERSE SOLIDUS
+0x5D    0x005D  #RIGHT SQUARE BRACKET
+0x5E    0x005E  #CIRCUMFLEX ACCENT
+0x5F    0x005F  #LOW LINE
+0x60    0x0060  #GRAVE ACCENT
+0x61    0x0061  #LATIN SMALL LETTER A
+0x62    0x0062  #LATIN SMALL LETTER B
+0x63    0x0063  #LATIN SMALL LETTER C
+0x64    0x0064  #LATIN SMALL LETTER D
+0x65    0x0065  #LATIN SMALL LETTER E
+0x66    0x0066  #LATIN SMALL LETTER F
+0x67    0x0067  #LATIN SMALL LETTER G
+0x68    0x0068  #LATIN SMALL LETTER H
+0x69    0x0069  #LATIN SMALL LETTER I
+0x6A    0x006A  #LATIN SMALL LETTER J
+0x6B    0x006B  #LATIN SMALL LETTER K
+0x6C    0x006C  #LATIN SMALL LETTER L
+0x6D    0x006D  #LATIN SMALL LETTER M
+0x6E    0x006E  #LATIN SMALL LETTER N
+0x6F    0x006F  #LATIN SMALL LETTER O
+0x70    0x0070  #LATIN SMALL LETTER P
+0x71    0x0071  #LATIN SMALL LETTER Q
+0x72    0x0072  #LATIN SMALL LETTER R
+0x73    0x0073  #LATIN SMALL LETTER S
+0x74    0x0074  #LATIN SMALL LETTER T
+0x75    0x0075  #LATIN SMALL LETTER U
+0x76    0x0076  #LATIN SMALL LETTER V
+0x77    0x0077  #LATIN SMALL LETTER W
+0x78    0x0078  #LATIN SMALL LETTER X
+0x79    0x0079  #LATIN SMALL LETTER Y
+0x7A    0x007A  #LATIN SMALL LETTER Z
+0x7B    0x007B  #LEFT CURLY BRACKET
+0x7C    0x007C  #VERTICAL LINE
+0x7D    0x007D  #RIGHT CURLY BRACKET
+0x7E    0x007E  #TILDE
+0x7F    0x007F  #DELETE
+0x80    0x0402  #CYRILLIC CAPITAL LETTER DJE
+0x81    0x0403  #CYRILLIC CAPITAL LETTER GJE
+0x82    0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83    0x0453  #CYRILLIC SMALL LETTER GJE
+0x84    0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85    0x2026  #HORIZONTAL ELLIPSIS
+0x86    0x2020  #DAGGER
+0x87    0x2021  #DOUBLE DAGGER
+0x88    0x20AC  #EURO SIGN
+0x89    0x2030  #PER MILLE SIGN
+0x8A    0x0409  #CYRILLIC CAPITAL LETTER LJE
+0x8B    0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C    0x040A  #CYRILLIC CAPITAL LETTER NJE
+0x8D    0x040C  #CYRILLIC CAPITAL LETTER KJE
+0x8E    0x040B  #CYRILLIC CAPITAL LETTER TSHE
+0x8F    0x040F  #CYRILLIC CAPITAL LETTER DZHE
+0x90    0x0452  #CYRILLIC SMALL LETTER DJE
+0x91    0x2018  #LEFT SINGLE QUOTATION MARK
+0x92    0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93    0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94    0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95    0x2022  #BULLET
+0x96    0x2013  #EN DASH
+0x97    0x2014  #EM DASH
+0x98            #UNDEFINED
+0x99    0x2122  #TRADE MARK SIGN
+0x9A    0x0459  #CYRILLIC SMALL LETTER LJE
+0x9B    0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C    0x045A  #CYRILLIC SMALL LETTER NJE
+0x9D    0x045C  #CYRILLIC SMALL LETTER KJE
+0x9E    0x045B  #CYRILLIC SMALL LETTER TSHE
+0x9F    0x045F  #CYRILLIC SMALL LETTER DZHE
+0xA0    0x00A0  #NO-BREAK SPACE
+0xA1    0x040E  #CYRILLIC CAPITAL LETTER SHORT U
+0xA2    0x045E  #CYRILLIC SMALL LETTER SHORT U
+0xA3    0x0408  #CYRILLIC CAPITAL LETTER JE
+0xA4    0x00A4  #CURRENCY SIGN
+0xA5    0x0490  #CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0xA6    0x00A6  #BROKEN BAR
+0xA7    0x00A7  #SECTION SIGN
+0xA8    0x0401  #CYRILLIC CAPITAL LETTER IO
+0xA9    0x00A9  #COPYRIGHT SIGN
+0xAA    0x0404  #CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0xAB    0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC    0x00AC  #NOT SIGN
+0xAD    0x00AD  #SOFT HYPHEN
+0xAE    0x00AE  #REGISTERED SIGN
+0xAF    0x0407  #CYRILLIC CAPITAL LETTER YI
+0xB0    0x00B0  #DEGREE SIGN
+0xB1    0x00B1  #PLUS-MINUS SIGN
+0xB2    0x0406  #CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0xB3    0x0456  #CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+0xB4    0x0491  #CYRILLIC SMALL LETTER GHE WITH UPTURN
+0xB5    0x00B5  #MICRO SIGN
+0xB6    0x00B6  #PILCROW SIGN
+0xB7    0x00B7  #MIDDLE DOT
+0xB8    0x0451  #CYRILLIC SMALL LETTER IO
+0xB9    0x2116  #NUMERO SIGN
+0xBA    0x0454  #CYRILLIC SMALL LETTER UKRAINIAN IE
+0xBB    0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC    0x0458  #CYRILLIC SMALL LETTER JE
+0xBD    0x0405  #CYRILLIC CAPITAL LETTER DZE
+0xBE    0x0455  #CYRILLIC SMALL LETTER DZE
+0xBF    0x0457  #CYRILLIC SMALL LETTER YI
+0xC0    0x0410  #CYRILLIC CAPITAL LETTER A
+0xC1    0x0411  #CYRILLIC CAPITAL LETTER BE
+0xC2    0x0412  #CYRILLIC CAPITAL LETTER VE
+0xC3    0x0413  #CYRILLIC CAPITAL LETTER GHE
+0xC4    0x0414  #CYRILLIC CAPITAL LETTER DE
+0xC5    0x0415  #CYRILLIC CAPITAL LETTER IE
+0xC6    0x0416  #CYRILLIC CAPITAL LETTER ZHE
+0xC7    0x0417  #CYRILLIC CAPITAL LETTER ZE
+0xC8    0x0418  #CYRILLIC CAPITAL LETTER I
+0xC9    0x0419  #CYRILLIC CAPITAL LETTER SHORT I
+0xCA    0x041A  #CYRILLIC CAPITAL LETTER KA
+0xCB    0x041B  #CYRILLIC CAPITAL LETTER EL
+0xCC    0x041C  #CYRILLIC CAPITAL LETTER EM
+0xCD    0x041D  #CYRILLIC CAPITAL LETTER EN
+0xCE    0x041E  #CYRILLIC CAPITAL LETTER O
+0xCF    0x041F  #CYRILLIC CAPITAL LETTER PE
+0xD0    0x0420  #CYRILLIC CAPITAL LETTER ER
+0xD1    0x0421  #CYRILLIC CAPITAL LETTER ES
+0xD2    0x0422  #CYRILLIC CAPITAL LETTER TE
+0xD3    0x0423  #CYRILLIC CAPITAL LETTER U
+0xD4    0x0424  #CYRILLIC CAPITAL LETTER EF
+0xD5    0x0425  #CYRILLIC CAPITAL LETTER HA
+0xD6    0x0426  #CYRILLIC CAPITAL LETTER TSE
+0xD7    0x0427  #CYRILLIC CAPITAL LETTER CHE
+0xD8    0x0428  #CYRILLIC CAPITAL LETTER SHA
+0xD9    0x0429  #CYRILLIC CAPITAL LETTER SHCHA
+0xDA    0x042A  #CYRILLIC CAPITAL LETTER HARD SIGN
+0xDB    0x042B  #CYRILLIC CAPITAL LETTER YERU
+0xDC    0x042C  #CYRILLIC CAPITAL LETTER SOFT SIGN
+0xDD    0x042D  #CYRILLIC CAPITAL LETTER E
+0xDE    0x042E  #CYRILLIC CAPITAL LETTER YU
+0xDF    0x042F  #CYRILLIC CAPITAL LETTER YA
+0xE0    0x0430  #CYRILLIC SMALL LETTER A
+0xE1    0x0431  #CYRILLIC SMALL LETTER BE
+0xE2    0x0432  #CYRILLIC SMALL LETTER VE
+0xE3    0x0433  #CYRILLIC SMALL LETTER GHE
+0xE4    0x0434  #CYRILLIC SMALL LETTER DE
+0xE5    0x0435  #CYRILLIC SMALL LETTER IE
+0xE6    0x0436  #CYRILLIC SMALL LETTER ZHE
+0xE7    0x0437  #CYRILLIC SMALL LETTER ZE
+0xE8    0x0438  #CYRILLIC SMALL LETTER I
+0xE9    0x0439  #CYRILLIC SMALL LETTER SHORT I
+0xEA    0x043A  #CYRILLIC SMALL LETTER KA
+0xEB    0x043B  #CYRILLIC SMALL LETTER EL
+0xEC    0x043C  #CYRILLIC SMALL LETTER EM
+0xED    0x043D  #CYRILLIC SMALL LETTER EN
+0xEE    0x043E  #CYRILLIC SMALL LETTER O
+0xEF    0x043F  #CYRILLIC SMALL LETTER PE
+0xF0    0x0440  #CYRILLIC SMALL LETTER ER
+0xF1    0x0441  #CYRILLIC SMALL LETTER ES
+0xF2    0x0442  #CYRILLIC SMALL LETTER TE
+0xF3    0x0443  #CYRILLIC SMALL LETTER U
+0xF4    0x0444  #CYRILLIC SMALL LETTER EF
+0xF5    0x0445  #CYRILLIC SMALL LETTER HA
+0xF6    0x0446  #CYRILLIC SMALL LETTER TSE
+0xF7    0x0447  #CYRILLIC SMALL LETTER CHE
+0xF8    0x0448  #CYRILLIC SMALL LETTER SHA
+0xF9    0x0449  #CYRILLIC SMALL LETTER SHCHA
+0xFA    0x044A  #CYRILLIC SMALL LETTER HARD SIGN
+0xFB    0x044B  #CYRILLIC SMALL LETTER YERU
+0xFC    0x044C  #CYRILLIC SMALL LETTER SOFT SIGN
+0xFD    0x044D  #CYRILLIC SMALL LETTER E
+0xFE    0x044E  #CYRILLIC SMALL LETTER YU
+0xFF    0x044F  #CYRILLIC SMALL LETTER YA
diff --git a/program/lib/encoding/CP1252.map b/program/lib/encoding/CP1252.map
new file mode 100644 (file)
index 0000000..2ca4486
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1252 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          04/15/98
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1252 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1252 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81           #UNDEFINED
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83   0x0192  #LATIN SMALL LETTER F WITH HOOK
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88   0x02C6  #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89   0x2030  #PER MILLE SIGN
+0x8A   0x0160  #LATIN CAPITAL LETTER S WITH CARON
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C   0x0152  #LATIN CAPITAL LIGATURE OE
+0x8D           #UNDEFINED
+0x8E   0x017D  #LATIN CAPITAL LETTER Z WITH CARON
+0x8F           #UNDEFINED
+0x90           #UNDEFINED
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98   0x02DC  #SMALL TILDE
+0x99   0x2122  #TRADE MARK SIGN
+0x9A   0x0161  #LATIN SMALL LETTER S WITH CARON
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C   0x0153  #LATIN SMALL LIGATURE OE
+0x9D           #UNDEFINED
+0x9E   0x017E  #LATIN SMALL LETTER Z WITH CARON
+0x9F   0x0178  #LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1   0x00A1  #INVERTED EXCLAMATION MARK
+0xA2   0x00A2  #CENT SIGN
+0xA3   0x00A3  #POUND SIGN
+0xA4   0x00A4  #CURRENCY SIGN
+0xA5   0x00A5  #YEN SIGN
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00A8  #DIAERESIS
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA   0x00AA  #FEMININE ORDINAL INDICATOR
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x00AF  #MACRON
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x00B2  #SUPERSCRIPT TWO
+0xB3   0x00B3  #SUPERSCRIPT THREE
+0xB4   0x00B4  #ACUTE ACCENT
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x00B8  #CEDILLA
+0xB9   0x00B9  #SUPERSCRIPT ONE
+0xBA   0x00BA  #MASCULINE ORDINAL INDICATOR
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #VULGAR FRACTION THREE QUARTERS
+0xBF   0x00BF  #INVERTED QUESTION MARK
+0xC0   0x00C0  #LATIN CAPITAL LETTER A WITH GRAVE
+0xC1   0x00C1  #LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x00C3  #LATIN CAPITAL LETTER A WITH TILDE
+0xC4   0x00C4  #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x00C5  #LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6   0x00C6  #LATIN CAPITAL LETTER AE
+0xC7   0x00C7  #LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x00C8  #LATIN CAPITAL LETTER E WITH GRAVE
+0xC9   0x00C9  #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x00CA  #LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB   0x00CB  #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x00CC  #LATIN CAPITAL LETTER I WITH GRAVE
+0xCD   0x00CD  #LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x00CF  #LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0   0x00D0  #LATIN CAPITAL LETTER ETH
+0xD1   0x00D1  #LATIN CAPITAL LETTER N WITH TILDE
+0xD2   0x00D2  #LATIN CAPITAL LETTER O WITH GRAVE
+0xD3   0x00D3  #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x00D5  #LATIN CAPITAL LETTER O WITH TILDE
+0xD6   0x00D6  #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #MULTIPLICATION SIGN
+0xD8   0x00D8  #LATIN CAPITAL LETTER O WITH STROKE
+0xD9   0x00D9  #LATIN CAPITAL LETTER U WITH GRAVE
+0xDA   0x00DA  #LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x00DB  #LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC   0x00DC  #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x00DD  #LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE   0x00DE  #LATIN CAPITAL LETTER THORN
+0xDF   0x00DF  #LATIN SMALL LETTER SHARP S
+0xE0   0x00E0  #LATIN SMALL LETTER A WITH GRAVE
+0xE1   0x00E1  #LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x00E3  #LATIN SMALL LETTER A WITH TILDE
+0xE4   0x00E4  #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x00E5  #LATIN SMALL LETTER A WITH RING ABOVE
+0xE6   0x00E6  #LATIN SMALL LETTER AE
+0xE7   0x00E7  #LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x00E8  #LATIN SMALL LETTER E WITH GRAVE
+0xE9   0x00E9  #LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x00EA  #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB   0x00EB  #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x00EC  #LATIN SMALL LETTER I WITH GRAVE
+0xED   0x00ED  #LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x00EF  #LATIN SMALL LETTER I WITH DIAERESIS
+0xF0   0x00F0  #LATIN SMALL LETTER ETH
+0xF1   0x00F1  #LATIN SMALL LETTER N WITH TILDE
+0xF2   0x00F2  #LATIN SMALL LETTER O WITH GRAVE
+0xF3   0x00F3  #LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x00F5  #LATIN SMALL LETTER O WITH TILDE
+0xF6   0x00F6  #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #DIVISION SIGN
+0xF8   0x00F8  #LATIN SMALL LETTER O WITH STROKE
+0xF9   0x00F9  #LATIN SMALL LETTER U WITH GRAVE
+0xFA   0x00FA  #LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x00FB  #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x00FD  #LATIN SMALL LETTER Y WITH ACUTE
+0xFE   0x00FE  #LATIN SMALL LETTER THORN
+0xFF   0x00FF  #LATIN SMALL LETTER Y WITH DIAERESIS
diff --git a/program/lib/encoding/CP1253.map b/program/lib/encoding/CP1253.map
new file mode 100644 (file)
index 0000000..2ba51a0
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1253 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          04/15/98
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1253 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1253 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81           #UNDEFINED
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83   0x0192  #LATIN SMALL LETTER F WITH HOOK
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88           #UNDEFINED
+0x89   0x2030  #PER MILLE SIGN
+0x8A           #UNDEFINED
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C           #UNDEFINED
+0x8D           #UNDEFINED
+0x8E           #UNDEFINED
+0x8F           #UNDEFINED
+0x90           #UNDEFINED
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98           #UNDEFINED
+0x99   0x2122  #TRADE MARK SIGN
+0x9A           #UNDEFINED
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C           #UNDEFINED
+0x9D           #UNDEFINED
+0x9E           #UNDEFINED
+0x9F           #UNDEFINED
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1   0x0385  #GREEK DIALYTIKA TONOS
+0xA2   0x0386  #GREEK CAPITAL LETTER ALPHA WITH TONOS
+0xA3   0x00A3  #POUND SIGN
+0xA4   0x00A4  #CURRENCY SIGN
+0xA5   0x00A5  #YEN SIGN
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00A8  #DIAERESIS
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA           #UNDEFINED
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x2015  #HORIZONTAL BAR
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x00B2  #SUPERSCRIPT TWO
+0xB3   0x00B3  #SUPERSCRIPT THREE
+0xB4   0x0384  #GREEK TONOS
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x0388  #GREEK CAPITAL LETTER EPSILON WITH TONOS
+0xB9   0x0389  #GREEK CAPITAL LETTER ETA WITH TONOS
+0xBA   0x038A  #GREEK CAPITAL LETTER IOTA WITH TONOS
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x038C  #GREEK CAPITAL LETTER OMICRON WITH TONOS
+0xBD   0x00BD  #VULGAR FRACTION ONE HALF
+0xBE   0x038E  #GREEK CAPITAL LETTER UPSILON WITH TONOS
+0xBF   0x038F  #GREEK CAPITAL LETTER OMEGA WITH TONOS
+0xC0   0x0390  #GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0xC1   0x0391  #GREEK CAPITAL LETTER ALPHA
+0xC2   0x0392  #GREEK CAPITAL LETTER BETA
+0xC3   0x0393  #GREEK CAPITAL LETTER GAMMA
+0xC4   0x0394  #GREEK CAPITAL LETTER DELTA
+0xC5   0x0395  #GREEK CAPITAL LETTER EPSILON
+0xC6   0x0396  #GREEK CAPITAL LETTER ZETA
+0xC7   0x0397  #GREEK CAPITAL LETTER ETA
+0xC8   0x0398  #GREEK CAPITAL LETTER THETA
+0xC9   0x0399  #GREEK CAPITAL LETTER IOTA
+0xCA   0x039A  #GREEK CAPITAL LETTER KAPPA
+0xCB   0x039B  #GREEK CAPITAL LETTER LAMDA
+0xCC   0x039C  #GREEK CAPITAL LETTER MU
+0xCD   0x039D  #GREEK CAPITAL LETTER NU
+0xCE   0x039E  #GREEK CAPITAL LETTER XI
+0xCF   0x039F  #GREEK CAPITAL LETTER OMICRON
+0xD0   0x03A0  #GREEK CAPITAL LETTER PI
+0xD1   0x03A1  #GREEK CAPITAL LETTER RHO
+0xD2           #UNDEFINED
+0xD3   0x03A3  #GREEK CAPITAL LETTER SIGMA
+0xD4   0x03A4  #GREEK CAPITAL LETTER TAU
+0xD5   0x03A5  #GREEK CAPITAL LETTER UPSILON
+0xD6   0x03A6  #GREEK CAPITAL LETTER PHI
+0xD7   0x03A7  #GREEK CAPITAL LETTER CHI
+0xD8   0x03A8  #GREEK CAPITAL LETTER PSI
+0xD9   0x03A9  #GREEK CAPITAL LETTER OMEGA
+0xDA   0x03AA  #GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+0xDB   0x03AB  #GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+0xDC   0x03AC  #GREEK SMALL LETTER ALPHA WITH TONOS
+0xDD   0x03AD  #GREEK SMALL LETTER EPSILON WITH TONOS
+0xDE   0x03AE  #GREEK SMALL LETTER ETA WITH TONOS
+0xDF   0x03AF  #GREEK SMALL LETTER IOTA WITH TONOS
+0xE0   0x03B0  #GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+0xE1   0x03B1  #GREEK SMALL LETTER ALPHA
+0xE2   0x03B2  #GREEK SMALL LETTER BETA
+0xE3   0x03B3  #GREEK SMALL LETTER GAMMA
+0xE4   0x03B4  #GREEK SMALL LETTER DELTA
+0xE5   0x03B5  #GREEK SMALL LETTER EPSILON
+0xE6   0x03B6  #GREEK SMALL LETTER ZETA
+0xE7   0x03B7  #GREEK SMALL LETTER ETA
+0xE8   0x03B8  #GREEK SMALL LETTER THETA
+0xE9   0x03B9  #GREEK SMALL LETTER IOTA
+0xEA   0x03BA  #GREEK SMALL LETTER KAPPA
+0xEB   0x03BB  #GREEK SMALL LETTER LAMDA
+0xEC   0x03BC  #GREEK SMALL LETTER MU
+0xED   0x03BD  #GREEK SMALL LETTER NU
+0xEE   0x03BE  #GREEK SMALL LETTER XI
+0xEF   0x03BF  #GREEK SMALL LETTER OMICRON
+0xF0   0x03C0  #GREEK SMALL LETTER PI
+0xF1   0x03C1  #GREEK SMALL LETTER RHO
+0xF2   0x03C2  #GREEK SMALL LETTER FINAL SIGMA
+0xF3   0x03C3  #GREEK SMALL LETTER SIGMA
+0xF4   0x03C4  #GREEK SMALL LETTER TAU
+0xF5   0x03C5  #GREEK SMALL LETTER UPSILON
+0xF6   0x03C6  #GREEK SMALL LETTER PHI
+0xF7   0x03C7  #GREEK SMALL LETTER CHI
+0xF8   0x03C8  #GREEK SMALL LETTER PSI
+0xF9   0x03C9  #GREEK SMALL LETTER OMEGA
+0xFA   0x03CA  #GREEK SMALL LETTER IOTA WITH DIALYTIKA
+0xFB   0x03CB  #GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+0xFC   0x03CC  #GREEK SMALL LETTER OMICRON WITH TONOS
+0xFD   0x03CD  #GREEK SMALL LETTER UPSILON WITH TONOS
+0xFE   0x03CE  #GREEK SMALL LETTER OMEGA WITH TONOS
+0xFF           #UNDEFINED
diff --git a/program/lib/encoding/CP1254.map b/program/lib/encoding/CP1254.map
new file mode 100644 (file)
index 0000000..ca1a1eb
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1254 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          04/15/98
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1254 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1254 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81           #UNDEFINED
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83   0x0192  #LATIN SMALL LETTER F WITH HOOK
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88   0x02C6  #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89   0x2030  #PER MILLE SIGN
+0x8A   0x0160  #LATIN CAPITAL LETTER S WITH CARON
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C   0x0152  #LATIN CAPITAL LIGATURE OE
+0x8D           #UNDEFINED
+0x8E           #UNDEFINED
+0x8F           #UNDEFINED
+0x90           #UNDEFINED
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98   0x02DC  #SMALL TILDE
+0x99   0x2122  #TRADE MARK SIGN
+0x9A   0x0161  #LATIN SMALL LETTER S WITH CARON
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C   0x0153  #LATIN SMALL LIGATURE OE
+0x9D           #UNDEFINED
+0x9E           #UNDEFINED
+0x9F   0x0178  #LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1   0x00A1  #INVERTED EXCLAMATION MARK
+0xA2   0x00A2  #CENT SIGN
+0xA3   0x00A3  #POUND SIGN
+0xA4   0x00A4  #CURRENCY SIGN
+0xA5   0x00A5  #YEN SIGN
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00A8  #DIAERESIS
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA   0x00AA  #FEMININE ORDINAL INDICATOR
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x00AF  #MACRON
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x00B2  #SUPERSCRIPT TWO
+0xB3   0x00B3  #SUPERSCRIPT THREE
+0xB4   0x00B4  #ACUTE ACCENT
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x00B8  #CEDILLA
+0xB9   0x00B9  #SUPERSCRIPT ONE
+0xBA   0x00BA  #MASCULINE ORDINAL INDICATOR
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #VULGAR FRACTION THREE QUARTERS
+0xBF   0x00BF  #INVERTED QUESTION MARK
+0xC0   0x00C0  #LATIN CAPITAL LETTER A WITH GRAVE
+0xC1   0x00C1  #LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x00C3  #LATIN CAPITAL LETTER A WITH TILDE
+0xC4   0x00C4  #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x00C5  #LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6   0x00C6  #LATIN CAPITAL LETTER AE
+0xC7   0x00C7  #LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x00C8  #LATIN CAPITAL LETTER E WITH GRAVE
+0xC9   0x00C9  #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x00CA  #LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB   0x00CB  #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x00CC  #LATIN CAPITAL LETTER I WITH GRAVE
+0xCD   0x00CD  #LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x00CF  #LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0   0x011E  #LATIN CAPITAL LETTER G WITH BREVE
+0xD1   0x00D1  #LATIN CAPITAL LETTER N WITH TILDE
+0xD2   0x00D2  #LATIN CAPITAL LETTER O WITH GRAVE
+0xD3   0x00D3  #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x00D5  #LATIN CAPITAL LETTER O WITH TILDE
+0xD6   0x00D6  #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #MULTIPLICATION SIGN
+0xD8   0x00D8  #LATIN CAPITAL LETTER O WITH STROKE
+0xD9   0x00D9  #LATIN CAPITAL LETTER U WITH GRAVE
+0xDA   0x00DA  #LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x00DB  #LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC   0x00DC  #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x0130  #LATIN CAPITAL LETTER I WITH DOT ABOVE
+0xDE   0x015E  #LATIN CAPITAL LETTER S WITH CEDILLA
+0xDF   0x00DF  #LATIN SMALL LETTER SHARP S
+0xE0   0x00E0  #LATIN SMALL LETTER A WITH GRAVE
+0xE1   0x00E1  #LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x00E3  #LATIN SMALL LETTER A WITH TILDE
+0xE4   0x00E4  #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x00E5  #LATIN SMALL LETTER A WITH RING ABOVE
+0xE6   0x00E6  #LATIN SMALL LETTER AE
+0xE7   0x00E7  #LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x00E8  #LATIN SMALL LETTER E WITH GRAVE
+0xE9   0x00E9  #LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x00EA  #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB   0x00EB  #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x00EC  #LATIN SMALL LETTER I WITH GRAVE
+0xED   0x00ED  #LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x00EF  #LATIN SMALL LETTER I WITH DIAERESIS
+0xF0   0x011F  #LATIN SMALL LETTER G WITH BREVE
+0xF1   0x00F1  #LATIN SMALL LETTER N WITH TILDE
+0xF2   0x00F2  #LATIN SMALL LETTER O WITH GRAVE
+0xF3   0x00F3  #LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x00F5  #LATIN SMALL LETTER O WITH TILDE
+0xF6   0x00F6  #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #DIVISION SIGN
+0xF8   0x00F8  #LATIN SMALL LETTER O WITH STROKE
+0xF9   0x00F9  #LATIN SMALL LETTER U WITH GRAVE
+0xFA   0x00FA  #LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x00FB  #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x0131  #LATIN SMALL LETTER DOTLESS I
+0xFE   0x015F  #LATIN SMALL LETTER S WITH CEDILLA
+0xFF   0x00FF  #LATIN SMALL LETTER Y WITH DIAERESIS
diff --git a/program/lib/encoding/CP1255.map b/program/lib/encoding/CP1255.map
new file mode 100644 (file)
index 0000000..341517f
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1255 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          1/7/2000
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1255 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1255 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81           #UNDEFINED
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83   0x0192  #LATIN SMALL LETTER F WITH HOOK
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88   0x02C6  #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89   0x2030  #PER MILLE SIGN
+0x8A           #UNDEFINED
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C           #UNDEFINED
+0x8D           #UNDEFINED
+0x8E           #UNDEFINED
+0x8F           #UNDEFINED
+0x90           #UNDEFINED
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98   0x02DC  #SMALL TILDE
+0x99   0x2122  #TRADE MARK SIGN
+0x9A           #UNDEFINED
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C           #UNDEFINED
+0x9D           #UNDEFINED
+0x9E           #UNDEFINED
+0x9F           #UNDEFINED
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1   0x00A1  #INVERTED EXCLAMATION MARK
+0xA2   0x00A2  #CENT SIGN
+0xA3   0x00A3  #POUND SIGN
+0xA4   0x20AA  #NEW SHEQEL SIGN
+0xA5   0x00A5  #YEN SIGN
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00A8  #DIAERESIS
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA   0x00D7  #MULTIPLICATION SIGN
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x00AF  #MACRON
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x00B2  #SUPERSCRIPT TWO
+0xB3   0x00B3  #SUPERSCRIPT THREE
+0xB4   0x00B4  #ACUTE ACCENT
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x00B8  #CEDILLA
+0xB9   0x00B9  #SUPERSCRIPT ONE
+0xBA   0x00F7  #DIVISION SIGN
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #VULGAR FRACTION THREE QUARTERS
+0xBF   0x00BF  #INVERTED QUESTION MARK
+0xC0   0x05B0  #HEBREW POINT SHEVA
+0xC1   0x05B1  #HEBREW POINT HATAF SEGOL
+0xC2   0x05B2  #HEBREW POINT HATAF PATAH
+0xC3   0x05B3  #HEBREW POINT HATAF QAMATS
+0xC4   0x05B4  #HEBREW POINT HIRIQ
+0xC5   0x05B5  #HEBREW POINT TSERE
+0xC6   0x05B6  #HEBREW POINT SEGOL
+0xC7   0x05B7  #HEBREW POINT PATAH
+0xC8   0x05B8  #HEBREW POINT QAMATS
+0xC9   0x05B9  #HEBREW POINT HOLAM
+0xCA           #UNDEFINED
+0xCB   0x05BB  #HEBREW POINT QUBUTS
+0xCC   0x05BC  #HEBREW POINT DAGESH OR MAPIQ
+0xCD   0x05BD  #HEBREW POINT METEG
+0xCE   0x05BE  #HEBREW PUNCTUATION MAQAF
+0xCF   0x05BF  #HEBREW POINT RAFE
+0xD0   0x05C0  #HEBREW PUNCTUATION PASEQ
+0xD1   0x05C1  #HEBREW POINT SHIN DOT
+0xD2   0x05C2  #HEBREW POINT SIN DOT
+0xD3   0x05C3  #HEBREW PUNCTUATION SOF PASUQ
+0xD4   0x05F0  #HEBREW LIGATURE YIDDISH DOUBLE VAV
+0xD5   0x05F1  #HEBREW LIGATURE YIDDISH VAV YOD
+0xD6   0x05F2  #HEBREW LIGATURE YIDDISH DOUBLE YOD
+0xD7   0x05F3  #HEBREW PUNCTUATION GERESH
+0xD8   0x05F4  #HEBREW PUNCTUATION GERSHAYIM
+0xD9           #UNDEFINED
+0xDA           #UNDEFINED
+0xDB           #UNDEFINED
+0xDC           #UNDEFINED
+0xDD           #UNDEFINED
+0xDE           #UNDEFINED
+0xDF           #UNDEFINED
+0xE0   0x05D0  #HEBREW LETTER ALEF
+0xE1   0x05D1  #HEBREW LETTER BET
+0xE2   0x05D2  #HEBREW LETTER GIMEL
+0xE3   0x05D3  #HEBREW LETTER DALET
+0xE4   0x05D4  #HEBREW LETTER HE
+0xE5   0x05D5  #HEBREW LETTER VAV
+0xE6   0x05D6  #HEBREW LETTER ZAYIN
+0xE7   0x05D7  #HEBREW LETTER HET
+0xE8   0x05D8  #HEBREW LETTER TET
+0xE9   0x05D9  #HEBREW LETTER YOD
+0xEA   0x05DA  #HEBREW LETTER FINAL KAF
+0xEB   0x05DB  #HEBREW LETTER KAF
+0xEC   0x05DC  #HEBREW LETTER LAMED
+0xED   0x05DD  #HEBREW LETTER FINAL MEM
+0xEE   0x05DE  #HEBREW LETTER MEM
+0xEF   0x05DF  #HEBREW LETTER FINAL NUN
+0xF0   0x05E0  #HEBREW LETTER NUN
+0xF1   0x05E1  #HEBREW LETTER SAMEKH
+0xF2   0x05E2  #HEBREW LETTER AYIN
+0xF3   0x05E3  #HEBREW LETTER FINAL PE
+0xF4   0x05E4  #HEBREW LETTER PE
+0xF5   0x05E5  #HEBREW LETTER FINAL TSADI
+0xF6   0x05E6  #HEBREW LETTER TSADI
+0xF7   0x05E7  #HEBREW LETTER QOF
+0xF8   0x05E8  #HEBREW LETTER RESH
+0xF9   0x05E9  #HEBREW LETTER SHIN
+0xFA   0x05EA  #HEBREW LETTER TAV
+0xFB           #UNDEFINED
+0xFC           #UNDEFINED
+0xFD   0x200E  #LEFT-TO-RIGHT MARK
+0xFE   0x200F  #RIGHT-TO-LEFT MARK
+0xFF           #UNDEFINED
diff --git a/program/lib/encoding/CP1256.map b/program/lib/encoding/CP1256.map
new file mode 100644 (file)
index 0000000..0edd081
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1256 to Unicode table
+#    Unicode version: 2.1
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          01/5/99
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1256 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1256 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81   0x067E  #ARABIC LETTER PEH
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83   0x0192  #LATIN SMALL LETTER F WITH HOOK
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88   0x02C6  #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89   0x2030  #PER MILLE SIGN
+0x8A   0x0679  #ARABIC LETTER TTEH
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C   0x0152  #LATIN CAPITAL LIGATURE OE
+0x8D   0x0686  #ARABIC LETTER TCHEH
+0x8E   0x0698  #ARABIC LETTER JEH
+0x8F   0x0688  #ARABIC LETTER DDAL
+0x90   0x06AF  #ARABIC LETTER GAF
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98   0x06A9  #ARABIC LETTER KEHEH
+0x99   0x2122  #TRADE MARK SIGN
+0x9A   0x0691  #ARABIC LETTER RREH
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C   0x0153  #LATIN SMALL LIGATURE OE
+0x9D   0x200C  #ZERO WIDTH NON-JOINER
+0x9E   0x200D  #ZERO WIDTH JOINER
+0x9F   0x06BA  #ARABIC LETTER NOON GHUNNA
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1   0x060C  #ARABIC COMMA
+0xA2   0x00A2  #CENT SIGN
+0xA3   0x00A3  #POUND SIGN
+0xA4   0x00A4  #CURRENCY SIGN
+0xA5   0x00A5  #YEN SIGN
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00A8  #DIAERESIS
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA   0x06BE  #ARABIC LETTER HEH DOACHASHMEE
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x00AF  #MACRON
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x00B2  #SUPERSCRIPT TWO
+0xB3   0x00B3  #SUPERSCRIPT THREE
+0xB4   0x00B4  #ACUTE ACCENT
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x00B8  #CEDILLA
+0xB9   0x00B9  #SUPERSCRIPT ONE
+0xBA   0x061B  #ARABIC SEMICOLON
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #VULGAR FRACTION THREE QUARTERS
+0xBF   0x061F  #ARABIC QUESTION MARK
+0xC0   0x06C1  #ARABIC LETTER HEH GOAL
+0xC1   0x0621  #ARABIC LETTER HAMZA
+0xC2   0x0622  #ARABIC LETTER ALEF WITH MADDA ABOVE
+0xC3   0x0623  #ARABIC LETTER ALEF WITH HAMZA ABOVE
+0xC4   0x0624  #ARABIC LETTER WAW WITH HAMZA ABOVE
+0xC5   0x0625  #ARABIC LETTER ALEF WITH HAMZA BELOW
+0xC6   0x0626  #ARABIC LETTER YEH WITH HAMZA ABOVE
+0xC7   0x0627  #ARABIC LETTER ALEF
+0xC8   0x0628  #ARABIC LETTER BEH
+0xC9   0x0629  #ARABIC LETTER TEH MARBUTA
+0xCA   0x062A  #ARABIC LETTER TEH
+0xCB   0x062B  #ARABIC LETTER THEH
+0xCC   0x062C  #ARABIC LETTER JEEM
+0xCD   0x062D  #ARABIC LETTER HAH
+0xCE   0x062E  #ARABIC LETTER KHAH
+0xCF   0x062F  #ARABIC LETTER DAL
+0xD0   0x0630  #ARABIC LETTER THAL
+0xD1   0x0631  #ARABIC LETTER REH
+0xD2   0x0632  #ARABIC LETTER ZAIN
+0xD3   0x0633  #ARABIC LETTER SEEN
+0xD4   0x0634  #ARABIC LETTER SHEEN
+0xD5   0x0635  #ARABIC LETTER SAD
+0xD6   0x0636  #ARABIC LETTER DAD
+0xD7   0x00D7  #MULTIPLICATION SIGN
+0xD8   0x0637  #ARABIC LETTER TAH
+0xD9   0x0638  #ARABIC LETTER ZAH
+0xDA   0x0639  #ARABIC LETTER AIN
+0xDB   0x063A  #ARABIC LETTER GHAIN
+0xDC   0x0640  #ARABIC TATWEEL
+0xDD   0x0641  #ARABIC LETTER FEH
+0xDE   0x0642  #ARABIC LETTER QAF
+0xDF   0x0643  #ARABIC LETTER KAF
+0xE0   0x00E0  #LATIN SMALL LETTER A WITH GRAVE
+0xE1   0x0644  #ARABIC LETTER LAM
+0xE2   0x00E2  #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x0645  #ARABIC LETTER MEEM
+0xE4   0x0646  #ARABIC LETTER NOON
+0xE5   0x0647  #ARABIC LETTER HEH
+0xE6   0x0648  #ARABIC LETTER WAW
+0xE7   0x00E7  #LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x00E8  #LATIN SMALL LETTER E WITH GRAVE
+0xE9   0x00E9  #LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x00EA  #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB   0x00EB  #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x0649  #ARABIC LETTER ALEF MAKSURA
+0xED   0x064A  #ARABIC LETTER YEH
+0xEE   0x00EE  #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x00EF  #LATIN SMALL LETTER I WITH DIAERESIS
+0xF0   0x064B  #ARABIC FATHATAN
+0xF1   0x064C  #ARABIC DAMMATAN
+0xF2   0x064D  #ARABIC KASRATAN
+0xF3   0x064E  #ARABIC FATHA
+0xF4   0x00F4  #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x064F  #ARABIC DAMMA
+0xF6   0x0650  #ARABIC KASRA
+0xF7   0x00F7  #DIVISION SIGN
+0xF8   0x0651  #ARABIC SHADDA
+0xF9   0x00F9  #LATIN SMALL LETTER U WITH GRAVE
+0xFA   0x0652  #ARABIC SUKUN
+0xFB   0x00FB  #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x200E  #LEFT-TO-RIGHT MARK
+0xFE   0x200F  #RIGHT-TO-LEFT MARK
+0xFF   0x06D2  #ARABIC LETTER YEH BARREE
diff --git a/program/lib/encoding/CP1257.map b/program/lib/encoding/CP1257.map
new file mode 100644 (file)
index 0000000..97979d9
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1257 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          04/15/98
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1257 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1257 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81           #UNDEFINED
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83           #UNDEFINED
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88           #UNDEFINED
+0x89   0x2030  #PER MILLE SIGN
+0x8A           #UNDEFINED
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C           #UNDEFINED
+0x8D   0x00A8  #DIAERESIS
+0x8E   0x02C7  #CARON
+0x8F   0x00B8  #CEDILLA
+0x90           #UNDEFINED
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98           #UNDEFINED
+0x99   0x2122  #TRADE MARK SIGN
+0x9A           #UNDEFINED
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C           #UNDEFINED
+0x9D   0x00AF  #MACRON
+0x9E   0x02DB  #OGONEK
+0x9F           #UNDEFINED
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1           #UNDEFINED
+0xA2   0x00A2  #CENT SIGN
+0xA3   0x00A3  #POUND SIGN
+0xA4   0x00A4  #CURRENCY SIGN
+0xA5           #UNDEFINED
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00D8  #LATIN CAPITAL LETTER O WITH STROKE
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA   0x0156  #LATIN CAPITAL LETTER R WITH CEDILLA
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x00C6  #LATIN CAPITAL LETTER AE
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x00B2  #SUPERSCRIPT TWO
+0xB3   0x00B3  #SUPERSCRIPT THREE
+0xB4   0x00B4  #ACUTE ACCENT
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x00F8  #LATIN SMALL LETTER O WITH STROKE
+0xB9   0x00B9  #SUPERSCRIPT ONE
+0xBA   0x0157  #LATIN SMALL LETTER R WITH CEDILLA
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #VULGAR FRACTION THREE QUARTERS
+0xBF   0x00E6  #LATIN SMALL LETTER AE
+0xC0   0x0104  #LATIN CAPITAL LETTER A WITH OGONEK
+0xC1   0x012E  #LATIN CAPITAL LETTER I WITH OGONEK
+0xC2   0x0100  #LATIN CAPITAL LETTER A WITH MACRON
+0xC3   0x0106  #LATIN CAPITAL LETTER C WITH ACUTE
+0xC4   0x00C4  #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x00C5  #LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6   0x0118  #LATIN CAPITAL LETTER E WITH OGONEK
+0xC7   0x0112  #LATIN CAPITAL LETTER E WITH MACRON
+0xC8   0x010C  #LATIN CAPITAL LETTER C WITH CARON
+0xC9   0x00C9  #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x0179  #LATIN CAPITAL LETTER Z WITH ACUTE
+0xCB   0x0116  #LATIN CAPITAL LETTER E WITH DOT ABOVE
+0xCC   0x0122  #LATIN CAPITAL LETTER G WITH CEDILLA
+0xCD   0x0136  #LATIN CAPITAL LETTER K WITH CEDILLA
+0xCE   0x012A  #LATIN CAPITAL LETTER I WITH MACRON
+0xCF   0x013B  #LATIN CAPITAL LETTER L WITH CEDILLA
+0xD0   0x0160  #LATIN CAPITAL LETTER S WITH CARON
+0xD1   0x0143  #LATIN CAPITAL LETTER N WITH ACUTE
+0xD2   0x0145  #LATIN CAPITAL LETTER N WITH CEDILLA
+0xD3   0x00D3  #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x014C  #LATIN CAPITAL LETTER O WITH MACRON
+0xD5   0x00D5  #LATIN CAPITAL LETTER O WITH TILDE
+0xD6   0x00D6  #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #MULTIPLICATION SIGN
+0xD8   0x0172  #LATIN CAPITAL LETTER U WITH OGONEK
+0xD9   0x0141  #LATIN CAPITAL LETTER L WITH STROKE
+0xDA   0x015A  #LATIN CAPITAL LETTER S WITH ACUTE
+0xDB   0x016A  #LATIN CAPITAL LETTER U WITH MACRON
+0xDC   0x00DC  #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x017B  #LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xDE   0x017D  #LATIN CAPITAL LETTER Z WITH CARON
+0xDF   0x00DF  #LATIN SMALL LETTER SHARP S
+0xE0   0x0105  #LATIN SMALL LETTER A WITH OGONEK
+0xE1   0x012F  #LATIN SMALL LETTER I WITH OGONEK
+0xE2   0x0101  #LATIN SMALL LETTER A WITH MACRON
+0xE3   0x0107  #LATIN SMALL LETTER C WITH ACUTE
+0xE4   0x00E4  #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x00E5  #LATIN SMALL LETTER A WITH RING ABOVE
+0xE6   0x0119  #LATIN SMALL LETTER E WITH OGONEK
+0xE7   0x0113  #LATIN SMALL LETTER E WITH MACRON
+0xE8   0x010D  #LATIN SMALL LETTER C WITH CARON
+0xE9   0x00E9  #LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x017A  #LATIN SMALL LETTER Z WITH ACUTE
+0xEB   0x0117  #LATIN SMALL LETTER E WITH DOT ABOVE
+0xEC   0x0123  #LATIN SMALL LETTER G WITH CEDILLA
+0xED   0x0137  #LATIN SMALL LETTER K WITH CEDILLA
+0xEE   0x012B  #LATIN SMALL LETTER I WITH MACRON
+0xEF   0x013C  #LATIN SMALL LETTER L WITH CEDILLA
+0xF0   0x0161  #LATIN SMALL LETTER S WITH CARON
+0xF1   0x0144  #LATIN SMALL LETTER N WITH ACUTE
+0xF2   0x0146  #LATIN SMALL LETTER N WITH CEDILLA
+0xF3   0x00F3  #LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x014D  #LATIN SMALL LETTER O WITH MACRON
+0xF5   0x00F5  #LATIN SMALL LETTER O WITH TILDE
+0xF6   0x00F6  #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #DIVISION SIGN
+0xF8   0x0173  #LATIN SMALL LETTER U WITH OGONEK
+0xF9   0x0142  #LATIN SMALL LETTER L WITH STROKE
+0xFA   0x015B  #LATIN SMALL LETTER S WITH ACUTE
+0xFB   0x016B  #LATIN SMALL LETTER U WITH MACRON
+0xFC   0x00FC  #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x017C  #LATIN SMALL LETTER Z WITH DOT ABOVE
+0xFE   0x017E  #LATIN SMALL LETTER Z WITH CARON
+0xFF   0x02D9  #DOT ABOVE
diff --git a/program/lib/encoding/CP1258.map b/program/lib/encoding/CP1258.map
new file mode 100644 (file)
index 0000000..392310a
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    Name:     cp1258 to Unicode table
+#    Unicode version: 2.0
+#    Table version: 2.01
+#    Table format:  Format A
+#    Date:          04/15/98
+#
+#    Contact:       cpxlate@microsoft.com
+#
+#    General notes: none
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the cp1258 code (in hex)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in cp1258 order
+#
+0x00   0x0000  #NULL
+0x01   0x0001  #START OF HEADING
+0x02   0x0002  #START OF TEXT
+0x03   0x0003  #END OF TEXT
+0x04   0x0004  #END OF TRANSMISSION
+0x05   0x0005  #ENQUIRY
+0x06   0x0006  #ACKNOWLEDGE
+0x07   0x0007  #BELL
+0x08   0x0008  #BACKSPACE
+0x09   0x0009  #HORIZONTAL TABULATION
+0x0A   0x000A  #LINE FEED
+0x0B   0x000B  #VERTICAL TABULATION
+0x0C   0x000C  #FORM FEED
+0x0D   0x000D  #CARRIAGE RETURN
+0x0E   0x000E  #SHIFT OUT
+0x0F   0x000F  #SHIFT IN
+0x10   0x0010  #DATA LINK ESCAPE
+0x11   0x0011  #DEVICE CONTROL ONE
+0x12   0x0012  #DEVICE CONTROL TWO
+0x13   0x0013  #DEVICE CONTROL THREE
+0x14   0x0014  #DEVICE CONTROL FOUR
+0x15   0x0015  #NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #SYNCHRONOUS IDLE
+0x17   0x0017  #END OF TRANSMISSION BLOCK
+0x18   0x0018  #CANCEL
+0x19   0x0019  #END OF MEDIUM
+0x1A   0x001A  #SUBSTITUTE
+0x1B   0x001B  #ESCAPE
+0x1C   0x001C  #FILE SEPARATOR
+0x1D   0x001D  #GROUP SEPARATOR
+0x1E   0x001E  #RECORD SEPARATOR
+0x1F   0x001F  #UNIT SEPARATOR
+0x20   0x0020  #SPACE
+0x21   0x0021  #EXCLAMATION MARK
+0x22   0x0022  #QUOTATION MARK
+0x23   0x0023  #NUMBER SIGN
+0x24   0x0024  #DOLLAR SIGN
+0x25   0x0025  #PERCENT SIGN
+0x26   0x0026  #AMPERSAND
+0x27   0x0027  #APOSTROPHE
+0x28   0x0028  #LEFT PARENTHESIS
+0x29   0x0029  #RIGHT PARENTHESIS
+0x2A   0x002A  #ASTERISK
+0x2B   0x002B  #PLUS SIGN
+0x2C   0x002C  #COMMA
+0x2D   0x002D  #HYPHEN-MINUS
+0x2E   0x002E  #FULL STOP
+0x2F   0x002F  #SOLIDUS
+0x30   0x0030  #DIGIT ZERO
+0x31   0x0031  #DIGIT ONE
+0x32   0x0032  #DIGIT TWO
+0x33   0x0033  #DIGIT THREE
+0x34   0x0034  #DIGIT FOUR
+0x35   0x0035  #DIGIT FIVE
+0x36   0x0036  #DIGIT SIX
+0x37   0x0037  #DIGIT SEVEN
+0x38   0x0038  #DIGIT EIGHT
+0x39   0x0039  #DIGIT NINE
+0x3A   0x003A  #COLON
+0x3B   0x003B  #SEMICOLON
+0x3C   0x003C  #LESS-THAN SIGN
+0x3D   0x003D  #EQUALS SIGN
+0x3E   0x003E  #GREATER-THAN SIGN
+0x3F   0x003F  #QUESTION MARK
+0x40   0x0040  #COMMERCIAL AT
+0x41   0x0041  #LATIN CAPITAL LETTER A
+0x42   0x0042  #LATIN CAPITAL LETTER B
+0x43   0x0043  #LATIN CAPITAL LETTER C
+0x44   0x0044  #LATIN CAPITAL LETTER D
+0x45   0x0045  #LATIN CAPITAL LETTER E
+0x46   0x0046  #LATIN CAPITAL LETTER F
+0x47   0x0047  #LATIN CAPITAL LETTER G
+0x48   0x0048  #LATIN CAPITAL LETTER H
+0x49   0x0049  #LATIN CAPITAL LETTER I
+0x4A   0x004A  #LATIN CAPITAL LETTER J
+0x4B   0x004B  #LATIN CAPITAL LETTER K
+0x4C   0x004C  #LATIN CAPITAL LETTER L
+0x4D   0x004D  #LATIN CAPITAL LETTER M
+0x4E   0x004E  #LATIN CAPITAL LETTER N
+0x4F   0x004F  #LATIN CAPITAL LETTER O
+0x50   0x0050  #LATIN CAPITAL LETTER P
+0x51   0x0051  #LATIN CAPITAL LETTER Q
+0x52   0x0052  #LATIN CAPITAL LETTER R
+0x53   0x0053  #LATIN CAPITAL LETTER S
+0x54   0x0054  #LATIN CAPITAL LETTER T
+0x55   0x0055  #LATIN CAPITAL LETTER U
+0x56   0x0056  #LATIN CAPITAL LETTER V
+0x57   0x0057  #LATIN CAPITAL LETTER W
+0x58   0x0058  #LATIN CAPITAL LETTER X
+0x59   0x0059  #LATIN CAPITAL LETTER Y
+0x5A   0x005A  #LATIN CAPITAL LETTER Z
+0x5B   0x005B  #LEFT SQUARE BRACKET
+0x5C   0x005C  #REVERSE SOLIDUS
+0x5D   0x005D  #RIGHT SQUARE BRACKET
+0x5E   0x005E  #CIRCUMFLEX ACCENT
+0x5F   0x005F  #LOW LINE
+0x60   0x0060  #GRAVE ACCENT
+0x61   0x0061  #LATIN SMALL LETTER A
+0x62   0x0062  #LATIN SMALL LETTER B
+0x63   0x0063  #LATIN SMALL LETTER C
+0x64   0x0064  #LATIN SMALL LETTER D
+0x65   0x0065  #LATIN SMALL LETTER E
+0x66   0x0066  #LATIN SMALL LETTER F
+0x67   0x0067  #LATIN SMALL LETTER G
+0x68   0x0068  #LATIN SMALL LETTER H
+0x69   0x0069  #LATIN SMALL LETTER I
+0x6A   0x006A  #LATIN SMALL LETTER J
+0x6B   0x006B  #LATIN SMALL LETTER K
+0x6C   0x006C  #LATIN SMALL LETTER L
+0x6D   0x006D  #LATIN SMALL LETTER M
+0x6E   0x006E  #LATIN SMALL LETTER N
+0x6F   0x006F  #LATIN SMALL LETTER O
+0x70   0x0070  #LATIN SMALL LETTER P
+0x71   0x0071  #LATIN SMALL LETTER Q
+0x72   0x0072  #LATIN SMALL LETTER R
+0x73   0x0073  #LATIN SMALL LETTER S
+0x74   0x0074  #LATIN SMALL LETTER T
+0x75   0x0075  #LATIN SMALL LETTER U
+0x76   0x0076  #LATIN SMALL LETTER V
+0x77   0x0077  #LATIN SMALL LETTER W
+0x78   0x0078  #LATIN SMALL LETTER X
+0x79   0x0079  #LATIN SMALL LETTER Y
+0x7A   0x007A  #LATIN SMALL LETTER Z
+0x7B   0x007B  #LEFT CURLY BRACKET
+0x7C   0x007C  #VERTICAL LINE
+0x7D   0x007D  #RIGHT CURLY BRACKET
+0x7E   0x007E  #TILDE
+0x7F   0x007F  #DELETE
+0x80   0x20AC  #EURO SIGN
+0x81           #UNDEFINED
+0x82   0x201A  #SINGLE LOW-9 QUOTATION MARK
+0x83   0x0192  #LATIN SMALL LETTER F WITH HOOK
+0x84   0x201E  #DOUBLE LOW-9 QUOTATION MARK
+0x85   0x2026  #HORIZONTAL ELLIPSIS
+0x86   0x2020  #DAGGER
+0x87   0x2021  #DOUBLE DAGGER
+0x88   0x02C6  #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89   0x2030  #PER MILLE SIGN
+0x8A           #UNDEFINED
+0x8B   0x2039  #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C   0x0152  #LATIN CAPITAL LIGATURE OE
+0x8D           #UNDEFINED
+0x8E           #UNDEFINED
+0x8F           #UNDEFINED
+0x90           #UNDEFINED
+0x91   0x2018  #LEFT SINGLE QUOTATION MARK
+0x92   0x2019  #RIGHT SINGLE QUOTATION MARK
+0x93   0x201C  #LEFT DOUBLE QUOTATION MARK
+0x94   0x201D  #RIGHT DOUBLE QUOTATION MARK
+0x95   0x2022  #BULLET
+0x96   0x2013  #EN DASH
+0x97   0x2014  #EM DASH
+0x98   0x02DC  #SMALL TILDE
+0x99   0x2122  #TRADE MARK SIGN
+0x9A           #UNDEFINED
+0x9B   0x203A  #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C   0x0153  #LATIN SMALL LIGATURE OE
+0x9D           #UNDEFINED
+0x9E           #UNDEFINED
+0x9F   0x0178  #LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xA0   0x00A0  #NO-BREAK SPACE
+0xA1   0x00A1  #INVERTED EXCLAMATION MARK
+0xA2   0x00A2  #CENT SIGN
+0xA3   0x00A3  #POUND SIGN
+0xA4   0x00A4  #CURRENCY SIGN
+0xA5   0x00A5  #YEN SIGN
+0xA6   0x00A6  #BROKEN BAR
+0xA7   0x00A7  #SECTION SIGN
+0xA8   0x00A8  #DIAERESIS
+0xA9   0x00A9  #COPYRIGHT SIGN
+0xAA   0x00AA  #FEMININE ORDINAL INDICATOR
+0xAB   0x00AB  #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #NOT SIGN
+0xAD   0x00AD  #SOFT HYPHEN
+0xAE   0x00AE  #REGISTERED SIGN
+0xAF   0x00AF  #MACRON
+0xB0   0x00B0  #DEGREE SIGN
+0xB1   0x00B1  #PLUS-MINUS SIGN
+0xB2   0x00B2  #SUPERSCRIPT TWO
+0xB3   0x00B3  #SUPERSCRIPT THREE
+0xB4   0x00B4  #ACUTE ACCENT
+0xB5   0x00B5  #MICRO SIGN
+0xB6   0x00B6  #PILCROW SIGN
+0xB7   0x00B7  #MIDDLE DOT
+0xB8   0x00B8  #CEDILLA
+0xB9   0x00B9  #SUPERSCRIPT ONE
+0xBA   0x00BA  #MASCULINE ORDINAL INDICATOR
+0xBB   0x00BB  #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #VULGAR FRACTION THREE QUARTERS
+0xBF   0x00BF  #INVERTED QUESTION MARK
+0xC0   0x00C0  #LATIN CAPITAL LETTER A WITH GRAVE
+0xC1   0x00C1  #LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x0102  #LATIN CAPITAL LETTER A WITH BREVE
+0xC4   0x00C4  #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x00C5  #LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6   0x00C6  #LATIN CAPITAL LETTER AE
+0xC7   0x00C7  #LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x00C8  #LATIN CAPITAL LETTER E WITH GRAVE
+0xC9   0x00C9  #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x00CA  #LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB   0x00CB  #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x0300  #COMBINING GRAVE ACCENT
+0xCD   0x00CD  #LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x00CF  #LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0   0x0110  #LATIN CAPITAL LETTER D WITH STROKE
+0xD1   0x00D1  #LATIN CAPITAL LETTER N WITH TILDE
+0xD2   0x0309  #COMBINING HOOK ABOVE
+0xD3   0x00D3  #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x01A0  #LATIN CAPITAL LETTER O WITH HORN
+0xD6   0x00D6  #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #MULTIPLICATION SIGN
+0xD8   0x00D8  #LATIN CAPITAL LETTER O WITH STROKE
+0xD9   0x00D9  #LATIN CAPITAL LETTER U WITH GRAVE
+0xDA   0x00DA  #LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x00DB  #LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC   0x00DC  #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x01AF  #LATIN CAPITAL LETTER U WITH HORN
+0xDE   0x0303  #COMBINING TILDE
+0xDF   0x00DF  #LATIN SMALL LETTER SHARP S
+0xE0   0x00E0  #LATIN SMALL LETTER A WITH GRAVE
+0xE1   0x00E1  #LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x0103  #LATIN SMALL LETTER A WITH BREVE
+0xE4   0x00E4  #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x00E5  #LATIN SMALL LETTER A WITH RING ABOVE
+0xE6   0x00E6  #LATIN SMALL LETTER AE
+0xE7   0x00E7  #LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x00E8  #LATIN SMALL LETTER E WITH GRAVE
+0xE9   0x00E9  #LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x00EA  #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB   0x00EB  #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x0301  #COMBINING ACUTE ACCENT
+0xED   0x00ED  #LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x00EF  #LATIN SMALL LETTER I WITH DIAERESIS
+0xF0   0x0111  #LATIN SMALL LETTER D WITH STROKE
+0xF1   0x00F1  #LATIN SMALL LETTER N WITH TILDE
+0xF2   0x0323  #COMBINING DOT BELOW
+0xF3   0x00F3  #LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x01A1  #LATIN SMALL LETTER O WITH HORN
+0xF6   0x00F6  #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #DIVISION SIGN
+0xF8   0x00F8  #LATIN SMALL LETTER O WITH STROKE
+0xF9   0x00F9  #LATIN SMALL LETTER U WITH GRAVE
+0xFA   0x00FA  #LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x00FB  #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x01B0  #LATIN SMALL LETTER U WITH HORN
+0xFE   0x20AB  #DONG SIGN
+0xFF   0x00FF  #LATIN SMALL LETTER Y WITH DIAERESIS
diff --git a/program/lib/encoding/ISO-8859-1.map b/program/lib/encoding/ISO-8859-1.map
new file mode 100644 (file)
index 0000000..473ecab
--- /dev/null
@@ -0,0 +1,303 @@
+#
+#      Name:             ISO/IEC 8859-1:1998 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.0
+#      Table format:     Format A
+#      Date:             1999 July 27
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-1:1998 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-1 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-1 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA1   0x00A1  #       INVERTED EXCLAMATION MARK
+0xA2   0x00A2  #       CENT SIGN
+0xA3   0x00A3  #       POUND SIGN
+0xA4   0x00A4  #       CURRENCY SIGN
+0xA5   0x00A5  #       YEN SIGN
+0xA6   0x00A6  #       BROKEN BAR
+0xA7   0x00A7  #       SECTION SIGN
+0xA8   0x00A8  #       DIAERESIS
+0xA9   0x00A9  #       COPYRIGHT SIGN
+0xAA   0x00AA  #       FEMININE ORDINAL INDICATOR
+0xAB   0x00AB  #       LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #       NOT SIGN
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAE   0x00AE  #       REGISTERED SIGN
+0xAF   0x00AF  #       MACRON
+0xB0   0x00B0  #       DEGREE SIGN
+0xB1   0x00B1  #       PLUS-MINUS SIGN
+0xB2   0x00B2  #       SUPERSCRIPT TWO
+0xB3   0x00B3  #       SUPERSCRIPT THREE
+0xB4   0x00B4  #       ACUTE ACCENT
+0xB5   0x00B5  #       MICRO SIGN
+0xB6   0x00B6  #       PILCROW SIGN
+0xB7   0x00B7  #       MIDDLE DOT
+0xB8   0x00B8  #       CEDILLA
+0xB9   0x00B9  #       SUPERSCRIPT ONE
+0xBA   0x00BA  #       MASCULINE ORDINAL INDICATOR
+0xBB   0x00BB  #       RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #       VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #       VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #       VULGAR FRACTION THREE QUARTERS
+0xBF   0x00BF  #       INVERTED QUESTION MARK
+0xC0   0x00C0  #       LATIN CAPITAL LETTER A WITH GRAVE
+0xC1   0x00C1  #       LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #       LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x00C3  #       LATIN CAPITAL LETTER A WITH TILDE
+0xC4   0x00C4  #       LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x00C5  #       LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6   0x00C6  #       LATIN CAPITAL LETTER AE
+0xC7   0x00C7  #       LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x00C8  #       LATIN CAPITAL LETTER E WITH GRAVE
+0xC9   0x00C9  #       LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x00CA  #       LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB   0x00CB  #       LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x00CC  #       LATIN CAPITAL LETTER I WITH GRAVE
+0xCD   0x00CD  #       LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #       LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x00CF  #       LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0   0x00D0  #       LATIN CAPITAL LETTER ETH (Icelandic)
+0xD1   0x00D1  #       LATIN CAPITAL LETTER N WITH TILDE
+0xD2   0x00D2  #       LATIN CAPITAL LETTER O WITH GRAVE
+0xD3   0x00D3  #       LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #       LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x00D5  #       LATIN CAPITAL LETTER O WITH TILDE
+0xD6   0x00D6  #       LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #       MULTIPLICATION SIGN
+0xD8   0x00D8  #       LATIN CAPITAL LETTER O WITH STROKE
+0xD9   0x00D9  #       LATIN CAPITAL LETTER U WITH GRAVE
+0xDA   0x00DA  #       LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x00DB  #       LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC   0x00DC  #       LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x00DD  #       LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE   0x00DE  #       LATIN CAPITAL LETTER THORN (Icelandic)
+0xDF   0x00DF  #       LATIN SMALL LETTER SHARP S (German)
+0xE0   0x00E0  #       LATIN SMALL LETTER A WITH GRAVE
+0xE1   0x00E1  #       LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #       LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x00E3  #       LATIN SMALL LETTER A WITH TILDE
+0xE4   0x00E4  #       LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x00E5  #       LATIN SMALL LETTER A WITH RING ABOVE
+0xE6   0x00E6  #       LATIN SMALL LETTER AE
+0xE7   0x00E7  #       LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x00E8  #       LATIN SMALL LETTER E WITH GRAVE
+0xE9   0x00E9  #       LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x00EA  #       LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB   0x00EB  #       LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x00EC  #       LATIN SMALL LETTER I WITH GRAVE
+0xED   0x00ED  #       LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #       LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x00EF  #       LATIN SMALL LETTER I WITH DIAERESIS
+0xF0   0x00F0  #       LATIN SMALL LETTER ETH (Icelandic)
+0xF1   0x00F1  #       LATIN SMALL LETTER N WITH TILDE
+0xF2   0x00F2  #       LATIN SMALL LETTER O WITH GRAVE
+0xF3   0x00F3  #       LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #       LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x00F5  #       LATIN SMALL LETTER O WITH TILDE
+0xF6   0x00F6  #       LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #       DIVISION SIGN
+0xF8   0x00F8  #       LATIN SMALL LETTER O WITH STROKE
+0xF9   0x00F9  #       LATIN SMALL LETTER U WITH GRAVE
+0xFA   0x00FA  #       LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x00FB  #       LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #       LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x00FD  #       LATIN SMALL LETTER Y WITH ACUTE
+0xFE   0x00FE  #       LATIN SMALL LETTER THORN (Icelandic)
+0xFF   0x00FF  #       LATIN SMALL LETTER Y WITH DIAERESIS
diff --git a/program/lib/encoding/ISO-8859-2.map b/program/lib/encoding/ISO-8859-2.map
new file mode 100644 (file)
index 0000000..e45df25
--- /dev/null
@@ -0,0 +1,303 @@
+#
+#      Name:             ISO 8859-2:1999 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.0
+#      Table format:     Format A
+#      Date:             1999 July 27
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-2:1999 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-2 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-2 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA1   0x0104  #       LATIN CAPITAL LETTER A WITH OGONEK
+0xA2   0x02D8  #       BREVE
+0xA3   0x0141  #       LATIN CAPITAL LETTER L WITH STROKE
+0xA4   0x00A4  #       CURRENCY SIGN
+0xA5   0x013D  #       LATIN CAPITAL LETTER L WITH CARON
+0xA6   0x015A  #       LATIN CAPITAL LETTER S WITH ACUTE
+0xA7   0x00A7  #       SECTION SIGN
+0xA8   0x00A8  #       DIAERESIS
+0xA9   0x0160  #       LATIN CAPITAL LETTER S WITH CARON
+0xAA   0x015E  #       LATIN CAPITAL LETTER S WITH CEDILLA
+0xAB   0x0164  #       LATIN CAPITAL LETTER T WITH CARON
+0xAC   0x0179  #       LATIN CAPITAL LETTER Z WITH ACUTE
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAE   0x017D  #       LATIN CAPITAL LETTER Z WITH CARON
+0xAF   0x017B  #       LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xB0   0x00B0  #       DEGREE SIGN
+0xB1   0x0105  #       LATIN SMALL LETTER A WITH OGONEK
+0xB2   0x02DB  #       OGONEK
+0xB3   0x0142  #       LATIN SMALL LETTER L WITH STROKE
+0xB4   0x00B4  #       ACUTE ACCENT
+0xB5   0x013E  #       LATIN SMALL LETTER L WITH CARON
+0xB6   0x015B  #       LATIN SMALL LETTER S WITH ACUTE
+0xB7   0x02C7  #       CARON
+0xB8   0x00B8  #       CEDILLA
+0xB9   0x0161  #       LATIN SMALL LETTER S WITH CARON
+0xBA   0x015F  #       LATIN SMALL LETTER S WITH CEDILLA
+0xBB   0x0165  #       LATIN SMALL LETTER T WITH CARON
+0xBC   0x017A  #       LATIN SMALL LETTER Z WITH ACUTE
+0xBD   0x02DD  #       DOUBLE ACUTE ACCENT
+0xBE   0x017E  #       LATIN SMALL LETTER Z WITH CARON
+0xBF   0x017C  #       LATIN SMALL LETTER Z WITH DOT ABOVE
+0xC0   0x0154  #       LATIN CAPITAL LETTER R WITH ACUTE
+0xC1   0x00C1  #       LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #       LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x0102  #       LATIN CAPITAL LETTER A WITH BREVE
+0xC4   0x00C4  #       LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x0139  #       LATIN CAPITAL LETTER L WITH ACUTE
+0xC6   0x0106  #       LATIN CAPITAL LETTER C WITH ACUTE
+0xC7   0x00C7  #       LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x010C  #       LATIN CAPITAL LETTER C WITH CARON
+0xC9   0x00C9  #       LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x0118  #       LATIN CAPITAL LETTER E WITH OGONEK
+0xCB   0x00CB  #       LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x011A  #       LATIN CAPITAL LETTER E WITH CARON
+0xCD   0x00CD  #       LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #       LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x010E  #       LATIN CAPITAL LETTER D WITH CARON
+0xD0   0x0110  #       LATIN CAPITAL LETTER D WITH STROKE
+0xD1   0x0143  #       LATIN CAPITAL LETTER N WITH ACUTE
+0xD2   0x0147  #       LATIN CAPITAL LETTER N WITH CARON
+0xD3   0x00D3  #       LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #       LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x0150  #       LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0xD6   0x00D6  #       LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #       MULTIPLICATION SIGN
+0xD8   0x0158  #       LATIN CAPITAL LETTER R WITH CARON
+0xD9   0x016E  #       LATIN CAPITAL LETTER U WITH RING ABOVE
+0xDA   0x00DA  #       LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x0170  #       LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0xDC   0x00DC  #       LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x00DD  #       LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE   0x0162  #       LATIN CAPITAL LETTER T WITH CEDILLA
+0xDF   0x00DF  #       LATIN SMALL LETTER SHARP S
+0xE0   0x0155  #       LATIN SMALL LETTER R WITH ACUTE
+0xE1   0x00E1  #       LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #       LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x0103  #       LATIN SMALL LETTER A WITH BREVE
+0xE4   0x00E4  #       LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x013A  #       LATIN SMALL LETTER L WITH ACUTE
+0xE6   0x0107  #       LATIN SMALL LETTER C WITH ACUTE
+0xE7   0x00E7  #       LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x010D  #       LATIN SMALL LETTER C WITH CARON
+0xE9   0x00E9  #       LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x0119  #       LATIN SMALL LETTER E WITH OGONEK
+0xEB   0x00EB  #       LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x011B  #       LATIN SMALL LETTER E WITH CARON
+0xED   0x00ED  #       LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #       LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x010F  #       LATIN SMALL LETTER D WITH CARON
+0xF0   0x0111  #       LATIN SMALL LETTER D WITH STROKE
+0xF1   0x0144  #       LATIN SMALL LETTER N WITH ACUTE
+0xF2   0x0148  #       LATIN SMALL LETTER N WITH CARON
+0xF3   0x00F3  #       LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #       LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x0151  #       LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0xF6   0x00F6  #       LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #       DIVISION SIGN
+0xF8   0x0159  #       LATIN SMALL LETTER R WITH CARON
+0xF9   0x016F  #       LATIN SMALL LETTER U WITH RING ABOVE
+0xFA   0x00FA  #       LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x0171  #       LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0xFC   0x00FC  #       LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x00FD  #       LATIN SMALL LETTER Y WITH ACUTE
+0xFE   0x0163  #       LATIN SMALL LETTER T WITH CEDILLA
+0xFF   0x02D9  #       DOT ABOVE
diff --git a/program/lib/encoding/ISO-8859-3.map b/program/lib/encoding/ISO-8859-3.map
new file mode 100644 (file)
index 0000000..9b6ac69
--- /dev/null
@@ -0,0 +1,296 @@
+#
+#      Name:             ISO/IEC 8859-3:1999 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.0
+#      Table format:     Format A
+#      Date:             1999 July 27
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-3:1999 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-3 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-3 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA1   0x0126  #       LATIN CAPITAL LETTER H WITH STROKE
+0xA2   0x02D8  #       BREVE
+0xA3   0x00A3  #       POUND SIGN
+0xA4   0x00A4  #       CURRENCY SIGN
+0xA6   0x0124  #       LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0xA7   0x00A7  #       SECTION SIGN
+0xA8   0x00A8  #       DIAERESIS
+0xA9   0x0130  #       LATIN CAPITAL LETTER I WITH DOT ABOVE
+0xAA   0x015E  #       LATIN CAPITAL LETTER S WITH CEDILLA
+0xAB   0x011E  #       LATIN CAPITAL LETTER G WITH BREVE
+0xAC   0x0134  #       LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAF   0x017B  #       LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xB0   0x00B0  #       DEGREE SIGN
+0xB1   0x0127  #       LATIN SMALL LETTER H WITH STROKE
+0xB2   0x00B2  #       SUPERSCRIPT TWO
+0xB3   0x00B3  #       SUPERSCRIPT THREE
+0xB4   0x00B4  #       ACUTE ACCENT
+0xB5   0x00B5  #       MICRO SIGN
+0xB6   0x0125  #       LATIN SMALL LETTER H WITH CIRCUMFLEX
+0xB7   0x00B7  #       MIDDLE DOT
+0xB8   0x00B8  #       CEDILLA
+0xB9   0x0131  #       LATIN SMALL LETTER DOTLESS I
+0xBA   0x015F  #       LATIN SMALL LETTER S WITH CEDILLA
+0xBB   0x011F  #       LATIN SMALL LETTER G WITH BREVE
+0xBC   0x0135  #       LATIN SMALL LETTER J WITH CIRCUMFLEX
+0xBD   0x00BD  #       VULGAR FRACTION ONE HALF
+0xBF   0x017C  #       LATIN SMALL LETTER Z WITH DOT ABOVE
+0xC0   0x00C0  #       LATIN CAPITAL LETTER A WITH GRAVE
+0xC1   0x00C1  #       LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #       LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC4   0x00C4  #       LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x010A  #       LATIN CAPITAL LETTER C WITH DOT ABOVE
+0xC6   0x0108  #       LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+0xC7   0x00C7  #       LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x00C8  #       LATIN CAPITAL LETTER E WITH GRAVE
+0xC9   0x00C9  #       LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x00CA  #       LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB   0x00CB  #       LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x00CC  #       LATIN CAPITAL LETTER I WITH GRAVE
+0xCD   0x00CD  #       LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #       LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x00CF  #       LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD1   0x00D1  #       LATIN CAPITAL LETTER N WITH TILDE
+0xD2   0x00D2  #       LATIN CAPITAL LETTER O WITH GRAVE
+0xD3   0x00D3  #       LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #       LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x0120  #       LATIN CAPITAL LETTER G WITH DOT ABOVE
+0xD6   0x00D6  #       LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #       MULTIPLICATION SIGN
+0xD8   0x011C  #       LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+0xD9   0x00D9  #       LATIN CAPITAL LETTER U WITH GRAVE
+0xDA   0x00DA  #       LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x00DB  #       LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC   0x00DC  #       LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x016C  #       LATIN CAPITAL LETTER U WITH BREVE
+0xDE   0x015C  #       LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+0xDF   0x00DF  #       LATIN SMALL LETTER SHARP S
+0xE0   0x00E0  #       LATIN SMALL LETTER A WITH GRAVE
+0xE1   0x00E1  #       LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #       LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE4   0x00E4  #       LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x010B  #       LATIN SMALL LETTER C WITH DOT ABOVE
+0xE6   0x0109  #       LATIN SMALL LETTER C WITH CIRCUMFLEX
+0xE7   0x00E7  #       LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x00E8  #       LATIN SMALL LETTER E WITH GRAVE
+0xE9   0x00E9  #       LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x00EA  #       LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB   0x00EB  #       LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x00EC  #       LATIN SMALL LETTER I WITH GRAVE
+0xED   0x00ED  #       LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #       LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x00EF  #       LATIN SMALL LETTER I WITH DIAERESIS
+0xF1   0x00F1  #       LATIN SMALL LETTER N WITH TILDE
+0xF2   0x00F2  #       LATIN SMALL LETTER O WITH GRAVE
+0xF3   0x00F3  #       LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #       LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x0121  #       LATIN SMALL LETTER G WITH DOT ABOVE
+0xF6   0x00F6  #       LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #       DIVISION SIGN
+0xF8   0x011D  #       LATIN SMALL LETTER G WITH CIRCUMFLEX
+0xF9   0x00F9  #       LATIN SMALL LETTER U WITH GRAVE
+0xFA   0x00FA  #       LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x00FB  #       LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #       LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x016D  #       LATIN SMALL LETTER U WITH BREVE
+0xFE   0x015D  #       LATIN SMALL LETTER S WITH CIRCUMFLEX
+0xFF   0x02D9  #       DOT ABOVE
diff --git a/program/lib/encoding/ISO-8859-4.map b/program/lib/encoding/ISO-8859-4.map
new file mode 100644 (file)
index 0000000..662e698
--- /dev/null
@@ -0,0 +1,303 @@
+#
+#      Name:             ISO/IEC 8859-4:1998 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.0
+#      Table format:     Format A
+#      Date:             1999 July 27
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-4:1998 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-4 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-4 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA1   0x0104  #       LATIN CAPITAL LETTER A WITH OGONEK
+0xA2   0x0138  #       LATIN SMALL LETTER KRA
+0xA3   0x0156  #       LATIN CAPITAL LETTER R WITH CEDILLA
+0xA4   0x00A4  #       CURRENCY SIGN
+0xA5   0x0128  #       LATIN CAPITAL LETTER I WITH TILDE
+0xA6   0x013B  #       LATIN CAPITAL LETTER L WITH CEDILLA
+0xA7   0x00A7  #       SECTION SIGN
+0xA8   0x00A8  #       DIAERESIS
+0xA9   0x0160  #       LATIN CAPITAL LETTER S WITH CARON
+0xAA   0x0112  #       LATIN CAPITAL LETTER E WITH MACRON
+0xAB   0x0122  #       LATIN CAPITAL LETTER G WITH CEDILLA
+0xAC   0x0166  #       LATIN CAPITAL LETTER T WITH STROKE
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAE   0x017D  #       LATIN CAPITAL LETTER Z WITH CARON
+0xAF   0x00AF  #       MACRON
+0xB0   0x00B0  #       DEGREE SIGN
+0xB1   0x0105  #       LATIN SMALL LETTER A WITH OGONEK
+0xB2   0x02DB  #       OGONEK
+0xB3   0x0157  #       LATIN SMALL LETTER R WITH CEDILLA
+0xB4   0x00B4  #       ACUTE ACCENT
+0xB5   0x0129  #       LATIN SMALL LETTER I WITH TILDE
+0xB6   0x013C  #       LATIN SMALL LETTER L WITH CEDILLA
+0xB7   0x02C7  #       CARON
+0xB8   0x00B8  #       CEDILLA
+0xB9   0x0161  #       LATIN SMALL LETTER S WITH CARON
+0xBA   0x0113  #       LATIN SMALL LETTER E WITH MACRON
+0xBB   0x0123  #       LATIN SMALL LETTER G WITH CEDILLA
+0xBC   0x0167  #       LATIN SMALL LETTER T WITH STROKE
+0xBD   0x014A  #       LATIN CAPITAL LETTER ENG
+0xBE   0x017E  #       LATIN SMALL LETTER Z WITH CARON
+0xBF   0x014B  #       LATIN SMALL LETTER ENG
+0xC0   0x0100  #       LATIN CAPITAL LETTER A WITH MACRON
+0xC1   0x00C1  #       LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #       LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x00C3  #       LATIN CAPITAL LETTER A WITH TILDE
+0xC4   0x00C4  #       LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x00C5  #       LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6   0x00C6  #       LATIN CAPITAL LETTER AE
+0xC7   0x012E  #       LATIN CAPITAL LETTER I WITH OGONEK
+0xC8   0x010C  #       LATIN CAPITAL LETTER C WITH CARON
+0xC9   0x00C9  #       LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x0118  #       LATIN CAPITAL LETTER E WITH OGONEK
+0xCB   0x00CB  #       LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x0116  #       LATIN CAPITAL LETTER E WITH DOT ABOVE
+0xCD   0x00CD  #       LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #       LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x012A  #       LATIN CAPITAL LETTER I WITH MACRON
+0xD0   0x0110  #       LATIN CAPITAL LETTER D WITH STROKE
+0xD1   0x0145  #       LATIN CAPITAL LETTER N WITH CEDILLA
+0xD2   0x014C  #       LATIN CAPITAL LETTER O WITH MACRON
+0xD3   0x0136  #       LATIN CAPITAL LETTER K WITH CEDILLA
+0xD4   0x00D4  #       LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x00D5  #       LATIN CAPITAL LETTER O WITH TILDE
+0xD6   0x00D6  #       LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #       MULTIPLICATION SIGN
+0xD8   0x00D8  #       LATIN CAPITAL LETTER O WITH STROKE
+0xD9   0x0172  #       LATIN CAPITAL LETTER U WITH OGONEK
+0xDA   0x00DA  #       LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x00DB  #       LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC   0x00DC  #       LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x0168  #       LATIN CAPITAL LETTER U WITH TILDE
+0xDE   0x016A  #       LATIN CAPITAL LETTER U WITH MACRON
+0xDF   0x00DF  #       LATIN SMALL LETTER SHARP S
+0xE0   0x0101  #       LATIN SMALL LETTER A WITH MACRON
+0xE1   0x00E1  #       LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #       LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x00E3  #       LATIN SMALL LETTER A WITH TILDE
+0xE4   0x00E4  #       LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x00E5  #       LATIN SMALL LETTER A WITH RING ABOVE
+0xE6   0x00E6  #       LATIN SMALL LETTER AE
+0xE7   0x012F  #       LATIN SMALL LETTER I WITH OGONEK
+0xE8   0x010D  #       LATIN SMALL LETTER C WITH CARON
+0xE9   0x00E9  #       LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x0119  #       LATIN SMALL LETTER E WITH OGONEK
+0xEB   0x00EB  #       LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x0117  #       LATIN SMALL LETTER E WITH DOT ABOVE
+0xED   0x00ED  #       LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #       LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x012B  #       LATIN SMALL LETTER I WITH MACRON
+0xF0   0x0111  #       LATIN SMALL LETTER D WITH STROKE
+0xF1   0x0146  #       LATIN SMALL LETTER N WITH CEDILLA
+0xF2   0x014D  #       LATIN SMALL LETTER O WITH MACRON
+0xF3   0x0137  #       LATIN SMALL LETTER K WITH CEDILLA
+0xF4   0x00F4  #       LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x00F5  #       LATIN SMALL LETTER O WITH TILDE
+0xF6   0x00F6  #       LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #       DIVISION SIGN
+0xF8   0x00F8  #       LATIN SMALL LETTER O WITH STROKE
+0xF9   0x0173  #       LATIN SMALL LETTER U WITH OGONEK
+0xFA   0x00FA  #       LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x00FB  #       LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #       LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x0169  #       LATIN SMALL LETTER U WITH TILDE
+0xFE   0x016B  #       LATIN SMALL LETTER U WITH MACRON
+0xFF   0x02D9  #       DOT ABOVE
diff --git a/program/lib/encoding/ISO-8859-5.map b/program/lib/encoding/ISO-8859-5.map
new file mode 100644 (file)
index 0000000..a7ed1ce
--- /dev/null
@@ -0,0 +1,303 @@
+#
+#      Name:             ISO 8859-5:1999 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.0
+#      Table format:     Format A
+#      Date:             1999 July 27
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-5:1999 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-5 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-5 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA1   0x0401  #       CYRILLIC CAPITAL LETTER IO
+0xA2   0x0402  #       CYRILLIC CAPITAL LETTER DJE
+0xA3   0x0403  #       CYRILLIC CAPITAL LETTER GJE
+0xA4   0x0404  #       CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0xA5   0x0405  #       CYRILLIC CAPITAL LETTER DZE
+0xA6   0x0406  #       CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0xA7   0x0407  #       CYRILLIC CAPITAL LETTER YI
+0xA8   0x0408  #       CYRILLIC CAPITAL LETTER JE
+0xA9   0x0409  #       CYRILLIC CAPITAL LETTER LJE
+0xAA   0x040A  #       CYRILLIC CAPITAL LETTER NJE
+0xAB   0x040B  #       CYRILLIC CAPITAL LETTER TSHE
+0xAC   0x040C  #       CYRILLIC CAPITAL LETTER KJE
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAE   0x040E  #       CYRILLIC CAPITAL LETTER SHORT U
+0xAF   0x040F  #       CYRILLIC CAPITAL LETTER DZHE
+0xB0   0x0410  #       CYRILLIC CAPITAL LETTER A
+0xB1   0x0411  #       CYRILLIC CAPITAL LETTER BE
+0xB2   0x0412  #       CYRILLIC CAPITAL LETTER VE
+0xB3   0x0413  #       CYRILLIC CAPITAL LETTER GHE
+0xB4   0x0414  #       CYRILLIC CAPITAL LETTER DE
+0xB5   0x0415  #       CYRILLIC CAPITAL LETTER IE
+0xB6   0x0416  #       CYRILLIC CAPITAL LETTER ZHE
+0xB7   0x0417  #       CYRILLIC CAPITAL LETTER ZE
+0xB8   0x0418  #       CYRILLIC CAPITAL LETTER I
+0xB9   0x0419  #       CYRILLIC CAPITAL LETTER SHORT I
+0xBA   0x041A  #       CYRILLIC CAPITAL LETTER KA
+0xBB   0x041B  #       CYRILLIC CAPITAL LETTER EL
+0xBC   0x041C  #       CYRILLIC CAPITAL LETTER EM
+0xBD   0x041D  #       CYRILLIC CAPITAL LETTER EN
+0xBE   0x041E  #       CYRILLIC CAPITAL LETTER O
+0xBF   0x041F  #       CYRILLIC CAPITAL LETTER PE
+0xC0   0x0420  #       CYRILLIC CAPITAL LETTER ER
+0xC1   0x0421  #       CYRILLIC CAPITAL LETTER ES
+0xC2   0x0422  #       CYRILLIC CAPITAL LETTER TE
+0xC3   0x0423  #       CYRILLIC CAPITAL LETTER U
+0xC4   0x0424  #       CYRILLIC CAPITAL LETTER EF
+0xC5   0x0425  #       CYRILLIC CAPITAL LETTER HA
+0xC6   0x0426  #       CYRILLIC CAPITAL LETTER TSE
+0xC7   0x0427  #       CYRILLIC CAPITAL LETTER CHE
+0xC8   0x0428  #       CYRILLIC CAPITAL LETTER SHA
+0xC9   0x0429  #       CYRILLIC CAPITAL LETTER SHCHA
+0xCA   0x042A  #       CYRILLIC CAPITAL LETTER HARD SIGN
+0xCB   0x042B  #       CYRILLIC CAPITAL LETTER YERU
+0xCC   0x042C  #       CYRILLIC CAPITAL LETTER SOFT SIGN
+0xCD   0x042D  #       CYRILLIC CAPITAL LETTER E
+0xCE   0x042E  #       CYRILLIC CAPITAL LETTER YU
+0xCF   0x042F  #       CYRILLIC CAPITAL LETTER YA
+0xD0   0x0430  #       CYRILLIC SMALL LETTER A
+0xD1   0x0431  #       CYRILLIC SMALL LETTER BE
+0xD2   0x0432  #       CYRILLIC SMALL LETTER VE
+0xD3   0x0433  #       CYRILLIC SMALL LETTER GHE
+0xD4   0x0434  #       CYRILLIC SMALL LETTER DE
+0xD5   0x0435  #       CYRILLIC SMALL LETTER IE
+0xD6   0x0436  #       CYRILLIC SMALL LETTER ZHE
+0xD7   0x0437  #       CYRILLIC SMALL LETTER ZE
+0xD8   0x0438  #       CYRILLIC SMALL LETTER I
+0xD9   0x0439  #       CYRILLIC SMALL LETTER SHORT I
+0xDA   0x043A  #       CYRILLIC SMALL LETTER KA
+0xDB   0x043B  #       CYRILLIC SMALL LETTER EL
+0xDC   0x043C  #       CYRILLIC SMALL LETTER EM
+0xDD   0x043D  #       CYRILLIC SMALL LETTER EN
+0xDE   0x043E  #       CYRILLIC SMALL LETTER O
+0xDF   0x043F  #       CYRILLIC SMALL LETTER PE
+0xE0   0x0440  #       CYRILLIC SMALL LETTER ER
+0xE1   0x0441  #       CYRILLIC SMALL LETTER ES
+0xE2   0x0442  #       CYRILLIC SMALL LETTER TE
+0xE3   0x0443  #       CYRILLIC SMALL LETTER U
+0xE4   0x0444  #       CYRILLIC SMALL LETTER EF
+0xE5   0x0445  #       CYRILLIC SMALL LETTER HA
+0xE6   0x0446  #       CYRILLIC SMALL LETTER TSE
+0xE7   0x0447  #       CYRILLIC SMALL LETTER CHE
+0xE8   0x0448  #       CYRILLIC SMALL LETTER SHA
+0xE9   0x0449  #       CYRILLIC SMALL LETTER SHCHA
+0xEA   0x044A  #       CYRILLIC SMALL LETTER HARD SIGN
+0xEB   0x044B  #       CYRILLIC SMALL LETTER YERU
+0xEC   0x044C  #       CYRILLIC SMALL LETTER SOFT SIGN
+0xED   0x044D  #       CYRILLIC SMALL LETTER E
+0xEE   0x044E  #       CYRILLIC SMALL LETTER YU
+0xEF   0x044F  #       CYRILLIC SMALL LETTER YA
+0xF0   0x2116  #       NUMERO SIGN
+0xF1   0x0451  #       CYRILLIC SMALL LETTER IO
+0xF2   0x0452  #       CYRILLIC SMALL LETTER DJE
+0xF3   0x0453  #       CYRILLIC SMALL LETTER GJE
+0xF4   0x0454  #       CYRILLIC SMALL LETTER UKRAINIAN IE
+0xF5   0x0455  #       CYRILLIC SMALL LETTER DZE
+0xF6   0x0456  #       CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+0xF7   0x0457  #       CYRILLIC SMALL LETTER YI
+0xF8   0x0458  #       CYRILLIC SMALL LETTER JE
+0xF9   0x0459  #       CYRILLIC SMALL LETTER LJE
+0xFA   0x045A  #       CYRILLIC SMALL LETTER NJE
+0xFB   0x045B  #       CYRILLIC SMALL LETTER TSHE
+0xFC   0x045C  #       CYRILLIC SMALL LETTER KJE
+0xFD   0x00A7  #       SECTION SIGN
+0xFE   0x045E  #       CYRILLIC SMALL LETTER SHORT U
+0xFF   0x045F  #       CYRILLIC SMALL LETTER DZHE
diff --git a/program/lib/encoding/ISO-8859-6.map b/program/lib/encoding/ISO-8859-6.map
new file mode 100644 (file)
index 0000000..69ac7f5
--- /dev/null
@@ -0,0 +1,260 @@
+#
+#      Name:             ISO 8859-6:1999 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.0
+#      Table format:     Format A
+#      Date:             1999 July 27
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-6:1999 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-6 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-6 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#      0x30..0x39 remapped to the ASCII digits (U+0030..U+0039) instead
+#      of the Arabic digits (U+0660..U+0669).
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA4   0x00A4  #       CURRENCY SIGN
+0xAC   0x060C  #       ARABIC COMMA
+0xAD   0x00AD  #       SOFT HYPHEN
+0xBB   0x061B  #       ARABIC SEMICOLON
+0xBF   0x061F  #       ARABIC QUESTION MARK
+0xC1   0x0621  #       ARABIC LETTER HAMZA
+0xC2   0x0622  #       ARABIC LETTER ALEF WITH MADDA ABOVE
+0xC3   0x0623  #       ARABIC LETTER ALEF WITH HAMZA ABOVE
+0xC4   0x0624  #       ARABIC LETTER WAW WITH HAMZA ABOVE
+0xC5   0x0625  #       ARABIC LETTER ALEF WITH HAMZA BELOW
+0xC6   0x0626  #       ARABIC LETTER YEH WITH HAMZA ABOVE
+0xC7   0x0627  #       ARABIC LETTER ALEF
+0xC8   0x0628  #       ARABIC LETTER BEH
+0xC9   0x0629  #       ARABIC LETTER TEH MARBUTA
+0xCA   0x062A  #       ARABIC LETTER TEH
+0xCB   0x062B  #       ARABIC LETTER THEH
+0xCC   0x062C  #       ARABIC LETTER JEEM
+0xCD   0x062D  #       ARABIC LETTER HAH
+0xCE   0x062E  #       ARABIC LETTER KHAH
+0xCF   0x062F  #       ARABIC LETTER DAL
+0xD0   0x0630  #       ARABIC LETTER THAL
+0xD1   0x0631  #       ARABIC LETTER REH
+0xD2   0x0632  #       ARABIC LETTER ZAIN
+0xD3   0x0633  #       ARABIC LETTER SEEN
+0xD4   0x0634  #       ARABIC LETTER SHEEN
+0xD5   0x0635  #       ARABIC LETTER SAD
+0xD6   0x0636  #       ARABIC LETTER DAD
+0xD7   0x0637  #       ARABIC LETTER TAH
+0xD8   0x0638  #       ARABIC LETTER ZAH
+0xD9   0x0639  #       ARABIC LETTER AIN
+0xDA   0x063A  #       ARABIC LETTER GHAIN
+0xE0   0x0640  #       ARABIC TATWEEL
+0xE1   0x0641  #       ARABIC LETTER FEH
+0xE2   0x0642  #       ARABIC LETTER QAF
+0xE3   0x0643  #       ARABIC LETTER KAF
+0xE4   0x0644  #       ARABIC LETTER LAM
+0xE5   0x0645  #       ARABIC LETTER MEEM
+0xE6   0x0646  #       ARABIC LETTER NOON
+0xE7   0x0647  #       ARABIC LETTER HEH
+0xE8   0x0648  #       ARABIC LETTER WAW
+0xE9   0x0649  #       ARABIC LETTER ALEF MAKSURA
+0xEA   0x064A  #       ARABIC LETTER YEH
+0xEB   0x064B  #       ARABIC FATHATAN
+0xEC   0x064C  #       ARABIC DAMMATAN
+0xED   0x064D  #       ARABIC KASRATAN
+0xEE   0x064E  #       ARABIC FATHA
+0xEF   0x064F  #       ARABIC DAMMA
+0xF0   0x0650  #       ARABIC KASRA
+0xF1   0x0651  #       ARABIC SHADDA
+0xF2   0x0652  #       ARABIC SUKUN
diff --git a/program/lib/encoding/ISO-8859-7.map b/program/lib/encoding/ISO-8859-7.map
new file mode 100644 (file)
index 0000000..bc46b74
--- /dev/null
@@ -0,0 +1,308 @@
+#
+#      Name:             ISO 8859-7:2003 to Unicode
+#      Unicode version:  4.0
+#      Table version:    2.0
+#      Table format:     Format A
+#      Date:             2003-Nov-12
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-2003 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO 8859-7:2003 characters map into Unicode.
+#
+#      ISO 8859-7:1987 is equivalent to ISO-IR-126, ELOT 928,
+#      and ECMA 118. ISO 8859-7:2003 adds two currency signs 
+#      and one other character not in the earlier standard.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO 8859-7 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO 8859-7 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#      Remap 0xA1 to U+2018 (instead of 0x02BD) to match text of 8859-7
+#      Remap 0xA2 to U+2019 (instead of 0x02BC) to match text of 8859-7
+#
+#      2.0 version updates 1.0 version by adding mappings for the
+#      three newly added characters 0xA4, 0xA5, 0xAA.
+#
+#      Updated versions of this file may be found in:
+#              <http://www.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact the Unicode Consortium at:
+#              <http://www.unicode.org/reporting.html>
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA1   0x2018  #       LEFT SINGLE QUOTATION MARK
+0xA2   0x2019  #       RIGHT SINGLE QUOTATION MARK
+0xA3   0x00A3  #       POUND SIGN
+0xA4   0x20AC  #       EURO SIGN
+0xA5   0x20AF  #       DRACHMA SIGN
+0xA6   0x00A6  #       BROKEN BAR
+0xA7   0x00A7  #       SECTION SIGN
+0xA8   0x00A8  #       DIAERESIS
+0xA9   0x00A9  #       COPYRIGHT SIGN
+0xAA   0x037A  #       GREEK YPOGEGRAMMENI
+0xAB   0x00AB  #       LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #       NOT SIGN
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAF   0x2015  #       HORIZONTAL BAR
+0xB0   0x00B0  #       DEGREE SIGN
+0xB1   0x00B1  #       PLUS-MINUS SIGN
+0xB2   0x00B2  #       SUPERSCRIPT TWO
+0xB3   0x00B3  #       SUPERSCRIPT THREE
+0xB4   0x0384  #       GREEK TONOS
+0xB5   0x0385  #       GREEK DIALYTIKA TONOS
+0xB6   0x0386  #       GREEK CAPITAL LETTER ALPHA WITH TONOS
+0xB7   0x00B7  #       MIDDLE DOT
+0xB8   0x0388  #       GREEK CAPITAL LETTER EPSILON WITH TONOS
+0xB9   0x0389  #       GREEK CAPITAL LETTER ETA WITH TONOS
+0xBA   0x038A  #       GREEK CAPITAL LETTER IOTA WITH TONOS
+0xBB   0x00BB  #       RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x038C  #       GREEK CAPITAL LETTER OMICRON WITH TONOS
+0xBD   0x00BD  #       VULGAR FRACTION ONE HALF
+0xBE   0x038E  #       GREEK CAPITAL LETTER UPSILON WITH TONOS
+0xBF   0x038F  #       GREEK CAPITAL LETTER OMEGA WITH TONOS
+0xC0   0x0390  #       GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0xC1   0x0391  #       GREEK CAPITAL LETTER ALPHA
+0xC2   0x0392  #       GREEK CAPITAL LETTER BETA
+0xC3   0x0393  #       GREEK CAPITAL LETTER GAMMA
+0xC4   0x0394  #       GREEK CAPITAL LETTER DELTA
+0xC5   0x0395  #       GREEK CAPITAL LETTER EPSILON
+0xC6   0x0396  #       GREEK CAPITAL LETTER ZETA
+0xC7   0x0397  #       GREEK CAPITAL LETTER ETA
+0xC8   0x0398  #       GREEK CAPITAL LETTER THETA
+0xC9   0x0399  #       GREEK CAPITAL LETTER IOTA
+0xCA   0x039A  #       GREEK CAPITAL LETTER KAPPA
+0xCB   0x039B  #       GREEK CAPITAL LETTER LAMDA
+0xCC   0x039C  #       GREEK CAPITAL LETTER MU
+0xCD   0x039D  #       GREEK CAPITAL LETTER NU
+0xCE   0x039E  #       GREEK CAPITAL LETTER XI
+0xCF   0x039F  #       GREEK CAPITAL LETTER OMICRON
+0xD0   0x03A0  #       GREEK CAPITAL LETTER PI
+0xD1   0x03A1  #       GREEK CAPITAL LETTER RHO
+0xD3   0x03A3  #       GREEK CAPITAL LETTER SIGMA
+0xD4   0x03A4  #       GREEK CAPITAL LETTER TAU
+0xD5   0x03A5  #       GREEK CAPITAL LETTER UPSILON
+0xD6   0x03A6  #       GREEK CAPITAL LETTER PHI
+0xD7   0x03A7  #       GREEK CAPITAL LETTER CHI
+0xD8   0x03A8  #       GREEK CAPITAL LETTER PSI
+0xD9   0x03A9  #       GREEK CAPITAL LETTER OMEGA
+0xDA   0x03AA  #       GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+0xDB   0x03AB  #       GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+0xDC   0x03AC  #       GREEK SMALL LETTER ALPHA WITH TONOS
+0xDD   0x03AD  #       GREEK SMALL LETTER EPSILON WITH TONOS
+0xDE   0x03AE  #       GREEK SMALL LETTER ETA WITH TONOS
+0xDF   0x03AF  #       GREEK SMALL LETTER IOTA WITH TONOS
+0xE0   0x03B0  #       GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+0xE1   0x03B1  #       GREEK SMALL LETTER ALPHA
+0xE2   0x03B2  #       GREEK SMALL LETTER BETA
+0xE3   0x03B3  #       GREEK SMALL LETTER GAMMA
+0xE4   0x03B4  #       GREEK SMALL LETTER DELTA
+0xE5   0x03B5  #       GREEK SMALL LETTER EPSILON
+0xE6   0x03B6  #       GREEK SMALL LETTER ZETA
+0xE7   0x03B7  #       GREEK SMALL LETTER ETA
+0xE8   0x03B8  #       GREEK SMALL LETTER THETA
+0xE9   0x03B9  #       GREEK SMALL LETTER IOTA
+0xEA   0x03BA  #       GREEK SMALL LETTER KAPPA
+0xEB   0x03BB  #       GREEK SMALL LETTER LAMDA
+0xEC   0x03BC  #       GREEK SMALL LETTER MU
+0xED   0x03BD  #       GREEK SMALL LETTER NU
+0xEE   0x03BE  #       GREEK SMALL LETTER XI
+0xEF   0x03BF  #       GREEK SMALL LETTER OMICRON
+0xF0   0x03C0  #       GREEK SMALL LETTER PI
+0xF1   0x03C1  #       GREEK SMALL LETTER RHO
+0xF2   0x03C2  #       GREEK SMALL LETTER FINAL SIGMA
+0xF3   0x03C3  #       GREEK SMALL LETTER SIGMA
+0xF4   0x03C4  #       GREEK SMALL LETTER TAU
+0xF5   0x03C5  #       GREEK SMALL LETTER UPSILON
+0xF6   0x03C6  #       GREEK SMALL LETTER PHI
+0xF7   0x03C7  #       GREEK SMALL LETTER CHI
+0xF8   0x03C8  #       GREEK SMALL LETTER PSI
+0xF9   0x03C9  #       GREEK SMALL LETTER OMEGA
+0xFA   0x03CA  #       GREEK SMALL LETTER IOTA WITH DIALYTIKA
+0xFB   0x03CB  #       GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+0xFC   0x03CC  #       GREEK SMALL LETTER OMICRON WITH TONOS
+0xFD   0x03CD  #       GREEK SMALL LETTER UPSILON WITH TONOS
+0xFE   0x03CE  #       GREEK SMALL LETTER OMEGA WITH TONOS
diff --git a/program/lib/encoding/ISO-8859-8.map b/program/lib/encoding/ISO-8859-8.map
new file mode 100644 (file)
index 0000000..bc8da4c
--- /dev/null
@@ -0,0 +1,270 @@
+#
+#      Name:             ISO/IEC 8859-8:1999 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.1
+#      Table format:     Format A
+#      Date:             2000-Jan-03
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on optical media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-8:1999 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-8 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-8 order.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#       1.1 version updates to the published 8859-8:1999, correcting
+#          the mapping of 0xAF and adding mappings for LRM and RLM.
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA2   0x00A2  #       CENT SIGN
+0xA3   0x00A3  #       POUND SIGN
+0xA4   0x00A4  #       CURRENCY SIGN
+0xA5   0x00A5  #       YEN SIGN
+0xA6   0x00A6  #       BROKEN BAR
+0xA7   0x00A7  #       SECTION SIGN
+0xA8   0x00A8  #       DIAERESIS
+0xA9   0x00A9  #       COPYRIGHT SIGN
+0xAA   0x00D7  #       MULTIPLICATION SIGN
+0xAB   0x00AB  #       LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #       NOT SIGN
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAE   0x00AE  #       REGISTERED SIGN
+0xAF   0x00AF  #       MACRON
+0xB0   0x00B0  #       DEGREE SIGN
+0xB1   0x00B1  #       PLUS-MINUS SIGN
+0xB2   0x00B2  #       SUPERSCRIPT TWO
+0xB3   0x00B3  #       SUPERSCRIPT THREE
+0xB4   0x00B4  #       ACUTE ACCENT
+0xB5   0x00B5  #       MICRO SIGN
+0xB6   0x00B6  #       PILCROW SIGN
+0xB7   0x00B7  #       MIDDLE DOT
+0xB8   0x00B8  #       CEDILLA
+0xB9   0x00B9  #       SUPERSCRIPT ONE
+0xBA   0x00F7  #       DIVISION SIGN
+0xBB   0x00BB  #       RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #       VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #       VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #       VULGAR FRACTION THREE QUARTERS
+0xDF   0x2017  #       DOUBLE LOW LINE
+0xE0   0x05D0  #       HEBREW LETTER ALEF
+0xE1   0x05D1  #       HEBREW LETTER BET
+0xE2   0x05D2  #       HEBREW LETTER GIMEL
+0xE3   0x05D3  #       HEBREW LETTER DALET
+0xE4   0x05D4  #       HEBREW LETTER HE
+0xE5   0x05D5  #       HEBREW LETTER VAV
+0xE6   0x05D6  #       HEBREW LETTER ZAYIN
+0xE7   0x05D7  #       HEBREW LETTER HET
+0xE8   0x05D8  #       HEBREW LETTER TET
+0xE9   0x05D9  #       HEBREW LETTER YOD
+0xEA   0x05DA  #       HEBREW LETTER FINAL KAF
+0xEB   0x05DB  #       HEBREW LETTER KAF
+0xEC   0x05DC  #       HEBREW LETTER LAMED
+0xED   0x05DD  #       HEBREW LETTER FINAL MEM
+0xEE   0x05DE  #       HEBREW LETTER MEM
+0xEF   0x05DF  #       HEBREW LETTER FINAL NUN
+0xF0   0x05E0  #       HEBREW LETTER NUN
+0xF1   0x05E1  #       HEBREW LETTER SAMEKH
+0xF2   0x05E2  #       HEBREW LETTER AYIN
+0xF3   0x05E3  #       HEBREW LETTER FINAL PE
+0xF4   0x05E4  #       HEBREW LETTER PE
+0xF5   0x05E5  #       HEBREW LETTER FINAL TSADI
+0xF6   0x05E6  #       HEBREW LETTER TSADI
+0xF7   0x05E7  #       HEBREW LETTER QOF
+0xF8   0x05E8  #       HEBREW LETTER RESH
+0xF9   0x05E9  #       HEBREW LETTER SHIN
+0xFA   0x05EA  #       HEBREW LETTER TAV
+0xFD   0x200E  #       LEFT-TO-RIGHT MARK
+0xFE   0x200F  #       RIGHT-TO-LEFT MARK
+
diff --git a/program/lib/encoding/ISO-8859-9.map b/program/lib/encoding/ISO-8859-9.map
new file mode 100644 (file)
index 0000000..22901f1
--- /dev/null
@@ -0,0 +1,307 @@
+#
+#      Name:             ISO/IEC 8859-9:1999 to Unicode
+#      Unicode version:  3.0
+#      Table version:    1.0
+#      Table format:     Format A
+#      Date:             1999 July 27
+#      Authors:          Ken Whistler <kenw@sybase.com>
+#
+#      Copyright (c) 1991-1999 Unicode, Inc.  All Rights reserved.
+#
+#      This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+#      No claims are made as to fitness for any particular purpose.  No
+#      warranties of any kind are expressed or implied.  The recipient
+#      agrees to determine applicability of information provided.  If this
+#      file has been provided on magnetic media by Unicode, Inc., the sole
+#      remedy for any claim will be exchange of defective media within 90
+#      days of receipt.
+#
+#      Unicode, Inc. hereby grants the right to freely use the information
+#      supplied in this file in the creation of products supporting the
+#      Unicode Standard, and to make copies of this file in any form for
+#      internal or external distribution as long as this notice remains
+#      attached.
+#
+#      General notes:
+#
+#      This table contains the data the Unicode Consortium has on how
+#       ISO/IEC 8859-9:1999 characters map into Unicode.
+#
+#      Format:  Three tab-separated columns
+#               Column #1 is the ISO/IEC 8859-9 code (in hex as 0xXX)
+#               Column #2 is the Unicode (in hex as 0xXXXX)
+#               Column #3 the Unicode name (follows a comment sign, '#')
+#
+#      The entries are in ISO/IEC 8859-9 order.
+#
+#      ISO/IEC 8859-9 is also equivalent to ISO-IR-148.
+#
+#      Version history
+#      1.0 version updates 0.1 version by adding mappings for all
+#      control characters.
+#
+#      Updated versions of this file may be found in:
+#              <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+#      Any comments or problems, contact <errata@unicode.org>
+#      Please note that <errata@unicode.org> is an archival address;
+#      notices will be checked, but do not expect an immediate response.
+#
+0x00   0x0000  #       NULL
+0x01   0x0001  #       START OF HEADING
+0x02   0x0002  #       START OF TEXT
+0x03   0x0003  #       END OF TEXT
+0x04   0x0004  #       END OF TRANSMISSION
+0x05   0x0005  #       ENQUIRY
+0x06   0x0006  #       ACKNOWLEDGE
+0x07   0x0007  #       BELL
+0x08   0x0008  #       BACKSPACE
+0x09   0x0009  #       HORIZONTAL TABULATION
+0x0A   0x000A  #       LINE FEED
+0x0B   0x000B  #       VERTICAL TABULATION
+0x0C   0x000C  #       FORM FEED
+0x0D   0x000D  #       CARRIAGE RETURN
+0x0E   0x000E  #       SHIFT OUT
+0x0F   0x000F  #       SHIFT IN
+0x10   0x0010  #       DATA LINK ESCAPE
+0x11   0x0011  #       DEVICE CONTROL ONE
+0x12   0x0012  #       DEVICE CONTROL TWO
+0x13   0x0013  #       DEVICE CONTROL THREE
+0x14   0x0014  #       DEVICE CONTROL FOUR
+0x15   0x0015  #       NEGATIVE ACKNOWLEDGE
+0x16   0x0016  #       SYNCHRONOUS IDLE
+0x17   0x0017  #       END OF TRANSMISSION BLOCK
+0x18   0x0018  #       CANCEL
+0x19   0x0019  #       END OF MEDIUM
+0x1A   0x001A  #       SUBSTITUTE
+0x1B   0x001B  #       ESCAPE
+0x1C   0x001C  #       FILE SEPARATOR
+0x1D   0x001D  #       GROUP SEPARATOR
+0x1E   0x001E  #       RECORD SEPARATOR
+0x1F   0x001F  #       UNIT SEPARATOR
+0x20   0x0020  #       SPACE
+0x21   0x0021  #       EXCLAMATION MARK
+0x22   0x0022  #       QUOTATION MARK
+0x23   0x0023  #       NUMBER SIGN
+0x24   0x0024  #       DOLLAR SIGN
+0x25   0x0025  #       PERCENT SIGN
+0x26   0x0026  #       AMPERSAND
+0x27   0x0027  #       APOSTROPHE
+0x28   0x0028  #       LEFT PARENTHESIS
+0x29   0x0029  #       RIGHT PARENTHESIS
+0x2A   0x002A  #       ASTERISK
+0x2B   0x002B  #       PLUS SIGN
+0x2C   0x002C  #       COMMA
+0x2D   0x002D  #       HYPHEN-MINUS
+0x2E   0x002E  #       FULL STOP
+0x2F   0x002F  #       SOLIDUS
+0x30   0x0030  #       DIGIT ZERO
+0x31   0x0031  #       DIGIT ONE
+0x32   0x0032  #       DIGIT TWO
+0x33   0x0033  #       DIGIT THREE
+0x34   0x0034  #       DIGIT FOUR
+0x35   0x0035  #       DIGIT FIVE
+0x36   0x0036  #       DIGIT SIX
+0x37   0x0037  #       DIGIT SEVEN
+0x38   0x0038  #       DIGIT EIGHT
+0x39   0x0039  #       DIGIT NINE
+0x3A   0x003A  #       COLON
+0x3B   0x003B  #       SEMICOLON
+0x3C   0x003C  #       LESS-THAN SIGN
+0x3D   0x003D  #       EQUALS SIGN
+0x3E   0x003E  #       GREATER-THAN SIGN
+0x3F   0x003F  #       QUESTION MARK
+0x40   0x0040  #       COMMERCIAL AT
+0x41   0x0041  #       LATIN CAPITAL LETTER A
+0x42   0x0042  #       LATIN CAPITAL LETTER B
+0x43   0x0043  #       LATIN CAPITAL LETTER C
+0x44   0x0044  #       LATIN CAPITAL LETTER D
+0x45   0x0045  #       LATIN CAPITAL LETTER E
+0x46   0x0046  #       LATIN CAPITAL LETTER F
+0x47   0x0047  #       LATIN CAPITAL LETTER G
+0x48   0x0048  #       LATIN CAPITAL LETTER H
+0x49   0x0049  #       LATIN CAPITAL LETTER I
+0x4A   0x004A  #       LATIN CAPITAL LETTER J
+0x4B   0x004B  #       LATIN CAPITAL LETTER K
+0x4C   0x004C  #       LATIN CAPITAL LETTER L
+0x4D   0x004D  #       LATIN CAPITAL LETTER M
+0x4E   0x004E  #       LATIN CAPITAL LETTER N
+0x4F   0x004F  #       LATIN CAPITAL LETTER O
+0x50   0x0050  #       LATIN CAPITAL LETTER P
+0x51   0x0051  #       LATIN CAPITAL LETTER Q
+0x52   0x0052  #       LATIN CAPITAL LETTER R
+0x53   0x0053  #       LATIN CAPITAL LETTER S
+0x54   0x0054  #       LATIN CAPITAL LETTER T
+0x55   0x0055  #       LATIN CAPITAL LETTER U
+0x56   0x0056  #       LATIN CAPITAL LETTER V
+0x57   0x0057  #       LATIN CAPITAL LETTER W
+0x58   0x0058  #       LATIN CAPITAL LETTER X
+0x59   0x0059  #       LATIN CAPITAL LETTER Y
+0x5A   0x005A  #       LATIN CAPITAL LETTER Z
+0x5B   0x005B  #       LEFT SQUARE BRACKET
+0x5C   0x005C  #       REVERSE SOLIDUS
+0x5D   0x005D  #       RIGHT SQUARE BRACKET
+0x5E   0x005E  #       CIRCUMFLEX ACCENT
+0x5F   0x005F  #       LOW LINE
+0x60   0x0060  #       GRAVE ACCENT
+0x61   0x0061  #       LATIN SMALL LETTER A
+0x62   0x0062  #       LATIN SMALL LETTER B
+0x63   0x0063  #       LATIN SMALL LETTER C
+0x64   0x0064  #       LATIN SMALL LETTER D
+0x65   0x0065  #       LATIN SMALL LETTER E
+0x66   0x0066  #       LATIN SMALL LETTER F
+0x67   0x0067  #       LATIN SMALL LETTER G
+0x68   0x0068  #       LATIN SMALL LETTER H
+0x69   0x0069  #       LATIN SMALL LETTER I
+0x6A   0x006A  #       LATIN SMALL LETTER J
+0x6B   0x006B  #       LATIN SMALL LETTER K
+0x6C   0x006C  #       LATIN SMALL LETTER L
+0x6D   0x006D  #       LATIN SMALL LETTER M
+0x6E   0x006E  #       LATIN SMALL LETTER N
+0x6F   0x006F  #       LATIN SMALL LETTER O
+0x70   0x0070  #       LATIN SMALL LETTER P
+0x71   0x0071  #       LATIN SMALL LETTER Q
+0x72   0x0072  #       LATIN SMALL LETTER R
+0x73   0x0073  #       LATIN SMALL LETTER S
+0x74   0x0074  #       LATIN SMALL LETTER T
+0x75   0x0075  #       LATIN SMALL LETTER U
+0x76   0x0076  #       LATIN SMALL LETTER V
+0x77   0x0077  #       LATIN SMALL LETTER W
+0x78   0x0078  #       LATIN SMALL LETTER X
+0x79   0x0079  #       LATIN SMALL LETTER Y
+0x7A   0x007A  #       LATIN SMALL LETTER Z
+0x7B   0x007B  #       LEFT CURLY BRACKET
+0x7C   0x007C  #       VERTICAL LINE
+0x7D   0x007D  #       RIGHT CURLY BRACKET
+0x7E   0x007E  #       TILDE
+0x7F   0x007F  #       DELETE
+0x80   0x0080  #       <control>
+0x81   0x0081  #       <control>
+0x82   0x0082  #       <control>
+0x83   0x0083  #       <control>
+0x84   0x0084  #       <control>
+0x85   0x0085  #       <control>
+0x86   0x0086  #       <control>
+0x87   0x0087  #       <control>
+0x88   0x0088  #       <control>
+0x89   0x0089  #       <control>
+0x8A   0x008A  #       <control>
+0x8B   0x008B  #       <control>
+0x8C   0x008C  #       <control>
+0x8D   0x008D  #       <control>
+0x8E   0x008E  #       <control>
+0x8F   0x008F  #       <control>
+0x90   0x0090  #       <control>
+0x91   0x0091  #       <control>
+0x92   0x0092  #       <control>
+0x93   0x0093  #       <control>
+0x94   0x0094  #       <control>
+0x95   0x0095  #       <control>
+0x96   0x0096  #       <control>
+0x97   0x0097  #       <control>
+0x98   0x0098  #       <control>
+0x99   0x0099  #       <control>
+0x9A   0x009A  #       <control>
+0x9B   0x009B  #       <control>
+0x9C   0x009C  #       <control>
+0x9D   0x009D  #       <control>
+0x9E   0x009E  #       <control>
+0x9F   0x009F  #       <control>
+0xA0   0x00A0  #       NO-BREAK SPACE
+0xA1   0x00A1  #       INVERTED EXCLAMATION MARK
+0xA2   0x00A2  #       CENT SIGN
+0xA3   0x00A3  #       POUND SIGN
+0xA4   0x00A4  #       CURRENCY SIGN
+0xA5   0x00A5  #       YEN SIGN
+0xA6   0x00A6  #       BROKEN BAR
+0xA7   0x00A7  #       SECTION SIGN
+0xA8   0x00A8  #       DIAERESIS
+0xA9   0x00A9  #       COPYRIGHT SIGN
+0xAA   0x00AA  #       FEMININE ORDINAL INDICATOR
+0xAB   0x00AB  #       LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC   0x00AC  #       NOT SIGN
+0xAD   0x00AD  #       SOFT HYPHEN
+0xAE   0x00AE  #       REGISTERED SIGN
+0xAF   0x00AF  #       MACRON
+0xB0   0x00B0  #       DEGREE SIGN
+0xB1   0x00B1  #       PLUS-MINUS SIGN
+0xB2   0x00B2  #       SUPERSCRIPT TWO
+0xB3   0x00B3  #       SUPERSCRIPT THREE
+0xB4   0x00B4  #       ACUTE ACCENT
+0xB5   0x00B5  #       MICRO SIGN
+0xB6   0x00B6  #       PILCROW SIGN
+0xB7   0x00B7  #       MIDDLE DOT
+0xB8   0x00B8  #       CEDILLA
+0xB9   0x00B9  #       SUPERSCRIPT ONE
+0xBA   0x00BA  #       MASCULINE ORDINAL INDICATOR
+0xBB   0x00BB  #       RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC   0x00BC  #       VULGAR FRACTION ONE QUARTER
+0xBD   0x00BD  #       VULGAR FRACTION ONE HALF
+0xBE   0x00BE  #       VULGAR FRACTION THREE QUARTERS
+0xBF   0x00BF  #       INVERTED QUESTION MARK
+0xC0   0x00C0  #       LATIN CAPITAL LETTER A WITH GRAVE
+0xC1   0x00C1  #       LATIN CAPITAL LETTER A WITH ACUTE
+0xC2   0x00C2  #       LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3   0x00C3  #       LATIN CAPITAL LETTER A WITH TILDE
+0xC4   0x00C4  #       LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5   0x00C5  #       LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6   0x00C6  #       LATIN CAPITAL LETTER AE
+0xC7   0x00C7  #       LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8   0x00C8  #       LATIN CAPITAL LETTER E WITH GRAVE
+0xC9   0x00C9  #       LATIN CAPITAL LETTER E WITH ACUTE
+0xCA   0x00CA  #       LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB   0x00CB  #       LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC   0x00CC  #       LATIN CAPITAL LETTER I WITH GRAVE
+0xCD   0x00CD  #       LATIN CAPITAL LETTER I WITH ACUTE
+0xCE   0x00CE  #       LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF   0x00CF  #       LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0   0x011E  #       LATIN CAPITAL LETTER G WITH BREVE
+0xD1   0x00D1  #       LATIN CAPITAL LETTER N WITH TILDE
+0xD2   0x00D2  #       LATIN CAPITAL LETTER O WITH GRAVE
+0xD3   0x00D3  #       LATIN CAPITAL LETTER O WITH ACUTE
+0xD4   0x00D4  #       LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5   0x00D5  #       LATIN CAPITAL LETTER O WITH TILDE
+0xD6   0x00D6  #       LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7   0x00D7  #       MULTIPLICATION SIGN
+0xD8   0x00D8  #       LATIN CAPITAL LETTER O WITH STROKE
+0xD9   0x00D9  #       LATIN CAPITAL LETTER U WITH GRAVE
+0xDA   0x00DA  #       LATIN CAPITAL LETTER U WITH ACUTE
+0xDB   0x00DB  #       LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC   0x00DC  #       LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD   0x0130  #       LATIN CAPITAL LETTER I WITH DOT ABOVE
+0xDE   0x015E  #       LATIN CAPITAL LETTER S WITH CEDILLA
+0xDF   0x00DF  #       LATIN SMALL LETTER SHARP S
+0xE0   0x00E0  #       LATIN SMALL LETTER A WITH GRAVE
+0xE1   0x00E1  #       LATIN SMALL LETTER A WITH ACUTE
+0xE2   0x00E2  #       LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3   0x00E3  #       LATIN SMALL LETTER A WITH TILDE
+0xE4   0x00E4  #       LATIN SMALL LETTER A WITH DIAERESIS
+0xE5   0x00E5  #       LATIN SMALL LETTER A WITH RING ABOVE
+0xE6   0x00E6  #       LATIN SMALL LETTER AE
+0xE7   0x00E7  #       LATIN SMALL LETTER C WITH CEDILLA
+0xE8   0x00E8  #       LATIN SMALL LETTER E WITH GRAVE
+0xE9   0x00E9  #       LATIN SMALL LETTER E WITH ACUTE
+0xEA   0x00EA  #       LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB   0x00EB  #       LATIN SMALL LETTER E WITH DIAERESIS
+0xEC   0x00EC  #       LATIN SMALL LETTER I WITH GRAVE
+0xED   0x00ED  #       LATIN SMALL LETTER I WITH ACUTE
+0xEE   0x00EE  #       LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF   0x00EF  #       LATIN SMALL LETTER I WITH DIAERESIS
+0xF0   0x011F  #       LATIN SMALL LETTER G WITH BREVE
+0xF1   0x00F1  #       LATIN SMALL LETTER N WITH TILDE
+0xF2   0x00F2  #       LATIN SMALL LETTER O WITH GRAVE
+0xF3   0x00F3  #       LATIN SMALL LETTER O WITH ACUTE
+0xF4   0x00F4  #       LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5   0x00F5  #       LATIN SMALL LETTER O WITH TILDE
+0xF6   0x00F6  #       LATIN SMALL LETTER O WITH DIAERESIS
+0xF7   0x00F7  #       DIVISION SIGN
+0xF8   0x00F8  #       LATIN SMALL LETTER O WITH STROKE
+0xF9   0x00F9  #       LATIN SMALL LETTER U WITH GRAVE
+0xFA   0x00FA  #       LATIN SMALL LETTER U WITH ACUTE
+0xFB   0x00FB  #       LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC   0x00FC  #       LATIN SMALL LETTER U WITH DIAERESIS
+0xFD   0x0131  #       LATIN SMALL LETTER DOTLESS I
+0xFE   0x015F  #       LATIN SMALL LETTER S WITH CEDILLA
+0xFF   0x00FF  #       LATIN SMALL LETTER Y WITH DIAERESIS
+
+
diff --git a/program/lib/enriched.inc b/program/lib/enriched.inc
new file mode 100644 (file)
index 0000000..2435a82
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/*
+       File:           read_enriched.inc
+       Author:         Ryo Chijiiwa
+       License:        GPL (part of IlohaMail)
+       Purpose:        functions for handling text/enriched messages
+       Reference:      RFC 1523, 1896
+*/
+
+
+function enriched_convert_newlines($str){
+       //remove single newlines, convert N newlines to N-1
+       
+       $str = str_replace("\r\n","\n",$str);
+       $len = strlen($str);
+       
+       $nl = 0;
+       $out = '';
+       for($i=0;$i<$len;$i++){
+               $c = $str[$i];
+               if (ord($c)==10) $nl++;
+               if ($nl && ord($c)!=10) $nl = 0;
+               if ($nl!=1) $out.=$c;
+               else $out.=' ';         
+       }
+       return $out;
+}
+
+function enriched_convert_formatting($body){
+       $a=array('<bold>'=>'<b>','</bold>'=>'</b>','<italic>'=>'<i>',
+                       '</italic>'=>'</i>','<fixed>'=>'<tt>','</fixed>'=>'</tt>',
+                       '<smaller>'=>'<font size=-1>','</smaller>'=>'</font>',
+                       '<bigger>'=>'<font size=+1>','</bigger>'=>'</font>',
+                       '<underline>'=>'<span style="text-decoration: underline">',
+                       '</underline>'=>'</span>',
+                       '<flushleft>'=>'<span style="text-align:left">',
+                       '</flushleft>'=>'</span>',
+                       '<flushright>'=>'<span style="text-align:right">',
+                       '</flushright>'=>'</span>',
+                       '<flushboth>'=>'<span style="text-align:justified">',
+                       '</flushboth>'=>'</span>',
+                       '<indent>'=>'<span style="padding-left: 20px">',
+                       '</indent>'=>'</span>',
+                       '<indentright>'=>'<span style="padding-right: 20px">',
+                       '</indentright>'=>'</span>');
+       
+       while(list($find,$replace)=each($a)){
+               $body = eregi_replace($find,$replace,$body);
+       }
+       return $body;
+}
+
+function enriched_font($body){
+       $pattern = '/(.*)\<fontfamily\>\<param\>(.*)\<\/param\>(.*)\<\/fontfamily\>(.*)/ims';
+       while(preg_match($pattern,$body,$a)){
+               //print_r($a);
+               if (count($a)!=5) continue;
+               $body=$a[1].'<span style="font-family: '.$a[2].'">'.$a[3].'</span>'.$a[4];
+       }
+
+       return $body;
+}
+
+
+function enriched_color($body){
+       $pattern = '/(.*)\<color\>\<param\>(.*)\<\/param\>(.*)\<\/color\>(.*)/ims';
+       while(preg_match($pattern,$body,$a)){
+               //print_r($a);
+               if (count($a)!=5) continue;
+
+               //extract color (either by name, or ####,####,####)
+               if (strpos($a[2],',')){
+                       $rgb = explode(',',$a[2]);
+                       $color ='#';
+                       for($i=0;$i<3;$i++) $color.=substr($rgb[$i],0,2); //just take first 2 bytes
+               }else{
+                       $color = $a[2];
+               }
+               
+               //put it all together
+               $body = $a[1].'<span style="color: '.$color.'">'.$a[3].'</span>'.$a[4];
+       }
+
+       return $body;
+}
+
+function enriched_excerpt($body){
+
+       $pattern = '/(.*)\<excerpt\>(.*)\<\/excerpt\>(.*)/i';
+       while(preg_match($pattern,$body,$a)){
+               //print_r($a);
+               if (count($a)!=4) continue;
+               $quoted = '';
+               $lines = explode('<br>',$a[2]);
+               foreach($lines as $n=>$line) $quoted.='&gt;'.$line.'<br>';
+               $body=$a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
+       }
+
+       return $body;
+}
+
+function enriched_to_html($body){
+       $body = str_replace('<<','&lt;',$body);
+       $body = enriched_convert_newlines($body);
+       $body = str_replace("\n", '<br>', $body);
+       $body = enriched_convert_formatting($body);
+       $body = enriched_color($body);
+       $body = enriched_font($body);
+       $body = enriched_excerpt($body);
+       //$body = nl2br($body);
+       return $body;
+}
+
+?>
\ No newline at end of file
diff --git a/program/lib/html2text.inc b/program/lib/html2text.inc
new file mode 100644 (file)
index 0000000..82a254e
--- /dev/null
@@ -0,0 +1,440 @@
+<?php
+
+/*************************************************************************
+*                                                                       *
+* class.html2text.inc                                                   *
+*                                                                       *
+*************************************************************************
+*                                                                       *
+* Converts HTML to formatted plain text                                 *
+*                                                                       *
+* Copyright (c) 2005 Jon Abernathy <jon@chuggnutt.com>                  *
+* All rights reserved.                                                  *
+*                                                                       *
+* This script 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.                                   *
+*                                                                       *
+* The GNU General Public License can be found at                        *
+* http://www.gnu.org/copyleft/gpl.html.                                 *
+*                                                                       *
+* This script 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.                          *
+*                                                                       *
+* Author(s): Jon Abernathy <jon@chuggnutt.com>                          *
+*                                                                       *
+* Last modified: 04/06/05                                               *
+* Modified: 2004/05/19 (tbr)                                            *
+*                                                                       *
+*************************************************************************/
+
+
+/**
+*  Takes HTML and converts it to formatted, plain text.
+*
+*  Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
+*  correcting an error in the regexp search array. Fixed 7/30/03.
+*
+*  Updated set_html() function's file reading mechanism, 9/25/03.
+*
+*  Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
+*  several more HTML entity codes to the $search and $replace arrays.
+*  Updated 11/7/03.
+*
+*  Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
+*  suggesting the addition of $allowed_tags and its supporting function
+*  (which I slightly modified). Updated 3/12/04.
+*
+*  Thanks to Justin Dearing for pointing out that a replacement for the
+*  <TH> tag was missing, and suggesting an appropriate fix.
+*  Updated 8/25/04.
+*
+*  Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
+*  display/formatting bug in the _build_link_list() function: email
+*  readers would show the left bracket and number ("[1") as part of the
+*  rendered email address.
+*  Updated 12/16/04.
+*
+*  Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
+*  to handle relative links, which I hadn't considered. I modified his
+*  code a bit to handle normal HTTP links and MAILTO links. Also for
+*  suggesting three additional HTML entity codes to search for.
+*  Updated 03/02/05.
+*
+*  Thanks to Jacob Chandler for pointing out another link condition
+*  for the _build_link_list() function: "https".
+*  Updated 04/06/05.
+*
+*  @author Jon Abernathy <jon@chuggnutt.com>
+*  @version 0.6.1
+*  @since PHP 4.0.2
+*/
+class html2text
+{
+
+    /**
+     *  Contains the HTML content to convert.
+     *
+     *  @var string $html
+     *  @access public
+     */
+    var $html;
+
+    /**
+     *  Contains the converted, formatted text.
+     *
+     *  @var string $text
+     *  @access public
+     */
+    var $text;
+
+    /**
+     *  Maximum width of the formatted text, in columns.
+     *
+     *  @var integer $width
+     *  @access public
+     */
+    var $width = 70;
+
+    /**
+     *  List of preg* regular expression patterns to search for,
+     *  used in conjunction with $replace.
+     *
+     *  @var array $search
+     *  @access public
+     *  @see $replace
+     */
+    var $search = array(
+        "/\r/",                                  // Non-legal carriage return
+        "/[\n\t]+/",                             // Newlines and tabs
+        '/<script[^>]*>.*?<\/script>/i',         // <script>s -- which strip_tags supposedly has problems with
+        //'/<!-- .* -->/',                         // Comments -- which strip_tags might have problem a with
+        '/<a href="([^"]+)"[^>]*>(.+?)<\/a>/ie', // <a href="">
+        '/<h[123][^>]*>(.+?)<\/h[123]>/ie',      // H1 - H3
+        '/<h[456][^>]*>(.+?)<\/h[456]>/ie',      // H4 - H6
+        '/<p[^>]*>/i',                           // <P>
+        '/<br[^>]*>/i',                          // <br>
+        '/<b[^>]*>(.+?)<\/b>/ie',                // <b>
+        '/<i[^>]*>(.+?)<\/i>/i',                 // <i>
+        '/(<ul[^>]*>|<\/ul>)/i',                 // <ul> and </ul>
+        '/(<ol[^>]*>|<\/ol>)/i',                 // <ol> and </ol>
+        '/<li[^>]*>/i',                          // <li>
+        '/<hr[^>]*>/i',                          // <hr>
+        '/(<table[^>]*>|<\/table>)/i',           // <table> and </table>
+        '/(<tr[^>]*>|<\/tr>)/i',                 // <tr> and </tr>
+        '/<td[^>]*>(.+?)<\/td>/i',               // <td> and </td>
+        '/<th[^>]*>(.+?)<\/th>/i',               // <th> and </th>
+        '/&nbsp;/i',
+        '/&quot;/i',
+        '/&gt;/i',
+        '/&lt;/i',
+        '/&amp;/i',
+        '/&copy;/i',
+        '/&trade;/i',
+        '/&#8220;/',
+        '/&#8221;/',
+        '/&#8211;/',
+        '/&#8217;/',
+        '/&#38;/',
+        '/&#169;/',
+        '/&#8482;/',
+        '/&#151;/',
+        '/&#147;/',
+        '/&#148;/',
+        '/&#149;/',
+        '/&reg;/i',
+        '/&bull;/i',
+        '/&[&;]+;/i'
+    );
+
+    /**
+     *  List of pattern replacements corresponding to patterns searched.
+     *
+     *  @var array $replace
+     *  @access public
+     *  @see $search
+     */
+    var $replace = array(
+        '',                                     // Non-legal carriage return
+        ' ',                                    // Newlines and tabs
+        '',                                     // <script>s -- which strip_tags supposedly has problems with
+        //'',                                     // Comments -- which strip_tags might have problem a with
+        '$this->_build_link_list("\\1", "\\2")', // <a href="">
+        "strtoupper(\"\n\n\\1\n\n\")",          // H1 - H3
+        "ucwords(\"\n\n\\1\n\n\")",             // H4 - H6
+        "\n\n\t",                               // <P>
+        "\n",                                   // <br>
+        'strtoupper("\\1")',                    // <b>
+        '_\\1_',                                // <i>
+        "\n\n",                                 // <ul> and </ul>
+        "\n\n",                                 // <ol> and </ol>
+        "\t*",                                  // <li>
+        "\n-------------------------\n",        // <hr>
+        "\n\n",                                 // <table> and </table>
+        "\n",                                   // <tr> and </tr>
+        "\t\t\\1\n",                            // <td> and </td>
+        "strtoupper(\"\t\t\\1\n\")",            // <th> and </th>
+        ' ',
+        '"',
+        '>',
+        '<',
+        '&',
+        '(c)',
+        '(tm)',
+        '"',
+        '"',
+        '-',
+        "'",
+        '&',
+        '(c)',
+        '(tm)',
+        '--',
+        '"',
+        '"',
+        '*',
+        '(R)',
+        '*',
+        ''
+    );
+
+    /**
+     *  Contains a list of HTML tags to allow in the resulting text.
+     *
+     *  @var string $allowed_tags
+     *  @access public
+     *  @see set_allowed_tags()
+     */
+    var $allowed_tags = '';
+
+    /**
+     *  Contains the base URL that relative links should resolve to.
+     *
+     *  @var string $url
+     *  @access public
+     */
+    var $url;
+
+    /**
+     *  Indicates whether content in the $html variable has been converted yet.
+     *
+     *  @var boolean $converted
+     *  @access private
+     *  @see $html, $text
+     */
+    var $_converted = false;
+
+    /**
+     *  Contains URL addresses from links to be rendered in plain text.
+     *
+     *  @var string $link_list
+     *  @access private
+     *  @see _build_link_list()
+     */
+    var $_link_list = array();
+
+    /**
+     *  Constructor.
+     *
+     *  If the HTML source string (or file) is supplied, the class
+     *  will instantiate with that source propagated, all that has
+     *  to be done it to call get_text().
+     *
+     *  @param string $source HTML content
+     *  @param boolean $from_file Indicates $source is a file to pull content from
+     *  @access public
+     *  @return void
+     */
+    function html2text( $source = '', $from_file = false )
+    {
+        if ( !empty($source) ) {
+            $this->set_html($source, $from_file);
+        }
+        $this->set_base_url();
+    }
+
+    /**
+     *  Loads source HTML into memory, either from $source string or a file.
+     *
+     *  @param string $source HTML content
+     *  @param boolean $from_file Indicates $source is a file to pull content from
+     *  @access public
+     *  @return void
+     */
+    function set_html( $source, $from_file = false )
+    {
+        $this->html = $source;
+
+        if ( $from_file && file_exists($source) ) {
+            $fp = fopen($source, 'r');
+            $this->html = fread($fp, filesize($source));
+            fclose($fp);
+        }
+
+        $this->_converted = false;
+    }
+
+    /**
+     *  Returns the text, converted from HTML.
+     *
+     *  @access public
+     *  @return string
+     */
+    function get_text()
+    {
+        if ( !$this->_converted ) {
+            $this->_convert();
+        }
+
+        return $this->text;
+    }
+
+    /**
+     *  Prints the text, converted from HTML.
+     *
+     *  @access public
+     *  @return void
+     */
+    function print_text()
+    {
+        print $this->get_text();
+    }
+
+    /**
+     *  Alias to print_text(), operates identically.
+     *
+     *  @access public
+     *  @return void
+     *  @see print_text()
+     */
+    function p()
+    {
+        print $this->get_text();
+    }
+
+    /**
+     *  Sets the allowed HTML tags to pass through to the resulting text.
+     *
+     *  Tags should be in the form "<p>", with no corresponding closing tag.
+     *
+     *  @access public
+     *  @return void
+     */
+    function set_allowed_tags( $allowed_tags = '' )
+    {
+        if ( !empty($allowed_tags) ) {
+            $this->allowed_tags = $allowed_tags;
+        }
+    }
+
+    /**
+     *  Sets a base URL to handle relative links.
+     *
+     *  @access public
+     *  @return void
+     */
+    function set_base_url( $url = '' )
+    {
+        if ( empty($url) ) {
+            $this->url = 'http://' . $_SERVER['HTTP_HOST'];
+        } else {
+            // Strip any trailing slashes for consistency (relative
+            // URLs may already start with a slash like "/file.html")
+            if ( substr($url, -1) == '/' ) {
+                $url = substr($url, 0, -1);
+            }
+            $this->url = $url;
+        }
+    }
+
+    /**
+     *  Workhorse function that does actual conversion.
+     *
+     *  First performs custom tag replacement specified by $search and
+     *  $replace arrays. Then strips any remaining HTML tags, reduces whitespace
+     *  and newlines to a readable format, and word wraps the text to
+     *  $width characters.
+     *
+     *  @access private
+     *  @return void
+     */
+    function _convert()
+    {
+        // Variables used for building the link list
+        //$link_count = 1;
+        //$this->_link_list = '';
+
+        $text = trim(stripslashes($this->html));
+
+        // Run our defined search-and-replace
+        $text = preg_replace($this->search, $this->replace, $text);
+
+        // Strip any other HTML tags
+        $text = strip_tags($text, $this->allowed_tags);
+
+        // Bring down number of empty lines to 2 max
+        $text = preg_replace("/\n\s+\n/", "\n", $text);
+        $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
+
+        // Add link list
+        if ( sizeof($this->_link_list) ) {
+            $text .= "\n\nLinks:\n------\n";
+            foreach ($this->_link_list as $id => $link) {
+                $text .= '[' . ($id+1) . '] ' . $link . "\n";
+            }
+        }
+
+        // Wrap the text to a readable format
+        // for PHP versions >= 4.0.2. Default width is 75
+        $text = wordwrap($text, $this->width);
+
+        $this->text = $text;
+
+        $this->_converted = true;
+    }
+
+    /**
+     *  Helper function called by preg_replace() on link replacement.
+     *
+     *  Maintains an internal list of links to be displayed at the end of the
+     *  text, with numeric indices to the original point in the text they
+     *  appeared. Also makes an effort at identifying and handling absolute
+     *  and relative links.
+     *
+     *  @param integer $link_count Counter tracking current link number
+     *  @param string $link URL of the link
+     *  @param string $display Part of the text to associate number with
+     *  @access private
+     *  @return string
+    */
+    function _build_link_list($link, $display)
+      {
+      $link_lc = strtolower($link);
+      
+      if (substr($link_lc, 0, 7) == 'http://' || substr($link_lc, 0, 8) == 'https://' || substr($link_lc, 0, 7) == 'mailto:')
+        {
+        $url = $link;
+        }
+      else
+        {
+        $url = $this->url;
+        if ($link{0} != '/') {
+             $url .= '/';
+            }
+        $url .= $link;
+        }
+
+      $index = array_search($url, $this->_link_list);
+      if ($index===FALSE)
+        {
+        $index = sizeof($this->_link_list);
+        $this->_link_list[$index] = $url;
+        }
+              
+      return $display . ' [' . ($index+1) . ']';
+      }
+}
+
+?>
\ No newline at end of file
diff --git a/program/lib/icl_commons.inc b/program/lib/icl_commons.inc
new file mode 100644 (file)
index 0000000..5992051
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+function mod_b64_decode($data){
+       return base64_decode(str_replace(",","/",$data));
+}
+
+function mod_b64_encode($data){
+       return str_replace("/",",",str_replace("=","",base64_encode($data)));
+}
+
+
+function utf8_to_html($str){
+       $len = strlen($str);
+       $out = "";
+       for($i=0;$i<$len;$i+=2){
+               $val = ord($str[$i]);
+               $next_val = ord($str[$i+1]);
+               if ($val<255){
+                       $out.="&#".($val*256+$next_val).";";
+               }else{
+                       $out.=$str[$i].$str[$i+1];
+               }
+       }
+       return $out;
+}
+
+function iil_utf7_decode($str, $raw=false){
+       if (strpos($str, '&')===false) return $str;
+       
+       $len = strlen($str);
+       $in_b64 = false;
+       $b64_data = "";
+       $out = "";
+       for ($i=0;$i<$len;$i++){
+               $char = $str[$i];
+               if ($char=='&') $in_b64 = true;
+               else if ($in_b64 && $char=='-'){
+                       $in_b64 = false;
+                       if ($b64_data=="") $out.="&";
+                       else{
+                               $dec=mod_b64_decode($b64_data);
+                               $out.=($raw?$dec:utf8_to_html($dec));
+                               $b64_data = "";
+                       }
+               }else if ($in_b64) $b64_data.=$char;
+               else $out.=$char;
+       }
+       return $out;
+}
+
+function iil_utf7_encode($str){
+       if (!ereg("[\200-\237]",$str) && !ereg("[\241-\377]",$str))
+        return $str;
+
+       $len = strlen($str);
+
+       for ($i=0;$i<$len;$i++){
+               $val = ord($str[$i]);
+               if ($val>=224 && $val<=239){
+                       $unicode = ($val-224) * 4096 + (ord($str[$i+1])-128) * 64 + (ord($str[$i+2])-128);
+                       $i+=2;
+                       $utf_code.=chr((int)($unicode/256)).chr($unicode%256);
+               }else if ($val>=192 && $val<=223){
+                       $unicode = ($val-192) * 64 + (ord($str[$i+1])-128);
+                       $i++;
+                       $utf_code.=chr((int)($unicode/256)).chr($unicode%256);
+               }else{
+                       if ($utf_code){
+                               $out.='&'.mod_b64_encode($utf_code).'-';
+                               $utf_code="";
+                       }
+                       if ($str[$i]=="-") $out.="&";
+                       $out.=$str[$i];
+               }
+       }
+       if ($utf_code)
+               $out.='&'.mod_b64_encode($utf_code).'-';
+       return $out;
+}
+
+
+?>
\ No newline at end of file
diff --git a/program/lib/imap.inc b/program/lib/imap.inc
new file mode 100644 (file)
index 0000000..2c07f34
--- /dev/null
@@ -0,0 +1,2120 @@
+<?php
+/////////////////////////////////////////////////////////
+//     
+//     Iloha IMAP Library (IIL)
+//
+//     (C)Copyright 2002 Ryo Chijiiwa <Ryo@IlohaMail.org>
+//
+//     This file is part of IlohaMail. IlohaMail is free software released 
+//     under the GPL license.  See enclosed file COPYING for details, or 
+//     see http://www.fsf.org/copyleft/gpl.html
+//
+/////////////////////////////////////////////////////////
+
+/********************************************************
+
+       FILE: include/imap.inc
+       PURPOSE:
+               Provide alternative IMAP library that doesn't rely on the standard 
+               C-Client based version.  This allows IlohaMail to function regardless
+               of whether or not the PHP build it's running on has IMAP functionality
+               built-in.
+       USEAGE:
+               Function containing "_C_" in name require connection handler to be
+               passed as one of the parameters.  To obtain connection handler, use
+               iil_Connect()
+       VERSION:
+               IlohaMail-0.9-20050415
+       CHANGES:
+               File altered by Thomas Bruederli <roundcube@gmail.com>
+               to fit enhanced equirements by the RoundCube Webmail:
+               - Added list of server capabilites and check these before invoking commands
+               - Added junk flag to iilBasicHeader
+               - Enhanced error reporting on fsockopen()
+               - Additional parameter for SORT command
+               - Removed Call-time pass-by-reference because deprecated
+               - Parse charset from content-type in iil_C_FetchHeaders()
+               - Enhanced heaer sorting
+               - Pass message as reference in iil_C_Append (to save memory)
+               - Added BCC and REFERENCE to the list of headers to fetch in iil_C_FetchHeaders()
+               - Leave messageID unchanged in iil_C_FetchHeaders()
+               - Avoid stripslahes in iil_Connect()
+               - Added patch to iil_SortHeaders() by Richard Green
+               - Removed <br> from error messages (better for logging)
+               - Added patch to iil_C_Sort() enabling UID SORT commands
+               - Added function iil_C_ID2UID()
+               - Casting date parts in iil_StrToTime() to avoid mktime() warnings
+               - Also acceppt LIST responses in iil_C_ListSubscribed()
+               - Sanity check of $message_set in iil_C_FetchHeaders(), iil_C_FetchHeaderIndex(), iil_C_FetchThreadHeaders()
+               - Removed some debuggers (echo ...)
+
+********************************************************/
+
+
+// changed path to work within roundcube webmail
+include_once("lib/icl_commons.inc");
+
+
+if (!$IMAP_USE_HEADER_DATE) $IMAP_USE_INTERNAL_DATE = true;
+$IMAP_MONTHS=array("Jan"=>1,"Feb"=>2,"Mar"=>3,"Apr"=>4,"May"=>5,"Jun"=>6,"Jul"=>7,"Aug"=>8,"Sep"=>9,"Oct"=>10,"Nov"=>11,"Dec"=>12);
+$IMAP_SERVER_TZ = date('Z');
+
+$iil_error;
+$iil_errornum;
+$iil_selected;
+
+class iilConnection{
+       var $fp;
+       var $error;
+       var $errorNum;
+       var $selected;
+       var $message;
+       var $host;
+       var $cache;
+       var $uid_cache;
+       var $do_cache;
+       var $exists;
+       var $recent;
+       var $rootdir;
+       var $delimiter;
+       var $capability = array();
+}
+
+class iilBasicHeader{
+       var $id;
+       var $uid;
+       var $subject;
+       var $from;
+       var $to;
+       var $cc;
+       var $replyto;
+       var $in_reply_to;
+       var $date;
+       var $messageID;
+       var $size;
+       var $encoding;
+       var $ctype;
+       var $flags;
+       var $timestamp;
+       var $f;
+       var $seen;
+       var $deleted;
+       var $recent;
+       var $answered;
+       var $junk;
+       var $internaldate;
+       var $is_reply;
+}
+
+
+class iilThreadHeader{
+       var $id;
+       var $sbj;
+       var $irt;
+       var $mid;
+}
+
+
+function iil_xor($string, $string2){
+    $result = "";
+    $size = strlen($string);
+    for ($i=0; $i<$size; $i++) $result .= chr(ord($string[$i]) ^ ord($string2[$i]));
+        
+    return $result;
+}
+
+function iil_ReadLine($fp, $size){
+       $line="";
+       if ($fp){
+               do{
+                       $buffer = fgets($fp, 2048);
+                       $line.=$buffer;
+               }while($buffer[strlen($buffer)-1]!="\n");
+       }
+       return $line;
+}
+
+function iil_MultLine($fp, $line){
+       $line = chop($line);
+       if (ereg('\{[0-9]+\}$', $line)){
+               $out = "";
+               preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);
+               $bytes = $a[2][0];
+               while(strlen($out)<$bytes){
+                       $out.=chop(iil_ReadLine($fp, 1024));
+               }
+               $line = $a[1][0]."\"$out\"";
+       }
+       return $line;
+}
+
+function iil_ReadBytes($fp, $bytes){
+       $data = "";
+       $len = 0;
+       do{
+               $data.=fread($fp, $bytes-$len);
+               $len = strlen($data);
+       }while($len<$bytes);
+       return $data;
+}
+
+function iil_ReadReply($fp){
+       do{
+               $line = chop(trim(iil_ReadLine($fp, 1024)));
+       }while($line[0]=="*");
+       
+       return $line;
+}
+
+function iil_ParseResult($string){
+       $a=explode(" ", $string);
+       if (count($a) > 2){
+               if (strcasecmp($a[1], "OK")==0) return 0;
+               else if (strcasecmp($a[1], "NO")==0) return -1;
+               else if (strcasecmp($a[1], "BAD")==0) return -2;
+       }else return -3;
+}
+
+// check if $string starts with $match
+function iil_StartsWith($string, $match){
+       $len = strlen($match);
+       if ($len==0) return false;
+       if (strncmp($string, $match, $len)==0) return true;
+       else return false;
+}
+
+function iil_StartsWithI($string, $match){
+       $len = strlen($match);
+       if ($len==0) return false;
+       if (strncasecmp($string, $match, $len)==0) return true;
+       else return false;
+}
+
+
+function iil_C_Authenticate(&$conn, $user, $pass, $encChallenge){
+    
+    // initialize ipad, opad
+    for ($i=0;$i<64;$i++){
+        $ipad.=chr(0x36);
+        $opad.=chr(0x5C);
+    }
+    // pad $pass so it's 64 bytes
+    $padLen = 64 - strlen($pass);
+    for ($i=0;$i<$padLen;$i++) $pass .= chr(0);
+    // generate hash
+    $hash = md5(iil_xor($pass,$opad).pack("H*",md5(iil_xor($pass, $ipad).base64_decode($encChallenge))));
+    // generate reply
+    $reply = base64_encode($user." ".$hash);
+    
+    // send result, get reply
+    fputs($conn->fp, $reply."\r\n");
+    $line = iil_ReadLine($conn->fp, 1024);
+    
+    // process result
+    if (iil_ParseResult($line)==0){
+        $conn->error .= "";
+        $conn->errorNum = 0;
+        return $conn->fp;
+    }else{
+        $conn->error .= 'Authentication for '.$user.' failed (AUTH): "'.htmlspecialchars($line)."\"";
+        $conn->errorNum = -2;
+        return false;
+    }
+}
+
+function iil_C_Login(&$conn, $user, $password){
+
+    fputs($conn->fp, "a001 LOGIN $user \"$password\"\r\n");
+               
+       do{
+           $line = iil_ReadReply($conn->fp);
+       }while(!iil_StartsWith($line, "a001 "));
+    $a=explode(" ", $line);
+    if (strcmp($a[1],"OK")==0){
+        $result=$conn->fp;
+        $conn->error.="";
+        $conn->errorNum = 0;
+    }else{
+        $result=false;
+        fclose($conn->fp);
+        $conn->error .= 'Authentication for '.$user.' failed (LOGIN): "'.htmlspecialchars($line)."\"";
+        $conn->errorNum = -2;
+    }
+    return $result;
+}
+
+function iil_ParseNamespace2($str, &$i, $len=0, $l){
+       if (!$l) $str = str_replace("NIL", "()", $str);
+       if (!$len) $len = strlen($str);
+       $data = array();
+       $in_quotes = false;
+       $elem = 0;
+       for($i;$i<$len;$i++){
+               $c = (string)$str[$i];
+               if ($c=='(' && !$in_quotes){
+                       $i++;
+                       $data[$elem] = iil_ParseNamespace2($str, $i, $len, $l++);
+                       $elem++;
+               }else if ($c==')' && !$in_quotes) return $data;
+               else if ($c=="\\"){
+                       $i++;
+                       if ($in_quotes) $data[$elem].=$c.$str[$i];
+               }else if ($c=='"'){
+                       $in_quotes = !$in_quotes;
+                       if (!$in_quotes) $elem++;
+               }else if ($in_quotes){
+                       $data[$elem].=$c;
+               }
+       }
+       return $data;
+}
+
+function iil_C_NameSpace(&$conn){
+       global $my_prefs;
+       
+       if (!in_array('NAMESPACE', $conn->capability))
+         return false;
+       
+       if ($my_prefs["rootdir"]) return true;
+       
+       fputs($conn->fp, "ns1 NAMESPACE\r\n");
+       do{
+               $line = iil_ReadLine($conn->fp, 1024);
+               if (iil_StartsWith($line, "* NAMESPACE")){
+                       $i = 0;
+                       $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
+               }
+       }while(!iil_StartsWith($line, "ns1"));
+       
+       if (!is_array($data)) return false;
+       
+       $user_space_data = $data[0];
+       if (!is_array($user_space_data)) return false;
+       
+       $first_userspace = $user_space_data[0];
+       if (count($first_userspace)!=2) return false;
+       
+       $conn->rootdir = $first_userspace[0];
+       $conn->delimiter = $first_userspace[1];
+       $my_prefs["rootdir"] = substr($conn->rootdir, 0, -1);
+       
+       return true;
+
+}
+
+function iil_Connect($host, $user, $password){ 
+    global $iil_error, $iil_errornum;
+       global $ICL_SSL, $ICL_PORT;
+       global $IMAP_NO_CACHE;
+       global $my_prefs, $IMAP_USE_INTERNAL_DATE;
+       
+       $iil_error = "";
+       $iil_errornum = 0;
+       
+       //strip slashes
+       // $user = stripslashes($user);
+       // $password = stripslashes($password);
+       
+       //set auth method
+       $auth_method = "plain";
+       if (func_num_args() >= 4){
+               $auth_array = func_get_arg(3);
+               if (is_array($auth_array)) $auth_method = $auth_array["imap"];
+               if (empty($auth_method)) $auth_method = "plain";
+       }
+       $message = "INITIAL: $auth_method\n";
+               
+       $result = false;
+       
+       //initialize connection
+       $conn = new iilConnection;
+       $conn->error="";
+       $conn->errorNum=0;
+       $conn->selected="";
+       $conn->user = $user;
+       $conn->host = $host;
+       $conn->cache = array();
+       $conn->do_cache = (function_exists("cache_write")&&!$IMAP_NO_CACHE);
+       $conn->cache_dirty = array();
+       
+       if ($my_prefs['sort_field']=='INTERNALDATE') $IMAP_USE_INTERNAL_DATE = true;
+       else if ($my_prefs['sort_field']=='DATE') $IMAP_USE_INTERNAL_DATE = false;
+       //echo '<!-- conn sort_field: '.$my_prefs['sort_field'].' //-->';
+       
+       //check input
+       if (empty($host)) $iil_error .= "Invalid host\n";
+       if (empty($user)) $iil_error .= "Invalid user\n";
+       if (empty($password)) $iil_error .= "Invalid password\n";
+       if (!empty($iil_error)) return false;
+       if (!$ICL_PORT) $ICL_PORT = 143;
+       
+       //check for SSL
+       if ($ICL_SSL){
+               $host = "ssl://".$host;
+       }
+       
+       //open socket connection
+       $conn->fp = @fsockopen($host, $ICL_PORT, $errno, $errstr, 10);
+       if (!$conn->fp){
+        $iil_error = "Could not connect to $host at port $ICL_PORT: $errstr";
+        $iil_errornum = -1;
+               return false;
+       }
+
+       $iil_error.="Socket connection established\r\n";
+       $line=iil_ReadLine($conn->fp, 300);
+
+       if (strcasecmp($auth_method, "check")==0){
+               //check for supported auth methods
+               
+               //default to plain text auth
+               $auth_method = "plain";
+                       
+               //check for CRAM-MD5
+               fputs($conn->fp, "cp01 CAPABILITY\r\n");
+               do{
+               $line = trim(chop(iil_ReadLine($conn->fp, 100)));
+               $conn->message.="$line\n";
+                       $a = explode(" ", $line);
+                       if ($line[0]=="*"){
+                               while ( list($k, $w) = each($a) ){
+                                   if ($w!='*' && $w!='CAPABILITY')
+                                       $conn->capability[] = $w;
+                                       if ((strcasecmp($w, "AUTH=CRAM_MD5")==0)||
+                                               (strcasecmp($w, "AUTH=CRAM-MD5")==0)){
+                                                       $auth_method = "auth";
+                                               }
+                               }
+                       }
+               }while($a[0]!="cp01");
+       }
+
+       if (strcasecmp($auth_method, "auth")==0){
+               $conn->message.="Trying CRAM-MD5\n";
+               //do CRAM-MD5 authentication
+               fputs($conn->fp, "a000 AUTHENTICATE CRAM-MD5\r\n");
+               $line = trim(chop(iil_ReadLine($conn->fp, 1024)));
+               $conn->message.="$line\n";
+               if ($line[0]=="+"){
+                       $conn->message.='Got challenge: '.htmlspecialchars($line)."\n";
+                       //got a challenge string, try CRAM-5
+                       $result = iil_C_Authenticate($conn, $user, $password, substr($line,2));
+                       $conn->message.= "Tried CRAM-MD5: $result \n";
+               }else{
+                       $conn->message.='No challenge ('.htmlspecialchars($line)."), try plain\n";
+                       $auth = "plain";
+               }
+       }
+               
+       if ((!$result)||(strcasecmp($auth, "plain")==0)){
+               //do plain text auth
+               $result = iil_C_Login($conn, $user, $password);
+               $conn->message.="Tried PLAIN: $result \n";
+       }
+               
+       $conn->message .= $auth;
+                       
+       if ($result){
+               iil_C_Namespace($conn);
+               return $conn;
+       }else{
+               $iil_error = $conn->error;
+               $iil_errornum = $conn->errorNum;
+               return false;
+       }
+}
+
+function iil_Close(&$conn){
+       iil_C_WriteCache($conn);
+       if (@fputs($conn->fp, "I LOGOUT\r\n")){
+               fgets($conn->fp, 1024);
+               fclose($conn->fp);
+               $conn->fp = false;
+       }
+}
+
+function iil_ClearCache($user, $host){
+}
+
+
+function iil_C_WriteCache(&$conn){
+       //echo "<!-- doing iil_C_WriteCache //-->\n";
+       if (!$conn->do_cache) return false;
+       
+       if (is_array($conn->cache)){
+               while(list($folder,$data)=each($conn->cache)){
+                       if ($folder && is_array($data) && $conn->cache_dirty[$folder]){
+                               $key = $folder.".imap";
+                               $result = cache_write($conn->user, $conn->host, $key, $data, true);
+                               //echo "<!-- writing $key $data: $result //-->\n";
+                       }
+               }
+       }
+}
+
+function iil_C_EnableCache(&$conn){
+       $conn->do_cache = true;
+}
+
+function iil_C_DisableCache(&$conn){
+       $conn->do_cache = false;
+}
+
+function iil_C_LoadCache(&$conn, $folder){
+       if (!$conn->do_cache) return false;
+       
+       $key = $folder.".imap";
+       if (!is_array($conn->cache[$folder])){
+               $conn->cache[$folder] = cache_read($conn->user, $conn->host, $key);
+               $conn->cache_dirty[$folder] = false;
+       }
+}
+
+function iil_C_ExpireCachedItems(&$conn, $folder, $message_set){
+       
+       if (!$conn->do_cache) return;   //caching disabled
+       if (!is_array($conn->cache[$folder])) return;   //cache not initialized|empty
+       if (count($conn->cache[$folder])==0) return;    //cache not initialized|empty
+               
+       $uids = iil_C_FetchHeaderIndex($conn, $folder, $message_set, "UID");
+       $num_removed = 0;
+       if (is_array($uids)){
+               //echo "<!-- unsetting: ".implode(",",$uids)." //-->\n";
+               while(list($n,$uid)=each($uids)){
+                       unset($conn->cache[$folder][$uid]);
+                       //$conn->cache[$folder][$uid] = false;
+                       //$num_removed++;
+               }
+               $conn->cache_dirty[$folder] = true;
+
+               //echo '<!--'."\n";
+               //print_r($conn->cache);
+               //echo "\n".'//-->'."\n";
+       }else{
+               echo "<!-- failed to get uids: $message_set //-->\n";
+       }
+       
+       /*
+       if ($num_removed>0){
+               $new_cache;
+               reset($conn->cache[$folder]);
+               while(list($uid,$item)=each($conn->cache[$folder])){
+                       if ($item) $new_cache[$uid] = $conn->cache[$folder][$uid];
+               }
+               $conn->cache[$folder] = $new_cache;
+       }
+       */
+}
+
+function iil_ExplodeQuotedString($delimiter, $string){
+       $quotes=explode("\"", $string);
+       while ( list($key, $val) = each($quotes))
+               if (($key % 2) == 1) 
+                       $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
+       $string=implode("\"", $quotes);
+       
+       $result=explode($delimiter, $string);
+       while ( list($key, $val) = each($result) )
+               $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
+       
+       return $result;
+}
+
+function iil_CheckForRecent($host, $user, $password, $mailbox){
+       if (empty($mailbox)) $mailbox="INBOX";
+       
+       $conn=iil_Connect($host, $user, $password, "plain");
+       $fp = $conn->fp;
+       if ($fp){
+               fputs($fp, "a002 EXAMINE \"$mailbox\"\r\n");
+               do{
+                       $line=chop(iil_ReadLine($fp, 300));
+                       $a=explode(" ", $line);
+                       if (($a[0]=="*") && (strcasecmp($a[2], "RECENT")==0))  $result=(int)$a[1];
+               }while (!iil_StartsWith($a[0],"a002"));
+
+               fputs($fp, "a003 LOGOUT\r\n");
+               fclose($fp);
+       }else $result=-2;
+       
+       return $result;
+}
+
+function iil_C_Select(&$conn, $mailbox){
+       $fp = $conn->fp;
+       
+       if (empty($mailbox)) return false;
+       if (strcmp($conn->selected, $mailbox)==0) return true;
+       
+       iil_C_LoadCache($conn, $mailbox);
+       
+       if (fputs($fp, "sel1 SELECT \"$mailbox\"\r\n")){
+               do{
+                       $line=chop(iil_ReadLine($fp, 300));
+                       $a=explode(" ", $line);
+                       if (count($a) == 3){
+                               if (strcasecmp($a[2], "EXISTS")==0) $conn->exists=(int)$a[1];
+                               if (strcasecmp($a[2], "RECENT")==0) $conn->recent=(int)$a[1];
+                       }
+               }while (!iil_StartsWith($line, "sel1"));
+
+               $a=explode(" ", $line);
+
+               if (strcasecmp($a[1],"OK")==0){
+                       $conn->selected = $mailbox;
+                       return true;
+               }else return false;
+       }else{
+               return false;
+       }
+}
+
+function iil_C_CheckForRecent(&$conn, $mailbox){
+       if (empty($mailbox)) $mailbox="INBOX";
+       
+       iil_C_Select($conn, $mailbox);
+       if ($conn->selected==$mailbox) return $conn->recent;
+       else return false;
+}
+
+function iil_C_CountMessages(&$conn, $mailbox, $refresh=false){
+       if ($refresh) $conn->selected="";
+       iil_C_Select($conn, $mailbox);
+       if ($conn->selected==$mailbox) return $conn->exists;
+       else return false;
+}
+
+function iil_SplitHeaderLine($string){
+       $pos=strpos($string, ":");
+       if ($pos>0){
+               $res[0]=substr($string, 0, $pos);
+               $res[1]=trim(substr($string, $pos+1));
+               return $res;
+       }else{
+               return $string;
+       }
+}
+
+function iil_StrToTime($str){
+       global $IMAP_MONTHS,$IMAP_SERVER_TZ;
+               
+       if ($str) $time1 = strtotime($str);
+       if ($time1 && $time1!=-1) return $time1-$IMAP_SERVER_TZ;
+       
+       //echo '<!--'.$str.'//-->';
+       
+       //replace double spaces with single space
+       $str = trim($str);
+       $str = str_replace("  ", " ", $str);
+       
+       //strip off day of week
+       $pos=strpos($str, " ");
+       if (!is_numeric(substr($str, 0, $pos))) $str = substr($str, $pos+1);
+
+       //explode, take good parts
+       $a=explode(" ",$str);
+       //$month_a=array("Jan"=>1,"Feb"=>2,"Mar"=>3,"Apr"=>4,"May"=>5,"Jun"=>6,"Jul"=>7,"Aug"=>8,"Sep"=>9,"Oct"=>10,"Nov"=>11,"Dec"=>12);
+       $month_str=$a[1];
+       $month=$IMAP_MONTHS[$month_str];
+       $day=(int)$a[0];
+       $year=(int)$a[2];
+       $time=$a[3];
+       $tz_str = $a[4];
+       $tz = substr($tz_str, 0, 3);
+       $ta = explode(":",$time);
+       $hour=(int)$ta[0]-(int)$tz;
+       $minute=(int)$ta[1];
+       $second=(int)$ta[2];
+       
+       //make UNIX timestamp
+       $time2 = mktime($hour, $minute, $second, $month, $day, $year);
+       //echo '<!--'.$time1.' '.$time2.' //-->'."\n";
+       return $time2;
+}
+
+function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE, $encoding='US-ASCII'){
+       /*  Do "SELECT" command */
+       if (!iil_C_Select($conn, $mailbox)) return false;
+       
+       $field = strtoupper($field);
+       if ($field=='INTERNALDATE') $field='ARRIVAL';
+       $fields = array('ARRIVAL'=>1,'CC'=>1,'DATE'=>1,'FROM'=>1,'SIZE'=>1,'SUBJECT'=>1,'TO'=>1);
+       
+       if (!$fields[$field])
+         return false;
+       
+       $is_uid = $is_uid ? 'UID ' : '';
+       
+       if (!empty($add))
+         $add = " $add";
+
+       $fp = $conn->fp;
+       $command = 's '. $is_uid .'SORT ('.$field.') '.$encoding.' ALL'."$add\r\n";
+       $line = $data = '';
+       
+       if (!fputs($fp, $command)) return false;
+       do{
+               $line = chop(iil_ReadLine($fp, 1024));
+               if (iil_StartsWith($line, '* SORT')) $data.=($data?' ':'').substr($line,7);
+       }while($line[0]!='s');
+       
+       if (empty($data)){
+               $conn->error = $line;
+               return false;
+       }
+       
+       $out = explode(' ',$data);
+       return $out;
+}
+
+function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field, $normalize=true){
+       global $IMAP_USE_INTERNAL_DATE;
+       
+       $c=0;
+       $result=array();
+       $fp = $conn->fp;
+               
+       if (empty($index_field)) $index_field="DATE";
+       $index_field = strtoupper($index_field);
+       
+       list($from_idx, $to_idx) = explode(':', $message_set);
+       if (empty($message_set) || (isset($to_idx) && (int)$from_idx > (int)$to_idx))
+               return false;
+       
+       //$fields_a["DATE"] = ($IMAP_USE_INTERNAL_DATE?6:1);
+       $fields_a['DATE'] = 1;
+       $fields_a['INTERNALDATE'] = 6;
+       $fields_a['FROM'] = 1;
+       $fields_a['REPLY-TO'] = 1;
+       $fields_a['SENDER'] = 1;
+       $fields_a['TO'] = 1;
+       $fields_a['SUBJECT'] = 1;
+       $fields_a['UID'] = 2;
+       $fields_a['SIZE'] = 2;
+       $fields_a['SEEN'] = 3;
+       $fields_a['RECENT'] = 4;
+       $fields_a['DELETED'] = 5;
+       
+       $mode=$fields_a[$index_field];
+       if (!($mode > 0)) return false;
+       
+       /*  Do "SELECT" command */
+       if (!iil_C_Select($conn, $mailbox)) return false;
+               
+       /* FETCH date,from,subject headers */
+       if ($mode==1){
+               $key="fhi".($c++);
+               $request=$key." FETCH $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)])\r\n";
+               if (!fputs($fp, $request)) return false;
+               do{
+                       
+                       $line=chop(iil_ReadLine($fp, 200));
+                       $a=explode(" ", $line);
+                       if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[strlen($line)-1]!=")")){
+                               $id=$a[1];
+
+                               $str=$line=chop(iil_ReadLine($fp, 300));
+
+                               while($line[0]!=")"){                                   //caution, this line works only in this particular case
+                                       $line=chop(iil_ReadLine($fp, 300));
+                                       if ($line[0]!=")"){
+                                               if (ord($line[0]) <= 32){                       //continuation from previous header line
+                                                       $str.=" ".trim($line);
+                                               }
+                                               if ((ord($line[0]) > 32) || (strlen($line[0]) == 0)){
+                                                       list($field, $string) = iil_SplitHeaderLine($str);
+                                                       if (strcasecmp($field, "date")==0){
+                                                               $result[$id]=iil_StrToTime($string);
+                                                       }else{
+                                                               $result[$id] = str_replace("\"", "", $string);
+                                                               if ($normalize) $result[$id]=strtoupper($result[$id]);
+                                                       }
+                                                       $str=$line;
+                                               }
+                                       }
+                               }
+                       }
+                       /*
+                       $end_pos = strlen($line)-1;
+                       if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[$end_pos]=="}")){
+                               $id = $a[1];
+                               $pos = strrpos($line, "{")+1;
+                               $bytes = (int)substr($line, $pos, $end_pos-$pos);
+                               $received = 0;
+                               do{
+                                       $line = iil_ReadLine($fp, 0);
+                                       $received+=strlen($line);
+                                       $line = chop($line);
+                                       
+                                       if ($received>$bytes) break;
+                                       else if (!$line) continue;
+                                       
+                                       list($field,$string)=explode(": ", $line);
+                                       
+                                       if (strcasecmp($field, "date")==0)
+                                               $result[$id] = iil_StrToTime($string);
+                                       else if ($index_field!="DATE")
+                                               $result[$id]=strtoupper(str_replace("\"", "", $string));
+                               }while($line[0]!=")");
+                       }else{
+                               //one line response, not expected so ignore                             
+                       }
+                       */
+               }while(!iil_StartsWith($line, $key));
+       }else if ($mode==6){
+               $key="fhi".($c++);
+               $request = $key." FETCH $message_set (INTERNALDATE)\r\n";
+               if (!fputs($fp, $request)) return false;
+               do{
+                       $line=chop(iil_ReadLine($fp, 200));
+                       if ($line[0]=="*"){
+                               //original: "* 10 FETCH (INTERNALDATE "31-Jul-2002 09:18:02 -0500")"
+                               $paren_pos = strpos($line, "(");
+                               $foo = substr($line, 0, $paren_pos);
+                               $a = explode(" ", $foo);
+                               $id = $a[1];
+                               
+                               $open_pos = strpos($line, "\"") + 1;
+                               $close_pos = strrpos($line, "\"");
+                               if ($open_pos && $close_pos){
+                                       $len = $close_pos - $open_pos;
+                                       $time_str = substr($line, $open_pos, $len);
+                                       $result[$id] = strtotime($time_str);
+                               }
+                       }else{
+                               $a = explode(" ", $line);
+                       }
+               }while(!iil_StartsWith($a[0], $key));
+       }else{
+               if ($mode >= 3) $field_name="FLAGS";
+               else if ($index_field=="SIZE") $field_name="RFC822.SIZE";
+               else $field_name=$index_field;
+
+               /*                      FETCH uid, size, flags          */
+               $key="fhi".($c++);
+               $request=$key." FETCH $message_set ($field_name)\r\n";
+
+               if (!fputs($fp, $request)) return false;
+               do{
+                       $line=chop(iil_ReadLine($fp, 200));
+                       $a = explode(" ", $line);
+                       if (($line[0]=="*") && ($a[2]=="FETCH")){
+                               $line=str_replace("(", "", $line);
+                               $line=str_replace(")", "", $line);
+                               $a=explode(" ", $line);
+                               
+                               $id=$a[1];
+
+                               if (isset($result[$id])) continue; //if we already got the data, skip forward
+                               if ($a[3]!=$field_name) continue;  //make sure it's returning what we requested
+                       
+                               /*  Caution, bad assumptions, next several lines */
+                               if ($mode==2) $result[$id]=$a[4];
+                               else{
+                                       $haystack=strtoupper($line);
+                                       $result[$id]=(strpos($haystack, $index_field) > 0 ? "F" : "N");
+                               }
+                       }
+               }while(!iil_StartsWith($line, $key));
+       }
+
+       //check number of elements...
+       list($start_mid,$end_mid)=explode(':',$message_set);
+       if (is_numeric($start_mid) && is_numeric($end_mid)){
+               //count how many we should have
+               $should_have = $end_mid - $start_mid +1;
+               
+               //if we have less, try and fill in the "gaps"
+               if (count($result)<$should_have){
+                       for($i=$start_mid;$i<=$end_mid;$i++) if (!isset($result[$i])) $result[$i] = '';
+               }
+       }
+       
+       return $result; 
+
+}
+
+function iil_CompressMessageSet($message_set){
+       //given a comma delimited list of independent mid's, 
+       //compresses by grouping sequences together
+       
+       //if less than 255 bytes long, let's not bother
+       if (strlen($message_set)<255) return $message_set;
+       
+       //see if it's already been compress
+       if (strpos($message_set,':')!==false) return $message_set;
+       
+       //separate, then sort
+       $ids = explode(',',$message_set);
+       sort($ids);
+       
+       $result = array();
+       $start = $prev = $ids[0];
+       foreach($ids as $id){
+               $incr = $id - $prev;
+               if ($incr>1){                   //found a gap
+                       if ($start==$prev) $result[] = $prev;   //push single id
+                       else $result[] = $start.':'.$prev;              //push sequence as start_id:end_id
+                       $start = $id;                                                   //start of new sequence
+               }
+               $prev = $id;
+       }
+       //handle the last sequence/id
+       if ($start==$prev) $result[] = $prev;
+       else $result[] = $start.':'.$prev;
+
+       //return as comma separated string
+       return implode(',',$result);
+}
+
+function iil_C_UIDsToMIDs(&$conn, $mailbox, $uids){
+       if (!is_array($uids) || count($uids)==0) return array();
+       return iil_C_Search($conn, $mailbox, "UID ".implode(",", $uids));
+}
+
+function iil_C_UIDToMID(&$conn, $mailbox, $uid){
+       $result = iil_C_UIDsToMIDs($conn, $mailbox, array($uid));
+       if (count($result)==1) return $result[0];
+       else return false;
+}
+
+function iil_C_FetchUIDs(&$conn,$mailbox){
+       global $clock;
+       
+       $num = iil_C_CountMessages($conn, $mailbox);
+       if ($num==0) return array();
+       $message_set = '1'.($num>1?':'.$num:'');
+       
+       //if cache not enabled, just call iil_C_FetchHeaderIndex on 'UID' field
+       if (!$conn->do_cache)
+               return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
+
+       //otherwise, let's check cache first
+       $key = $mailbox.'.uids';
+       $cache_good = true;
+       if ($conn->uid_cache) $data = $conn->uid_cache;
+       else $data = cache_read($conn->user, $conn->host, $key);
+       
+       //was anything cached at all?
+       if ($data===false) $cache_good = -1;
+       
+       //make sure number of messages were the same
+       if ($cache_good>0 && $data['n']!=$num) $cache_good = -2;
+       
+       //if everything's okay so far...
+       if ($cache_good>0){
+               //check UIDs of highest mid with current and cached
+               $temp = iil_C_Search($conn, $mailbox, 'UID '.$data['d'][$num]);
+               if (!$temp || !is_array($temp) || $temp[0]!=$num) $cache_good=-3;
+       }
+
+       //if cached data's good, return it
+       if ($cache_good>0){
+               return $data['d'];
+       }
+
+       //otherwise, we need to fetch it
+       $data = array('n'=>$num,'d'=>array());
+       $data['d'] = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
+       cache_write($conn->user, $conn->host, $key, $data);
+       $conn->uid_cache = $data;
+       return $data['d'];
+}
+
+function iil_SortThreadHeaders($headers, $index_a, $uids){
+       asort($index_a);
+       $result = array();
+       foreach($index_a as $mid=>$foobar){
+               $uid = $uids[$mid];
+               $result[$uid] = $headers[$uid];
+       }
+       return $result;
+}
+
+function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set){
+       global $clock;
+       global $index_a;
+       
+       list($from_idx, $to_idx) = explode(':', $message_set);
+       if (empty($message_set) || (isset($to_idx) && (int)$from_idx > (int)$to_idx))
+               return false;
+
+       $result = array();
+       $uids = iil_C_FetchUIDs($conn, $mailbox);
+       $debug = false;
+       
+       /* Get cached records where possible */
+       if ($conn->do_cache){
+               $cached = cache_read($conn->user, $conn->host, $mailbox.'.thhd');
+               if ($cached && is_array($uids) && count($uids)>0){
+                       $needed_set = "";
+                       foreach($uids as $id=>$uid){
+                               if ($cached[$uid]){
+                                       $result[$uid] = $cached[$uid];
+                                       $result[$uid]->id = $id;
+                               }else $needed_set.=($needed_set?",":"").$id;
+                       }
+                       if ($needed_set) $message_set = $needed_set;
+                       else $message_set = '';
+               }
+       }
+       $message_set = iil_CompressMessageSet($message_set);
+       if ($debug) echo "Still need: ".$message_set;
+       
+       /* if we're missing any, get them */
+       if ($message_set){
+               /* FETCH date,from,subject headers */
+               $key="fh";
+               $fp = $conn->fp;
+               $request=$key." FETCH $message_set (BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])\r\n";
+               $mid_to_id = array();
+               if (!fputs($fp, $request)) return false;
+               do{
+                       $line = chop(iil_ReadLine($fp, 1024));
+                       if ($debug) echo $line."\n";
+                       if (ereg('\{[0-9]+\}$', $line)){
+                               $a = explode(" ", $line);
+                               $new = array();
+
+                               $new_thhd = new iilThreadHeader;
+                               $new_thhd->id = $a[1];
+                               do{
+                                       $line=chop(iil_ReadLine($fp, 1024),"\r\n");
+                                       if (iil_StartsWithI($line,'Message-ID:') || (iil_StartsWithI($line,'In-Reply-To:')) || (iil_StartsWithI($line,'SUBJECT:'))){
+                                               $pos = strpos($line, ":");
+                                               $field_name = substr($line, 0, $pos);
+                                               $field_val = substr($line, $pos+1);
+                                               $new[strtoupper($field_name)] = trim($field_val);
+                                       }else if (ereg('^[[:space:]]', $line)){
+                                               $new[strtoupper($field_name)].= trim($line);
+                                       }
+                               }while($line[0]!=')');
+                               $new_thhd->sbj = $new['SUBJECT'];
+                               $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);
+                               $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);
+                               
+                               $result[$uids[$new_thhd->id]] = $new_thhd;
+                       }
+               }while(!iil_StartsWith($line, "fh"));
+       }
+       
+       /* sort headers */
+       if (is_array($index_a)){
+               $result = iil_SortThreadHeaders($result, $index_a, $uids);      
+       }
+       
+       /* write new set to cache */
+       if ($conn->do_cache){
+               if (count($result)!=count($cached))
+                       cache_write($conn->user, $conn->host, $mailbox.'.thhd', $result);               
+       }
+       
+       //echo 'iil_FetchThreadHeaders:'."\n";
+       //print_r($result);
+       
+       return $result;
+}
+
+function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock){
+       global $index_a;
+
+       list($from_idx, $to_idx) = explode(':', $message_set);
+       if (empty($message_set) || (isset($to_idx) && (int)$from_idx > (int)$to_idx))
+               return false;
+       
+       $result=array();
+       $roots=array();
+       $root_mids = array();
+       $sub_mids = array();
+       $strays = array();
+       $messages = array();
+       $fp = $conn->fp;
+       $debug = false;
+       
+       $sbj_filter_pat = '[a-zA-Z]{2,3}(\[[0-9]*\])?:([[:space:]]*)';
+       
+       /*  Do "SELECT" command */
+       if (!iil_C_Select($conn, $mailbox)) return false;
+
+       /* FETCH date,from,subject headers */
+       $mid_to_id = array();
+       $messages = array();
+       $headers = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);
+       if ($clock) $clock->register('fetched headers');
+       
+       if ($debug) print_r($headers);
+       
+       /* go through header records */
+       foreach($headers as $header){
+               //$id = $header['i'];
+               //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'], 
+               //                      'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);
+               $id = $header->id;
+               $new = array('id'=>$id, 'MESSAGE-ID'=>$header->mid, 
+                                       'IN-REPLY-TO'=>$header->irt, 'SUBJECT'=>$header->sbj);
+
+               /* add to message-id -> mid lookup table */
+               $mid_to_id[$new['MESSAGE-ID']] = $id;
+               
+               /* if no subject, use message-id */
+               if (empty($new['SUBJECT'])) $new['SUBJECT'] = $new['MESSAGE-ID'];
+               
+               /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
+               $sbj_pre ='';
+               $has_re = false;
+               if (eregi($sbj_filter_pat, $new['SUBJECT'])) $has_re = true;
+               if ($has_re||$new['IN-REPLY-TO']) $sbj_pre = 'RE:';
+               
+               /* strip out 're:', 'fw:' etc */
+               if ($has_re) $sbj = ereg_replace($sbj_filter_pat,'', $new['SUBJECT']);
+               else $sbj = $new['SUBJECT'];
+               $new['SUBJECT'] = $sbj_pre.$sbj;
+               
+               
+               /* if subject not a known thread-root, add to list */
+               if ($debug) echo $id.' '.$new['SUBJECT']."\t".$new['MESSAGE-ID']."\n";
+               $root_id = $roots[$sbj];
+               
+               if ($root_id && ($has_re || !$root_in_root[$root_id])){
+                       if ($debug) echo "\tfound root: $root_id\n";
+                       $sub_mids[$new['MESSAGE-ID']] = $root_id;
+                       $result[$root_id][] = $id;
+               }else if (!isset($roots[$sbj])||(!$has_re&&$root_in_root[$root_id])){
+                       /* try to use In-Reply-To header to find root 
+                               unless subject contains 'Re:' */
+                       if ($has_re&&$new['IN-REPLY-TO']){
+                               if ($debug) echo "\tlooking: ".$new['IN-REPLY-TO']."\n";
+                               
+                               //reply to known message?
+                               $temp = $sub_mids[$new['IN-REPLY-TO']];
+                               
+                               if ($temp){
+                                       //found it, root:=parent's root
+                                       if ($debug) echo "\tfound parent: ".$new['SUBJECT']."\n";
+                                       $result[$temp][] = $id;
+                                       $sub_mids[$new['MESSAGE-ID']] = $temp;
+                                       $sbj = '';
+                               }else{
+                                       //if we can't find referenced parent, it's a "stray"
+                                       $strays[$id] = $new['IN-REPLY-TO'];
+                               }
+                       }
+                       
+                       //add subject as root
+                       if ($sbj){
+                               if ($debug) echo "\t added to root\n";
+                               $roots[$sbj] = $id;
+                               $root_in_root[$id] = !$has_re;
+                               $sub_mids[$new['MESSAGE-ID']] = $id;
+                               $result[$id] = array($id);
+                       }
+                       if ($debug) echo $new['MESSAGE-ID']."\t".$sbj."\n";
+               }
+                       
+       }
+       
+       //now that we've gone through all the messages,
+       //go back and try and link up the stray threads
+       if (count($strays)>0){
+               foreach($strays as $id=>$irt){
+                       $root_id = $sub_mids[$irt];
+                       if (!$root_id || $root_id==$id) continue;
+                       $result[$root_id] = array_merge($result[$root_id],$result[$id]);
+                       unset($result[$id]);
+               }
+       }
+       
+       if ($clock) $clock->register('data prepped');
+       
+       if ($debug) print_r($roots);
+       //print_r($result);
+       return $result;
+}
+
+
+function iil_SortThreads(&$tree, $index, $sort_order='ASC'){
+       if (!is_array($tree) || !is_array($index)) return false;
+
+       //create an id to position lookup table
+       $i = 0;
+       foreach($index as $id=>$val){
+               $i++;
+               $index[$id] = $i;
+       }
+       $max = $i+1;
+       
+       //for each tree, set array key to position
+       $itree = array();
+       foreach($tree as $id=>$node){
+               if (count($tree[$id])<=1){
+                       //for "threads" with only one message, key is position of that message
+                       $n = $index[$id];
+                       $itree[$n] = array($n=>$id);
+               }else{
+                       //for "threads" with multiple messages, 
+                       $min = $max;
+                       $new_a = array();
+                       foreach($tree[$id] as $mid){
+                               $new_a[$index[$mid]] = $mid;            //create new sub-array mapping position to id
+                               $pos = $index[$mid];
+                               if ($pos&&$pos<$min) $min = $index[$mid];       //find smallest position
+                       }
+                       $n = $min;      //smallest position of child is thread position
+                       
+                       //assign smallest position to root level key
+                       //set children array to one created above
+                       ksort($new_a);
+                       $itree[$n] = $new_a;
+               }
+       }
+       
+       
+       //sort by key, this basically sorts all threads
+       ksort($itree);
+       $i=0;
+       $out=array();
+       foreach($itree as $k=>$node){
+               $out[$i] = $itree[$k];
+               $i++;
+       }
+       
+       //return
+       return $out;
+}
+
+function iil_IndexThreads(&$tree){
+       /* creates array mapping mid to thread id */
+       
+       if (!is_array($tree)) return false;
+       
+       $t_index = array();
+       foreach($tree as $pos=>$kids){
+               foreach($kids as $kid) $t_index[$kid] = $pos;
+       }
+       
+       return $t_index;
+}
+
+function iil_C_FetchHeaders(&$conn, $mailbox, $message_set){
+       global $IMAP_USE_INTERNAL_DATE;
+       
+       $c=0;
+       $result=array();
+       $fp = $conn->fp;
+       
+       list($from_idx, $to_idx) = explode(':', $message_set);
+       if (empty($message_set) || (isset($to_idx) && (int)$from_idx > (int)$to_idx))
+               return false;
+               
+       /*  Do "SELECT" command */
+       if (!iil_C_Select($conn, $mailbox)){
+               $conn->error = "Couldn't select $mailbox";
+               return false;
+       }
+               
+       /* Get cached records where possible */
+       if ($conn->do_cache){
+               $uids = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, "UID");
+               if (is_array($uids) && count($conn->cache[$mailbox]>0)){
+                       $needed_set = "";
+                       while(list($id,$uid)=each($uids)){
+                               if ($conn->cache[$mailbox][$uid]){
+                                       $result[$id] = $conn->cache[$mailbox][$uid];
+                                       $result[$id]->id = $id;
+                               }else $needed_set.=($needed_set?",":"").$id;
+                       }
+                       //echo "<!-- iil_C_FetchHeader\nMessage Set: $message_set\nNeeded Set:$needed_set\n//-->\n";
+                       if ($needed_set) $message_set = iil_CompressMessageSet($needed_set);
+                       else return $result;
+               }
+       }
+
+       /* FETCH date,from,subject headers */
+       $key="fh".($c++);
+       $request=$key." FETCH $message_set (BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID REFERENCE)])\r\n";
+
+       if (!fputs($fp, $request)) return false;
+       do{
+               $line=chop(iil_ReadLine($fp, 200));
+               $a=explode(" ", $line);
+               if (($line[0]=="*") && ($a[2]=="FETCH")){
+                       $id=$a[1];
+                       $result[$id]=new iilBasicHeader;
+                       $result[$id]->id = $id;
+                       $result[$id]->subject = "";
+                       /*
+                               Start parsing headers.  The problem is, some header "lines" take up multiple lines.
+                               So, we'll read ahead, and if the one we're reading now is a valid header, we'll
+                               process the previous line.  Otherwise, we'll keep adding the strings until we come
+                               to the next valid header line.
+                       */
+                       $i = 0;
+                       $lines = array();
+                       do{
+                               $line = chop(iil_ReadLine($fp, 300),"\r\n");
+                               if (ord($line[0])<=32) $lines[$i].=(empty($lines[$i])?"":"\n").trim(chop($line));
+                               else{
+                                       $i++;
+                                       $lines[$i] = trim(chop($line));
+                               }
+                       }while($line[0]!=")" && strncmp($line, $key, strlen($key)));  // patch from "Maksim Rubis" <siburny@hotmail.com>
+                       
+            if(strncmp($line, $key, strlen($key)))
+            { 
+                       //process header, fill iilBasicHeader obj.
+                       //      initialize
+                       if (is_array($headers)){
+                               reset($headers);
+                               while ( list($k, $bar) = each($headers) ) $headers[$k] = "";
+                       }
+
+                       //      create array with header field:data
+                       $headers = array();
+                       while ( list($lines_key, $str) = each($lines) ){
+                               list($field, $string) = iil_SplitHeaderLine($str);
+                               $field = strtolower($field);
+                               $headers[$field] = $string;
+                       }
+                       $result[$id]->date = $headers["date"];
+                       $result[$id]->timestamp = iil_StrToTime($headers["date"]);
+                       $result[$id]->from = $headers["from"];
+                       $result[$id]->to = str_replace("\n", " ", $headers["to"]);
+                       $result[$id]->subject = str_replace("\n", "", $headers["subject"]);
+                       $result[$id]->replyto = str_replace("\n", " ", $headers["reply-to"]);
+                       $result[$id]->cc = str_replace("\n", " ", $headers["cc"]);
+                       $result[$id]->bcc = str_replace("\n", " ", $headers["bcc"]);
+                       $result[$id]->encoding = str_replace("\n", " ", $headers["content-transfer-encoding"]);
+                       $result[$id]->ctype = str_replace("\n", " ", $headers["content-type"]);
+                       $result[$id]->in_reply_to = ereg_replace("[\n<>]",'', $headers['in-reply-to']);
+                       $result[$id]->reference = $headers["reference"];
+                       
+                       list($result[$id]->ctype, $ctype_add) = explode(";", $headers["content-type"]);
+
+                       if (preg_match('/charset="?([a-z0-9\-]+)"?/i', $ctype_add, $regs))
+                               $result[$id]->charset = $regs[1];
+
+                       $messageID = $headers["message-id"];
+                       if (!$messageID) "mid:".$id;
+                       $result[$id]->messageID = $messageID;
+                       }
+            else {
+            $a=explode(" ", $line);
+            } 
+                       
+               }
+       }while(strcmp($a[0], $key)!=0);
+               
+       /* 
+               FETCH uid, size, flags
+               Sample reply line: "* 3 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen \Deleted))"
+       */
+       $command_key="fh".($c++);
+       $request= $command_key." FETCH $message_set (UID RFC822.SIZE FLAGS INTERNALDATE)\r\n";
+       if (!fputs($fp, $request)) return false;
+       do{
+               $line=chop(iil_ReadLine($fp, 200));
+               //$a = explode(" ", $line);
+               //if (($line[0]=="*") && ($a[2]=="FETCH")){
+               if ($line[0]=="*"){
+                       //echo "<!-- $line //-->\n";
+                       //get outter most parens
+                       $open_pos = strpos($line, "(") + 1;
+                       $close_pos = strrpos($line, ")");
+                       if ($open_pos && $close_pos){
+                               //extract ID from pre-paren
+                               $pre_str = substr($line, 0, $open_pos);
+                               $pre_a = explode(" ", $line);
+                               $id = $pre_a[1];
+                               
+                               //get data
+                               $len = $close_pos - $open_pos;
+                               $str = substr($line, $open_pos, $len);
+                               
+                               //swap parents with quotes, then explode
+                               $str = eregi_replace("[()]", "\"", $str);
+                               $a = iil_ExplodeQuotedString(" ", $str);
+                               
+                               //did we get the right number of replies?
+                               $parts_count = count($a);
+                               if ($parts_count>=8){
+                                       for ($i=0;$i<$parts_count;$i=$i+2){
+                                               if (strcasecmp($a[$i],"UID")==0) $result[$id]->uid=$a[$i+1];
+                                               else if (strcasecmp($a[$i],"RFC822.SIZE")==0) $result[$id]->size=$a[$i+1];
+                                               else if (strcasecmp($a[$i],"INTERNALDATE")==0) $time_str = $a[$i+1];
+                                               else if (strcasecmp($a[$i],"FLAGS")==0) $flags_str = $a[$i+1];
+                                       }
+
+                                       // process flags
+                                       $flags_str = eregi_replace('[\\\"]', "", $flags_str);
+                                       $flags_a = explode(" ", $flags_str);
+                                       //echo "<!-- ID: $id FLAGS: ".implode(",", $flags_a)." //-->\n";
+                                       
+                                       $result[$id]->seen = false;
+                                       $result[$id]->recent = false;
+                                       $result[$id]->deleted = false;
+                                       $result[$id]->answered = false;
+                                       if (is_array($flags_a)){
+                                               reset($flags_a);
+                                               while (list($key,$val)=each($flags_a)){
+                                                       if (strcasecmp($val,"Seen")==0) $result[$id]->seen = true;
+                                                       else if (strcasecmp($val, "Deleted")==0) $result[$id]->deleted=true;
+                                                       else if (strcasecmp($val, "Recent")==0) $result[$id]->recent = true;
+                                                       else if (strcasecmp($val, "Answered")==0) $result[$id]->answered = true;
+                                               }
+                                               $result[$id]->flags=$flags_str;
+                                       }
+                       
+                                       // if time is gmt...    
+                                       $time_str = str_replace('GMT','+0000',$time_str);
+                                       
+                                       //get timezone
+                                       $time_str = substr($time_str, 0, -1);
+                                       $time_zone_str = substr($time_str, -5); //extract timezone
+                                       $time_str = substr($time_str, 1, -6); //remove quotes
+                                       $time_zone = (float)substr($time_zone_str, 1, 2); //get first two digits
+                                       if ($time_zone_str[3]!='0') $time_zone += 0.5;  //handle half hour offset
+                                       if ($time_zone_str[0]=="-") $time_zone = $time_zone * -1.0; //minus?
+                                       $result[$id]->internaldate = $time_str;
+                                       
+                                       if ($IMAP_USE_INTERNAL_DATE){
+                                               //calculate timestamp
+                                               $timestamp = strtotime($time_str); //return's server's time
+                                               $na_timestamp = $timestamp;
+                                               $timestamp -= $time_zone * 3600; //compensate for tz, get GMT
+                                               $result[$id]->timestamp = $timestamp;
+                                       }
+                                               
+                                       if ($conn->do_cache){
+                                               $uid = $result[$id]->uid;
+                                               $conn->cache[$mailbox][$uid] = $result[$id];
+                                               $conn->cache_dirty[$mailbox] = true;
+                                       }
+                                       //echo "<!-- ID: $id : $time_str -- local: $na_timestamp (".date("F j, Y, g:i a", $na_timestamp).") tz: $time_zone -- GMT: ".$timestamp." (".date("F j, Y, g:i a", $timestamp).")  //-->\n";
+                               }else{
+                                       //echo "<!-- ERROR: $id : $str //-->\n";
+                               }
+                       }
+               }
+       }while(strpos($line, $command_key)===false);
+               
+       return $result;
+}
+
+
+function iil_C_FetchHeader(&$conn, $mailbox, $id){
+       $fp = $conn->fp;
+       $a=iil_C_FetchHeaders($conn, $mailbox, $id);
+       if (is_array($a)) return $a[$id];
+       else return false;
+}
+
+
+function iil_SortHeaders($a, $field, $flag){
+       if (empty($field)) $field="uid";
+       $field=strtolower($field);
+       if ($field=="date"||$field=='internaldate') $field="timestamp";
+       if (empty($flag)) $flag="ASC";
+       $flag=strtoupper($flag);
+       $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ',"\"") : array("\"");
+
+       $c=count($a);
+       if ($c>0){
+               /*
+                       Strategy:
+                       First, we'll create an "index" array.
+                       Then, we'll use sort() on that array, 
+                       and use that to sort the main array.
+               */
+                
+               // create "index" array
+               $index=array();
+               reset($a);
+               while (list($key, $val)=each($a)){
+
+                       if ($field=="timestamp"){
+                               $data = @strtotime($val->date);
+                               if ($data == false)
+                                       $data = $val->timestamp;
+                               }
+                       else {
+                               $data = $val->$field;
+                               if (is_string($data))
+                                       $data=strtoupper(str_replace($stripArr, "", $data));
+                               }
+
+                       $index[$key]=$data;
+               }
+               
+               // sort index
+               $i=0;
+               if ($flag=="ASC") asort($index);
+               else arsort($index);
+               
+               // form new array based on index 
+               $result=array();
+               reset($index);
+               while (list($key, $val)=each($index)){
+                       $result[$key]=$a[$key];
+                       $i++;
+               }
+       }
+       
+       return $result;
+}
+
+function iil_C_Expunge(&$conn, $mailbox){
+       $fp = $conn->fp;
+       if (iil_C_Select($conn, $mailbox)){
+               $c=0;
+               fputs($fp, "exp1 EXPUNGE\r\n");
+               do{
+                       $line=chop(iil_ReadLine($fp, 100));
+                       if ($line[0]=="*") $c++;
+               }while (!iil_StartsWith($line, "exp1"));
+               
+               if (iil_ParseResult($line) == 0){
+                       $conn->selected = ""; //state has changed, need to reselect                     
+                       //$conn->exists-=$c;
+                       return $c;
+               }else{
+                       $conn->error = $line;
+                       return -1;
+               }
+       }
+       
+       return -1;
+}
+
+function iil_C_ModFlag(&$conn, $mailbox, $messages, $flag, $mod){
+       if ($mod!="+" && $mod!="-") return -1;
+       
+       $fp = $conn->fp;
+       $flags=array(
+                    "SEEN"=>"\\Seen",
+                    "DELETED"=>"\\Deleted",
+                    "RECENT"=>"\\Recent",
+                    "ANSWERED"=>"\\Answered",
+                    "DRAFT"=>"\\Draft",
+                                       "FLAGGED"=>"\\Flagged"
+                   );
+       $flag=strtoupper($flag);
+       $flag=$flags[$flag];
+       if (iil_C_Select($conn, $mailbox)){
+               $c=0;
+               fputs($fp, "flg STORE $messages ".$mod."FLAGS (".$flag.")\r\n");
+               do{
+                       $line=chop(iil_ReadLine($fp, 100));
+                       if ($line[0]=="*") $c++;
+               }while (!iil_StartsWith($line, "flg"));
+
+               if (iil_ParseResult($line) == 0){
+                       iil_C_ExpireCachedItems($conn, $mailbox, $messages);
+                       return $c;
+               }else{
+                       $conn->error = $line;
+                       return -1;
+               }
+       }else{
+               $conn->error = "Select failed";
+               return -1;
+       }
+}
+
+function iil_C_Flag(&$conn, $mailbox, $messages, $flag){
+       return iil_C_ModFlag($conn, $mailbox, $messages, $flag, "+");
+}
+
+function iil_C_Unflag(&$conn, $mailbox, $messages, $flag){
+       return iil_C_ModFlag($conn, $mailbox, $messages, $flag, "-");
+}
+
+function iil_C_Delete(&$conn, $mailbox, $messages){
+       return iil_C_ModFlag($conn, $mailbox, $messages, "DELETED", "+");
+}
+
+function iil_C_Undelete(&$conn, $mailbox, $messages){
+       return iil_C_ModFlag($conn, $mailbox, $messages, "DELETED", "-");
+}
+
+
+function iil_C_Unseen(&$conn, $mailbox, $messages){
+       return iil_C_ModFlag($conn, $mailbox, $messages, "SEEN", "-");
+}
+
+
+function iil_C_Copy(&$conn, $messages, $from, $to){
+       $fp = $conn->fp;
+
+       if (empty($from) || empty($to)) return -1;
+
+       if (iil_C_Select($conn, $from)){
+               $c=0;
+               
+               fputs($fp, "cpy1 COPY $messages \"$to\"\r\n");
+               $line=iil_ReadReply($fp);
+               return iil_ParseResult($line);
+       }else{
+               return -1;
+       }
+}
+
+function iil_FormatSearchDate($month, $day, $year){
+       $month = (int)$month;
+       $months=array(
+                       1=>"Jan", 2=>"Feb", 3=>"Mar", 4=>"Apr", 
+                       5=>"May", 6=>"Jun", 7=>"Jul", 8=>"Aug", 
+                       9=>"Sep", 10=>"Oct", 11=>"Nov", 12=>"Dec"
+                       );
+       return $day."-".$months[$month]."-".$year;
+}
+
+function iil_C_CountUnseen(&$conn, $folder){
+       $index = iil_C_Search($conn, $folder, "ALL UNSEEN");
+       if (is_array($index)){
+               $str = implode(",", $index);
+               if (empty($str)) return false;
+               else return count($index);
+       }else return false;
+}
+
+function iil_C_UID2ID(&$conn, $folder, $uid){
+       if ($uid > 0){
+               $id_a = iil_C_Search($conn, $folder, "UID $uid");
+               if (is_array($id_a)){
+                       $count = count($id_a);
+                       if ($count > 1) return false;
+                       else return $id_a[0];
+               }
+       }
+       return false;
+}
+
+function iil_C_ID2UID(&$conn, $folder, $id){
+       $fp = $conn->fp;
+       $result=-1;
+       if ($id > 0) {
+               if (iil_C_Select($conn, $folder)){
+                       $key = "FUID";
+                       if (fputs($fp, "$key FETCH $id (UID)\r\n")){
+                               do{
+                                       $line=chop(iil_ReadLine($fp, 1024));
+                                       if (eregi("^\* $id FETCH \(UID (.*)\)", $line, $r)){
+                                               $result = $r[1];
+                                       }
+                               } while (!preg_match("/^$key/", $line));
+                       }
+               }
+       }
+       return $result;
+}
+
+function iil_C_Search(&$conn, $folder, $criteria){
+       $fp = $conn->fp;
+       if (iil_C_Select($conn, $folder)){
+               $c=0;
+               
+               $query = "srch1 SEARCH ".chop($criteria)."\r\n";
+               fputs($fp, $query);
+               do{
+                       $line=trim(chop(iil_ReadLine($fp, 10000)));
+                       if (eregi("^\* SEARCH", $line)){
+                               $str = trim(substr($line, 8));
+                               $messages = explode(" ", $str);
+                       }
+               }while(!iil_StartsWith($line, "srch1"));
+               
+               $result_code=iil_ParseResult($line);
+               if ($result_code==0) return $messages;
+               else{
+                       $conn->error = "iil_C_Search: ".$line."\n";
+                       return false;
+               }
+               
+       }else{
+               $conn->error = "iil_C_Search: Couldn't select \"$folder\"\n";
+               return false;
+       }
+}
+
+function iil_C_Move(&$conn, $messages, $from, $to){
+       $fp = $conn->fp;
+       
+       if (!$from || !$to) return -1;
+       
+       $r=iil_C_Copy($conn, $messages, $from,$to);
+       if ($r==0){
+               return iil_C_Delete($conn, $from, $messages);
+       }else{
+               return $r;
+       }
+}
+
+function iil_C_GetHierarchyDelimiter(&$conn){
+       if ($conn->delimiter) return $conn->delimiter;
+       
+       $fp = $conn->fp;
+       $delimiter = false;
+       
+       //try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
+       if (!fputs($fp, "ghd LIST \"\" \"\"\r\n")) return false;
+       do{
+               $line=iil_ReadLine($fp, 500);
+               if ($line[0]=="*"){
+                       $line = rtrim($line);
+                       $a=iil_ExplodeQuotedString(" ", $line);
+                       if ($a[0]=="*") $delimiter = str_replace("\"", "", $a[count($a)-2]);
+               }
+       }while (!iil_StartsWith($line, "ghd"));
+
+       if (strlen($delimiter)>0) return $delimiter;
+       
+       //if that fails, try namespace extension
+       //try to fetch namespace data
+       fputs($conn->fp, "ns1 NAMESPACE\r\n");
+       do{
+               $line = iil_ReadLine($conn->fp, 1024);
+               if (iil_StartsWith($line, "* NAMESPACE")){
+                       $i = 0;
+                       $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
+               }
+       }while(!iil_StartsWith($line, "ns1"));
+               
+       if (!is_array($data)) return false;
+       
+       //extract user space data (opposed to global/shared space)
+       $user_space_data = $data[0];
+       if (!is_array($user_space_data)) return false;
+       
+       //get first element
+       $first_userspace = $user_space_data[0];
+       if (!is_array($first_userspace)) return false;
+
+       //extract delimiter
+       $delimiter = $first_userspace[1];       
+
+       return $delimiter;
+}
+
+function iil_C_ListMailboxes(&$conn, $ref, $mailbox){
+       global $IGNORE_FOLDERS;
+       
+       $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
+               
+       $fp = $conn->fp;
+       if (empty($mailbox)) $mailbox="*";
+       if (empty($ref) && $conn->rootdir) $ref = $conn->rootdir;
+       
+    // send command
+       if (!fputs($fp, "lmb LIST \"".$ref."\" \"$mailbox\"\r\n")) return false;
+       $i=0;
+    // get folder list
+       do{
+               $line=iil_ReadLine($fp, 500);
+               $line=iil_MultLine($fp, $line);
+
+               $a = explode(" ", $line);
+               if (($line[0]=="*") && ($a[1]=="LIST")){
+                       $line = rtrim($line);
+            // split one line
+                       $a=iil_ExplodeQuotedString(" ", $line);
+            // last string is folder name
+                       $folder = str_replace("\"", "", $a[count($a)-1]);
+            if (empty($ignore) || (!empty($ignore) && !eregi($ignore, $folder))) $folders[$i] = $folder;
+            // second from last is delimiter
+            $delim = str_replace("\"", "", $a[count($a)-2]);
+            // is it a container?
+            $i++;
+               }
+       }while (!iil_StartsWith($line, "lmb"));
+
+       if (is_array($folders)){
+        if (!empty($ref)){
+            // if rootdir was specified, make sure it's the first element
+            // some IMAP servers (i.e. Courier) won't return it
+            if ($ref[strlen($ref)-1]==$delim) $ref = substr($ref, 0, strlen($ref)-1);
+            if ($folders[0]!=$ref) array_unshift($folders, $ref);
+        }
+        return $folders;
+       }else if (iil_ParseResult($line)==0){
+               return array('INBOX');
+       }else{
+               $conn->error = $line;
+               return false;
+       }
+}
+
+
+function iil_C_ListSubscribed(&$conn, $ref, $mailbox){
+       global $IGNORE_FOLDERS;
+       
+       $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
+       
+       $fp = $conn->fp;
+       if (empty($mailbox)) $mailbox = "*";
+       if (empty($ref) && $conn->rootdir) $ref = $conn->rootdir;
+       $folders = array();
+
+    // send command
+       if (!fputs($fp, "lsb LSUB \"".$ref."\" \"".$mailbox."\"\r\n")){
+               $conn->error = "Couldn't send LSUB command\n";
+               return false;
+       }
+       $i=0;
+    // get folder list
+       do{
+               $line=iil_ReadLine($fp, 500);
+               $line=iil_MultLine($fp, $line);
+               $a = explode(" ", $line);
+               if (($line[0]=="*") && ($a[1]=="LSUB" || $a[1]=="LIST")){
+                       $line = rtrim($line);
+            // split one line
+                       $a=iil_ExplodeQuotedString(" ", $line);
+            // last string is folder name
+            //$folder = UTF7DecodeString(str_replace("\"", "", $a[count($a)-1]));
+            $folder = str_replace("\"", "", $a[count($a)-1]);
+                       if ((!in_array($folder, $folders)) && (empty($ignore) || (!empty($ignore) && !eregi($ignore, $folder)))) $folders[$i] = $folder;
+            // second from last is delimiter
+            $delim = str_replace("\"", "", $a[count($a)-2]);
+            // is it a container?
+            $i++;
+               }
+       }while (!iil_StartsWith($line, "lsb"));
+
+       if (is_array($folders)){
+        if (!empty($ref)){
+            // if rootdir was specified, make sure it's the first element
+            // some IMAP servers (i.e. Courier) won't return it
+            if ($ref[strlen($ref)-1]==$delim) $ref = substr($ref, 0, strlen($ref)-1);
+            if ($folders[0]!=$ref) array_unshift($folders, $ref);
+        }
+        return $folders;
+       }else{
+               $conn->error = $line;
+               return false;
+       }
+}
+
+
+function iil_C_Subscribe(&$conn, $folder){
+       $fp = $conn->fp;
+
+       $query = "sub1 SUBSCRIBE \"".$folder."\"\r\n";
+       fputs($fp, $query);
+       $line=trim(chop(iil_ReadLine($fp, 10000)));
+       return iil_ParseResult($line);
+}
+
+
+function iil_C_UnSubscribe(&$conn, $folder){
+       $fp = $conn->fp;
+
+       $query = "usub1 UNSUBSCRIBE \"".$folder."\"\r\n";
+       fputs($fp, $query);
+       $line=trim(chop(iil_ReadLine($fp, 10000)));
+       return iil_ParseResult($line);
+}
+
+
+function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part){
+       $fp = $conn->fp;
+       $result=false;
+       if (($part==0)||(empty($part))) $part="HEADER";
+       else $part.=".MIME";
+       
+       if (iil_C_Select($conn, $mailbox)){
+               $key="fh".($c++);
+               $request=$key." FETCH $id (BODY.PEEK[$part])\r\n";
+               if (!fputs($fp, $request)) return false;
+               do{
+                       $line=chop(iil_ReadLine($fp, 200));
+                       $a=explode(" ", $line);
+                       if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[strlen($line)-1]!=")")){
+                               $line=iil_ReadLine($fp, 300);
+                               while(chop($line)!=")"){
+                                       $result.=$line;
+                                       $line=iil_ReadLine($fp, 300);
+                               }
+                       }
+               }while(strcmp($a[0], $key)!=0);
+       }
+       
+       return $result;
+}
+
+
+function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part, $mode){
+    /* modes:
+        1: return string
+        2: print
+        3: base64 and print
+    */
+       $fp = $conn->fp;
+       $result=false;
+       if (($part==0)||(empty($part))) $part="TEXT";
+       
+       if (iil_C_Select($conn, $mailbox)){
+        $reply_key="* ".$id;
+        // format request
+               $key="ftch".($c++)." ";
+               $request=$key."FETCH $id (BODY.PEEK[$part])\r\n";
+        // send request
+               if (!fputs($fp, $request)) return false;
+        // receive reply line
+        do{
+            $line = chop(iil_ReadLine($fp, 1000));
+            $a = explode(" ", $line);
+        }while ($a[2]!="FETCH");
+        $len = strlen($line);
+        if ($line[$len-1] == ")"){
+            //one line response, get everything between first and last quotes
+            $from = strpos($line, "\"") + 1;
+            $to = strrpos($line, "\"");
+            $len = $to - $from;
+            if ($mode==1) $result = substr($line, $from, $len);
+            else if ($mode==2) echo substr($line, $from, $len);
+            else if ($mode==3) echo base64_decode(substr($line, $from, $len));
+        }else if ($line[$len-1] == "}"){
+            //multi-line request, find sizes of content and receive that many bytes
+            $from = strpos($line, "{") + 1;
+            $to = strrpos($line, "}");
+            $len = $to - $from;
+            $sizeStr = substr($line, $from, $len);
+            $bytes = (int)$sizeStr;
+            $received = 0;
+            while ($received < $bytes){
+                $remaining = $bytes - $received;
+                $line = iil_ReadLine($fp, 1024);
+                $len = strlen($line);
+                if ($len > $remaining) substr($line, 0, $remaining);
+                $received += strlen($line);
+                if ($mode==1) $result .= chop($line)."\n";
+                else if ($mode==2){ echo chop($line)."\n"; flush(); }
+                else if ($mode==3){ echo base64_decode($line); flush(); }
+            }
+        }
+        // read in anything up until 'til last line
+               do{
+            $line = iil_ReadLine($fp, 1024);
+               }while(!iil_StartsWith($line, $key));
+        
+        if ($result){
+                       $result = chop($result);
+            return $result; // substr($result, 0, strlen($result)-1);
+        }else return false;
+       }else{
+               echo "Select failed.";
+       }
+    
+    if ($mode==1) return $result;
+    else return $received;
+}
+
+function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part){
+    return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1);
+}
+
+function iil_C_PrintPartBody(&$conn, $mailbox, $id, $part){
+    iil_C_HandlePartBody($conn, $mailbox, $id, $part, 2);
+}
+
+function iil_C_PrintBase64Body(&$conn, $mailbox, $id, $part){
+    iil_C_HandlePartBody($conn, $mailbox, $id, $part, 3);
+}
+
+function iil_C_CreateFolder(&$conn, $folder){
+       $fp = $conn->fp;
+       if (fputs($fp, "c CREATE \"".$folder."\"\r\n")){
+               do{
+                       $line=iil_ReadLine($fp, 300);
+               }while($line[0]!="c");
+        $conn->error = $line;
+               return (iil_ParseResult($line)==0);
+       }else{
+               return false;
+       }
+}
+
+function iil_C_RenameFolder(&$conn, $from, $to){
+       $fp = $conn->fp;
+       if (fputs($fp, "r RENAME \"".$from."\" \"".$to."\"\r\n")){
+               do{
+                       $line=iil_ReadLine($fp, 300);
+               }while($line[0]!="r");
+               return (iil_ParseResult($line)==0);
+       }else{
+               return false;
+       }       
+}
+
+function iil_C_DeleteFolder(&$conn, $folder){
+       $fp = $conn->fp;
+       if (fputs($fp, "d DELETE \"".$folder."\"\r\n")){
+               do{
+                       $line=iil_ReadLine($fp, 300);
+               }while($line[0]!="d");
+               return (iil_ParseResult($line)==0);
+       }else{
+               $conn->error = "Couldn't send command\n";
+               return false;
+       }
+}
+
+function iil_C_Append(&$conn, $folder, &$message){
+       if (!$folder) return false;
+       $fp = $conn->fp;
+
+       $message = str_replace("\r", "", $message);
+       $message = str_replace("\n", "\r\n", $message);         
+
+       $len = strlen($message);
+       if (!$len) return false;
+       
+       $request="A APPEND \"".$folder."\" (\\Seen) {".$len."}\r\n";
+       if (fputs($fp, $request)){
+               $line=iil_ReadLine($fp, 100);           
+               $sent = fwrite($fp, $message."\r\n");
+               flush();
+               do{
+                       $line=iil_ReadLine($fp, 1000);
+               }while($line[0]!="A");
+       
+               $result = (iil_ParseResult($line)==0);
+               if (!$result) $conn->error .= $line."\n";
+               return $result;
+       
+       }else{
+               $conn->error .= "Couldn't send command \"$request\"\n";
+               return false;
+       }
+}
+
+
+function iil_C_AppendFromFile(&$conn, $folder, $path){
+       if (!$folder) return false;
+       
+       //open message file
+       $in_fp = false;                         
+       if (file_exists(realpath($path))) $in_fp = fopen($path, "r");
+       if (!$in_fp){ 
+               $conn->error .= "Couldn't open $path for reading\n";
+               return false;
+       }
+       
+       $fp = $conn->fp;
+       $len = filesize($path);
+       if (!$len) return false;
+       
+       //send APPEND command
+       $request="A APPEND \"".$folder."\" (\\Seen) {".$len."}\r\n";
+       $bytes_sent = 0;
+       if (fputs($fp, $request)){
+               $line=iil_ReadLine($fp, 100);
+                               
+               //send file
+               while(!feof($in_fp)){
+                       $buffer = fgets($in_fp, 4096);
+                       $bytes_sent += strlen($buffer);
+                       fputs($fp, $buffer);
+               }
+               fclose($in_fp);
+
+               fputs($fp, "\r\n");
+
+               //read response
+               do{
+                       $line=iil_ReadLine($fp, 1000);
+               }while($line[0]!="A");
+                       
+               $result = (iil_ParseResult($line)==0);
+               if (!$result) $conn->error .= $line."\n";
+               return $result;
+       
+       }else{
+               $conn->error .= "Couldn't send command \"$request\"\n";
+               return false;
+       }
+}
+
+
+function iil_C_FetchStructureString(&$conn, $folder, $id){
+       $fp = $conn->fp;
+       $result=false;
+       if (iil_C_Select($conn, $folder)){
+               $key = "F1247";
+               if (fputs($fp, "$key FETCH $id (BODYSTRUCTURE)\r\n")){
+                       do{
+                               $line=chop(iil_ReadLine($fp, 5000));
+                               if ($line[0]=="*"){
+                                       if (ereg("\}$", $line)){
+                                               preg_match('/(.+)\{([0-9]+)\}/', $line, $match);  
+                                               $result = $match[1];
+                                               do{
+                                                       $line = chop(iil_ReadLine($fp, 100));
+                                                       if (!preg_match("/^$key/", $line)) $result .= $line;
+                                                       else $done = true;
+                                               }while(!$done);
+                                       }else{
+                                               $result = $line;
+                                       }
+                                       list($pre, $post) = explode("BODYSTRUCTURE ", $result);
+                                       $result = substr($post, 0, strlen($post)-1);            //truncate last ')' and return
+                               }
+                       }while (!preg_match("/^$key/",$line));
+               }
+       }
+       return $result;
+}
+
+function iil_C_PrintSource(&$conn, $folder, $id, $part){
+       $header = iil_C_FetchPartHeader($conn, $folder, $id, $part);
+       //echo str_replace("\r", "", $header);
+       echo $header;
+       echo iil_C_PrintPartBody($conn, $folder, $id, $part);
+}
+
+function iil_C_GetQuota(&$conn){
+/*
+b GETQUOTAROOT "INBOX"
+* QUOTAROOT INBOX user/rchijiiwa1
+* QUOTA user/rchijiiwa1 (STORAGE 654 9765)
+b OK Completed
+*/
+       $fp = $conn->fp;
+       $result=false;
+       $quota_line = "";
+       
+       //get line containing quota info
+       if (fputs($fp, "QUOT1 GETQUOTAROOT \"INBOX\"\r\n")){
+               do{
+                       $line=chop(iil_ReadLine($fp, 5000));
+                       if (iil_StartsWith($line, "* QUOTA ")) $quota_line = $line;
+               }while(!iil_StartsWith($line, "QUOT1"));
+       }
+       
+       //return false if not found, parse if found
+       if (!empty($quota_line)){
+               $quota_line = eregi_replace("[()]", "", $quota_line);
+               $parts = explode(" ", $quota_line);
+               $storage_part = array_search("STORAGE", $parts);
+               if ($storage_part>0){
+                       $result = array();
+                       $used = $parts[$storage_part+1];
+                       $total = $parts[$storage_part+2];
+                       $result["used"] = $used;
+                       $result["total"] = (empty($total)?"??":$total);
+                       $result["percent"] = (empty($total)?"??":round(($used/$total)*100));
+                       $result["free"] = 100 - $result["percent"];
+               }
+       }
+       
+       return $result;
+}
+
+
+function iil_C_ClearFolder(&$conn, $folder){
+       $num_in_trash = iil_C_CountMessages($conn, $folder);
+       if ($num_in_trash > 0) iil_C_Delete($conn, $folder, "1:".$num_in_trash);
+       return (iil_C_Expunge($conn, $folder) >= 0);
+}
+
+?>
diff --git a/program/lib/mime.inc b/program/lib/mime.inc
new file mode 100644 (file)
index 0000000..ad6561e
--- /dev/null
@@ -0,0 +1,322 @@
+<?php
+/////////////////////////////////////////////////////////
+//     
+//     Iloha MIME Library (IML)
+//
+//     (C)Copyright 2002 Ryo Chijiiwa <Ryo@IlohaMail.org>
+//
+//     This file is part of IlohaMail. IlohaMail is free software released 
+//     under the GPL license.  See enclosed file COPYING for details, or 
+//     see http://www.fsf.org/copyleft/gpl.html
+//
+/////////////////////////////////////////////////////////
+
+/********************************************************
+
+       FILE: include/mime.inc
+       PURPOSE:
+               Provide functions for handling mime messages.
+       USAGE:
+               Use iil_C_FetchStructureString to get IMAP structure stirng, then pass that through
+               iml_GetRawStructureArray() to get root node to a nested data structure.
+               Pass root node to the iml_GetPart*() functions to retreive individual bits of info.
+
+********************************************************/
+$MIME_INVALID = -1;
+$MIME_TEXT = 0;
+$MIME_MULTIPART = 1;
+$MIME_MESSAGE = 2;
+$MIME_APPLICATION = 3;
+$MIME_AUDIO = 4;
+$MIME_IMAGE = 5;
+$MIME_VIDEO = 6;
+$MIME_OTHER = 7;
+
+function iml_ClosingParenPos($str, $start){
+    $level=0;
+    $len = strlen($str);
+    $in_quote = 0;
+    for ($i=$start;$i<$len;$i++){
+       if ($str[$i]=="\"") $in_quote = ($in_quote + 1) % 2;
+       if (!$in_quote){
+               if ($str[$i]=="(") $level++;
+               else if (($level > 0) && ($str[$i]==")")) $level--;
+               else if (($level == 0) && ($str[$i]==")")) return $i;
+       }
+    }
+}
+
+function iml_ParseBSString($str){      
+    
+    $id = 0;
+    $a = array();
+    $len = strlen($str);
+    
+    $in_quote = 0;
+    for ($i=0; $i<$len; $i++){
+        if ($str[$i] == "\"") $in_quote = ($in_quote + 1) % 2;
+        else if (!$in_quote){
+            if ($str[$i] == " ") $id++; //space means new element
+            else if ($str[$i]=="("){ //new part
+                $i++;
+                $endPos = iml_ClosingParenPos($str, $i);
+                $partLen = $endPos - $i;
+                $part = substr($str, $i, $partLen);
+                $a[$id] = iml_ParseBSString($part); //send part string
+                if ($verbose){
+                                       echo "{>".$endPos."}";
+                                       flush();
+                               }
+                $i = $endPos;
+            }else $a[$id].=$str[$i]; //add to current element in array
+        }else if ($in_quote){
+            if ($str[$i]=="\\") $i++; //escape backslashes
+            else $a[$id].=$str[$i]; //add to current element in array
+        }
+    }
+        
+    reset($a);
+    return $a;
+}
+
+function iml_GetRawStructureArray($str){
+    $line=substr($str, 1, strlen($str) - 2);
+    $line = str_replace(")(", ") (", $line);
+       
+       $struct = iml_ParseBSString($line);
+       if ((strcasecmp($struct[0], "message")==0) && (strcasecmp($struct[1], "rfc822")==0)){
+               $struct = array($struct);
+       }
+    return $struct;
+}
+
+function iml_GetPartArray($a, $part){
+       if (!is_array($a)) return false;
+       if (strpos($part, ".") > 0){
+               $original_part = $part;
+               $pos = strpos($part, ".");
+               $rest = substr($original_part, $pos+1);
+               $part = substr($original_part, 0, $pos);
+               if ((strcasecmp($a[0], "message")==0) && (strcasecmp($a[1], "rfc822")==0)){
+                       $a = $a[8];
+               }
+               //echo "m - part: $original_part current: $part rest: $rest array: ".implode(" ", $a)."<br>\n";
+               return iml_GetPartArray($a[$part-1], $rest);
+       }else if ($part>0){
+               if ((strcasecmp($a[0], "message")==0) && (strcasecmp($a[1], "rfc822")==0)){
+                       $a = $a[8];
+               }
+               //echo "s - part: $part rest: $rest array: ".implode(" ", $a)."<br>\n";
+               if (is_array($a[$part-1])) return $a[$part-1];
+               else return false;
+       }else if (($part==0) || (empty($part))){
+               return $a;
+       }
+}
+
+function iml_GetNumParts($a, $part){
+       if (is_array($a)){
+               $parent=iml_GetPartArray($a, $part);
+               
+               if ((strcasecmp($parent[0], "message")==0) && (strcasecmp($parent[1], "rfc822")==0)){
+                       $parent = $parent[8];
+               }
+
+               $is_array=true;
+               $c=0;
+               while (( list ($key, $val) = each ($parent) )&&($is_array)){
+                       $is_array=is_array($parent[$key]);
+                       if ($is_array) $c++;
+               }
+               return $c;
+       }
+       
+       return false;
+}
+
+function iml_GetPartTypeString($a, $part){
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])){
+                       $type_str = "MULTIPART/";
+                       reset($part_a);
+                       while(list($n,$element)=each($part_a)){
+                               if (!is_array($part_a[$n])){
+                                       $type_str.=$part_a[$n];
+                                       break;
+                               }
+                       }
+                       return $type_str;
+               }else return $part_a[0]."/".$part_a[1];
+       }else return false;
+}
+
+function iml_GetFirstTextPart($structure,$part){
+    if ($part==0) $part="";
+    $typeCode = -1;
+    while ($typeCode!=0){
+        $typeCode = iml_GetPartTypeCode($structure, $part);
+        if ($typeCode == 1){
+            $part .= (empty($part)?"":".")."1";
+        }else if ($typeCode > 0){
+            $parts_a = explode(".", $part);
+            $lastPart = count($parts_a) - 1;
+            $parts_a[$lastPart] = (int)$parts_a[$lastPart] + 1;
+            $part = implode(".", $parts_a);
+        }else if ($typeCode == -1){
+            return "";
+        }
+    }
+    
+    return $part;
+}
+
+function iml_GetPartTypeCode($a, $part){
+       $types=array(0=>"text",1=>"multipart",2=>"message",3=>"application",4=>"audio",5=>"image",6=>"video",7=>"other");
+
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) $str="multipart";
+               else $str=$part_a[0];
+
+               $code=7;
+               while ( list($key, $val) = each($types)) if (strcasecmp($val, $str)==0) $code=$key;
+               return $code;
+       }else return -1;
+}
+
+function iml_GetPartEncodingCode($a, $part){
+       $encodings=array("7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "OTHER");
+
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) return -1;
+               else $str=$part_a[5];
+
+               $code=5;
+               while ( list($key, $val) = each($encodings)) if (strcasecmp($val, $str)==0) $code=$key;
+
+               return $code;
+
+       }else return -1;
+}
+
+function iml_GetPartEncodingString($a, $part){
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) return -1;
+               else return $part_a[5];
+       }else return -1;
+}
+
+function iml_GetPartSize($a, $part){
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) return -1;
+               else return $part_a[6];
+       }else return -1;
+}
+
+function iml_GetPartID($a, $part){
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) return -1;
+               else return $part_a[3];
+       }else return -1;
+}
+
+function iml_GetPartDisposition($a, $part){
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) return -1;
+               else{
+            $id = count($part_a) - 2;
+                       if (is_array($part_a[$id])) return $part_a[$id][0];
+                       else return "";
+               }
+       }else return "";
+}
+
+function iml_GetPartName($a, $part){
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) return -1;
+               else{
+            $name = "";
+                       if (is_array($part_a[2])){
+                //first look in content type
+                               $name="";
+                               while ( list($key, $val) = each ($part_a[2])){
+                    if ((strcasecmp($val, "NAME")==0)||(strcasecmp($val, "FILENAME")==0)) 
+                        $name=$part_a[2][$key+1];
+                }
+                       }
+            if (empty($name)){
+                //check in content disposition
+                $id = count($part_a) - 2;
+                if ((is_array($part_a[$id])) && (is_array($part_a[$id][1]))){
+                    $array = $part_a[$id][1];
+                    while ( list($key, $val) = each($array)){
+                        if ((strcasecmp($val, "NAME")==0)||(strcasecmp($val, "FILENAME")==0)) 
+                            $name=$array[$key+1];
+                    }
+                }
+            }
+                       return $name;
+               }
+       }else return "";
+}
+
+
+function iml_GetPartCharset($a, $part){
+       $part_a=iml_GetPartArray($a, $part);
+       if ($part_a){
+               if (is_array($part_a[0])) return -1;
+               else{
+                       if (is_array($part_a[2])){
+                               $name="";
+                               while ( list($key, $val) = each ($part_a[2])) if (strcasecmp($val, "charset")==0) $name=$part_a[2][$key+1];
+                               return $name;
+                       }
+                       else return "";
+               }
+       }else return "";
+}
+
+function iml_GetPartList($a, $part){
+       //echo "MOO?"; flush();
+       $data = array();
+       $num_parts = iml_GetNumParts($a, $part);
+       //echo "($num_parts)"; flush();
+       if ($num_parts !== false){
+               //echo "<!-- ($num_parts parts)//-->\n";
+               for ($i = 0; $i<$num_parts; $i++){
+                       $part_code = $part.(empty($part)?"":".").($i+1);
+                       $part_type = iml_GetPartTypeCode($a, $part_code);
+                       $part_disposition = iml_GetPartDisposition($a, $part_code);
+                       //echo "<!-- part: $part_code type: $part_type //-->\n";
+                       if (strcasecmp($part_disposition, "attachment")!=0 && 
+                               (($part_type == 1) || ($part_type==2))){
+                               $data = array_merge($data, iml_GetPartList($a, $part_code));
+                       }else{
+                               $data[$part_code]["typestring"] = iml_GetPartTypeString($a, $part_code);
+                               $data[$part_code]["disposition"] = $part_disposition;
+                               $data[$part_code]["size"] = iml_GetPartSize($a, $part_code);
+                               $data[$part_code]["name"] = iml_GetPartName($a, $part_code);
+                               $data[$part_code]["id"] = iml_GetPartID($a, $part_code);
+                       }
+               }
+       }
+       return $data;
+}
+
+function iml_GetNextPart($part){
+       if (strpos($part, ".")===false) return $part++;
+       else{
+               $parts_a = explode(".", $part);
+               $num_levels = count($parts_a);
+               $parts_a[$num_levels-1]++;
+               return implode(".", $parts_a);
+       }
+}
+?>
\ No newline at end of file
diff --git a/program/lib/utf7.inc b/program/lib/utf7.inc
new file mode 100644 (file)
index 0000000..a887958
--- /dev/null
@@ -0,0 +1,384 @@
+<?php
+//
+//
+//      utf7.inc - Routines to encode bytes to UTF7 and decode UTF7 strings
+//
+//      Copyright (C)  1999, 2002  Ziberex and Torben Rybner
+//
+//
+//      Version 1.01    2002-06-08      19:00
+//
+//      - Adapted for use in IlohaMail (modified UTF-7 decoding)
+//      - Converted from C to PHP4
+//
+//
+//      Version 1.00    1999-09-03      19:00
+//
+//      - Encodes bytes to UTF7 strings
+//          *OutString = '\0';
+//          StartBase64Encode();
+//          for (CP = InString;  *CP;  CP++)
+//            strcat(OutString, Base64Encode(*CP));
+//          strcat(OutString, StopBase64Encode());
+//      - Decodes Base64 strings to bytes
+//          StartBase64Decode();
+//          for (CP1 = InString, CP2 = OutString;  *CP1 && (*CP1 != '=');  CP1++)
+//            CP2 += Base64Decode(*CP1, CP2);
+//          StopBase64Decode();
+//
+
+$BASE64LENGTH              =  60;
+
+$BASE64DECODE_NO_DATA      = -1;
+$BASE64DECODE_EMPTY_DATA   = -2;
+$BASE64DECODE_INVALID_DATA = -3;
+
+
+//
+//
+//      Used for conversion to UTF7
+//
+$_ToUTF7 = array
+(
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', ','
+);
+
+//
+//
+//      Used for conversion from UTF7
+//      (0x80 => Illegal, 0x40 => CR/LF)
+//
+$_FromUTF7 = array
+(
+  0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,       // 00 - 07 - Ctrl -
+  0x80, 0x80, 0x40, 0x80, 0x80, 0x40, 0x80, 0x80,       // 08 - 0F - Ctrl -
+  0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,       // 10 - 17 - Ctrl -
+  0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,       // 18 - 1F - Ctrl -
+  0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,       // 20 - 27  !"#$%&'
+  0x80, 0x80, 0x80, 0x3E, 0x3F, 0x80, 0x80, 0x3F,       // 28 - 2F ()*+,-./
+  0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,       // 30 - 37 01234567
+  0x3C, 0x3D, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80,       // 38 - 3F 89:;<=>?
+  0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,       // 40 - 47 @ABCDEFG
+  0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,       // 48 - 4F HIJKLMNO
+  0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,       // 50 - 57 PQRSTUVW
+  0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,       // 58 - 5F XYZ[\]^_
+  0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,       // 60 - 67 `abcdefg
+  0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,       // 68 - 6F hijklmno
+  0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,       // 70 - 77 pqrstuvw
+  0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,       // 78 - 7F xyz{|}~
+);
+
+
+//
+//
+//      UTF7EncodeInit:
+//
+//      Start the encoding of bytes
+//
+function UTF7EncodeInit(&$Context)
+{
+  $Context[ "Data" ]  = "";
+  $Context[ "Count" ] = 0;
+  $Context[ "Pos" ]   = 0;
+  $Context[ "State" ] = 0;
+} // UTF7EncodeInit
+
+
+//
+//
+//      UTF7EncodeByte:
+//
+//      Encodes one byte to UTF7
+//
+function UTF7EncodeByte(&$Context, $Byte)
+{
+  global $_ToUTF7;
+
+  $Byte = ord($Byte);
+  switch ($Context[ "State" ])
+  {
+    case 0:
+      // Convert into a byte
+      $Context[ "Data" ] = $_ToUTF7[ $Byte >> 2 ];
+      $Context[ "Pos" ]++;
+      // Save residue for next converted byte
+      $Context[ "Residue" ] = ($Byte & 0x03) << 4;
+      // This is the first byte in this line
+      $Context[ "Count" ] = 1;
+      // Next state is 1
+      $Context[ "State" ] = 1;
+      break;
+    case 1:
+      // Convert into a byte
+      $Context[ "Data" ] .= $_ToUTF7[ $Context[ "Residue" ] | ($Byte >> 4) ];
+      $Context[ "Pos" ]++;
+      // Save residue for next converted byte
+      $Context[ "Residue" ] = ($Byte & 0x0F) << 2;
+      // Bumb byte counter
+      $Context[ "Count" ]++;
+      // Next state is 2
+      $Context[ "State" ] = 2;
+      break;
+    case 2:
+      // Convert into a byte
+      $Context[ "Data" ] .= $_ToUTF7[ $Context[ "Residue" ] | ($Byte >> 6) ];
+      $Context[ "Pos" ]++;
+      // Residue fits precisely into the next byte
+      $Context[ "Data" ] .= $_ToUTF7[ $Byte & 0x3F ];
+      $Context[ "Pos" ]++;
+      // Bumb byte counter
+      $Context[ "Count" ]++;
+      // Next state is 3
+      $Context[ "State" ] = 3;
+      break;
+
+    case 3:
+      // Convert into a byte
+      $Context[ "Data" ] .= $_ToUTF7[ $Byte >> 2 ];
+      $Context[ "Pos" ]++;
+      // Save residue for next converted byte
+      $Context[ "Residue" ] = ($Byte & 0x03) << 4;
+      // Bumb byte counter
+      $Context[ "Count" ]++;
+      // Next state is 1
+      $Context[ "State" ] = 1;
+      break;
+    default:
+      // printf("Internal error in UTF7Encode: State is %d\n", $Context[ "State" ]);
+      // exit(1);
+      break;
+  }
+} // UTF7EncodeByte
+
+
+//
+//
+//      UTF7EncodeFinal:
+//
+//      Terminates the encoding of bytes
+//
+function UTF7EncodeFinal(&$Context)
+{
+  if ($Context[ "State" ] == 0)
+    return "";
+  if ($Context[ "State" ] != 3)
+    UTF7EncodeByte($Context, "\0");
+  return $Context[ "Data" ];
+} // UTF7EncodeFinal
+
+
+//
+//
+//      UTF7EncodeString
+//
+//      Encodes a string to modified UTF-7 format
+//
+function UTF7EncodeString($String)
+{
+  // Not during encoding, yet
+  $Encoding = false;
+  // Go through the string
+  for ($I = 0;  $I < strlen($String);  $I++)
+  {
+    $Ch = substr($String, $I, 1);
+    if (ord($Ch) > 0x7F)
+    {
+      if (! $Encoding)
+      {
+        $RetVal .= "&";
+        $Encoding = true;
+        // Initialise UTF7 context
+        UTF7EncodeInit($Context);
+      }
+      UTF7EncodeByte($Context, "\0");
+      UTF7EncodeByte($Context, $Ch);
+    }
+    elseif ($Ch == "&")
+    {
+      if (! $Encoding)
+      {
+        $RetVal .= "&";
+        $Encoding = true;
+        // Initialise UTF7 context
+        UTF7EncodeInit($Context);
+      }
+      else
+      {
+        UTF7EncodeByte($Context, "\0");
+        UTF7EncodeByte($Context, $Ch);
+      }
+    }
+    else
+    {
+      if ($Encoding)
+      {
+        $RetVal .= UTF7EncodeFinal($Context) . "-$Ch";
+        $Encoding = false;
+      }
+      else
+        $RetVal .= $Ch;
+    }
+  }
+  if ($Encoding)
+    $RetVal .= UTF7EncodeFinal($Context) . "-";
+  return $RetVal;
+} // UTF7EncodeString
+
+
+//
+//
+//      UTF7DecodeInit:
+//
+//      Start the decoding of bytes
+//
+function UTF7DecodeInit(&$Context)
+{
+  $Context[ "Data" ]  = "";
+  $Context[ "State" ] = 0;
+  $Context[ "Pos" ]   = 0;
+} // UTF7DecodeInit
+
+
+//
+//
+//      UTF7DecodeByte:
+//
+//      Decodes one character from UTF7
+//
+function UTF7DecodeByte(&$Context, $Byte)
+{
+  global $BASE64DECODE_INVALID_DATA;
+  global $_FromUTF7;
+
+  // Restore bits
+  $Byte = $_FromUTF7[ ord($Byte) ];
+  // Ignore carriage returns and linefeeds
+  if ($Byte == 0x40)
+    return "";
+  // Invalid byte - Tell caller!
+  if ($Byte == 0x80)
+    $Context[ "Count" ] = $BASE64DECODE_INVALID_DATA;
+  switch ($Context[ "State" ])
+  {
+    case 0:
+      // Save residue
+      $Context[ "Residue" ] = $Byte;
+      // Initialise count
+      $Context[ "Count" ] = 0;
+      // Next state
+      $Context[ "State" ] = 1;
+      break;
+
+    case 1:
+      // Store byte
+      $Context[ "Data" ] .= chr(($Context[ "Residue" ] << 2) | ($Byte >> 4));
+      $Context[ "Pos" ]++;
+      // Update count
+      $Context[ "Count" ]++;
+      // Save residue
+      $Context[ "Residue" ] = $Byte;
+      // Next state
+      $Context[ "State" ] = 2;
+      break;
+
+    case 2:
+      // Store byte
+      $Context[ "Data" ] .= chr(($Context[ "Residue" ] << 4) | ($Byte >> 2));
+      $Context[ "Pos" ]++;
+      // Update count
+      $Context[ "Count" ]++;
+      // Save residue
+      $Context[ "Residue" ] = $Byte;
+      // Next state
+      $Context[ "State" ] = 3;
+      break;
+
+    case 3:
+      // Store byte
+      $Context[ "Data" ] .= chr(($Context[ "Residue" ] << 6) | $Byte);
+      $Context[ "Pos" ]++;
+      // Update count
+      $Context[ "Count" ]++;
+      // Next state
+      $Context[ "State" ] = 4;
+      break;
+
+    case 4:
+      // Save residue
+      $Context[ "Residue" ] = $Byte;
+      // Next state
+      $Context[ "State" ] = 1;
+      break;
+  }
+} // UTF7DecodeByte
+
+
+//
+//
+//      UTF7DecodeFinal:
+//
+//      Decodes one character from UTF7
+//
+function UTF7DecodeFinal(&$Context)
+{
+  // Buffer not empty - Return remainder!
+  if ($Context[ "Count" ])
+  {
+    $Context[ "Pos" ] = 0;
+    $Context[ "State" ] = 0;
+    return $Context[ "Data" ];
+  }
+  return "";
+} // UTF7DecodeFinal
+
+
+//
+//
+//      UTF7DecodeString
+//
+//      Converts a string encoded in modified UTF-7 encoding
+//      to ISO 8859-1.
+//      OBS: Works only for valid ISO 8859-1 characters in the
+//      encoded data
+//
+function UTF7DecodeString($String)
+{
+  $Decoding = false;
+  for ($I = 0;  $I < strlen($String);  $I++)
+  {
+    $Ch = substr($String, $I, 1);
+    if ($Decoding)
+    {
+      if ($Ch == "-")
+      {
+        $RetVal .= UTF7DecodeFinal($Context);
+        $Decoding = false;
+      }
+      else
+        UTF7DecodeByte($Context, $Ch);
+    }
+    elseif ($Ch == "&")
+    {
+      if (($I < strlen($String) - 1) && (substr($String, $I + 1, 1) == "-"))
+      {
+        $RetVal .= $Ch;
+        $I++;
+      }
+      else
+      {
+        UTF7DecodeInit($Context);
+        $Decoding = true;
+      }
+    }
+    else
+      $RetVal .= $Ch;
+  }
+  return str_replace("\0", "", $RetVal);
+} // UTF7DecodeString
+?>
diff --git a/program/lib/utf8.class.php b/program/lib/utf8.class.php
new file mode 100644 (file)
index 0000000..c0bd0a7
--- /dev/null
@@ -0,0 +1,174 @@
+<?php
+/*
+utf8 1.0
+Copyright: Left
+---------------------------------------------------------------------------------
+Version:        1.0
+Date:           23 November 2004
+---------------------------------------------------------------------------------
+Author:         Alexander Minkovsky (a_minkovsky@hotmail.com)
+---------------------------------------------------------------------------------
+License:        Choose the more appropriated for You - I don't care.
+---------------------------------------------------------------------------------
+Description:
+    Class provides functionality to convert single byte strings, such as CP1251
+    ti UTF-8 multibyte format and vice versa.
+    Class loads a concrete charset map, for example CP1251.
+    (Refer to ftp://ftp.unicode.org/Public/MAPPINGS/ for map files)
+    Directory containing MAP files is predefined as constant.
+    Each charset is also predefined as constant pointing to the MAP file.
+---------------------------------------------------------------------------------
+Example usage:
+    Pass the desired charset in the class constructor:
+    $utfConverter = new utf8(CP1251); //defaults to CP1250.
+    or load the charset MAP using loadCharset method like this:
+    $utfConverter->loadCharset(CP1252);
+    Then call
+    $res = $utfConverter->strToUtf8($str);
+    or
+    $res = $utfConverter->utf8ToStr($utf);
+    to get the needed encoding.
+---------------------------------------------------------------------------------
+Note:
+    Rewrite or Override the onError method if needed. It's the error handler used from everywhere and takes 2 parameters:
+    err_code and err_text. By default it just prints out a message about the error.
+*/
+
+// Charset maps
+// Adapted to fit RoundCube
+define("UTF8_MAP_DIR", "program/lib/encoding");
+$utf8_maps = array(
+  "CP1250" => UTF8_MAP_DIR . "/CP1250.map",
+  "CP1251" => UTF8_MAP_DIR . "/CP1251.map",
+  "CP1252" => UTF8_MAP_DIR . "/CP1252.map",
+  "CP1253" => UTF8_MAP_DIR . "/CP1253.map",
+  "CP1254" => UTF8_MAP_DIR . "/CP1254.map",
+  "CP1255" => UTF8_MAP_DIR . "/CP1255.map",
+  "CP1256" => UTF8_MAP_DIR . "/CP1256.map",
+  "CP1257" => UTF8_MAP_DIR . "/CP1257.map",
+  "CP1258" => UTF8_MAP_DIR . "/CP1258.map",
+  "ISO-8859-1" => UTF8_MAP_DIR . "/ISO-8859-1.map",
+  "ISO-8859-2" => UTF8_MAP_DIR . "/ISO-8859-2.map",
+  "ISO-8859-3" => UTF8_MAP_DIR . "/ISO-8859-3.map",
+  "ISO-8859-4" => UTF8_MAP_DIR . "/ISO-8859-4.map");
+
+//Error constants
+define("ERR_OPEN_MAP_FILE","ERR_OPEN_MAP_FILE");
+
+//Class definition
+Class utf8{
+
+  var $charset = "ISO-8859-1";
+  var $ascMap = array();
+  var $utfMap = array();
+
+  // made PHP5 capable by RoundCube
+  function __construct($charset="ISO-8859-1"){
+    $this->loadCharset($charset);
+  }
+  
+  //Constructor
+  function utf8($charset="ISO-8859-1"){
+    $this->__construct($charset);
+  }
+
+  //Load charset
+  function loadCharset($charset){
+    global $utf8_maps;
+
+    if (!is_file($utf8_maps[$charset]))
+      {
+      $this->onError(ERR_OPEN_MAP_FILE, "Failed to open map file for $charset");
+      return;
+      }
+    
+    if (empty($this->ascMap[$charset]))
+      {
+      $lines = file_get_contents($utf8_maps[$charset]);
+      $lines = preg_replace("/#.*$/m","",$lines);
+      $lines = preg_replace("/\n\n/","",$lines);
+      $lines = explode("\n",$lines);
+      foreach($lines as $line){
+        $parts = explode('0x',$line);
+        if(count($parts)==3){
+          $asc=hexdec(substr($parts[1],0,2));
+          $utf=hexdec(substr($parts[2],0,4));
+          $this->ascMap[$charset][$asc]=$utf;
+        }
+      }
+    }
+    
+    $this->charset = $charset;
+    $this->utfMap = array_flip($this->ascMap[$charset]);
+  }
+
+  //Error handler
+  function onError($err_code,$err_text){
+    //print($err_code . " : " . $err_text . "<hr>\n");
+    raise_error(array('code' => 500,
+                      'file' => __FILE__,
+                      'message' => $err_text), TRUE, FALSE);
+  }
+
+  //Translate string ($str) to UTF-8 from given charset
+  function strToUtf8($str){
+    $chars = unpack('C*', $str);
+    $cnt = count($chars);
+    for($i=1;$i<=$cnt;$i++) $this->_charToUtf8($chars[$i]);
+    return implode("",$chars);
+  }
+
+  //Translate UTF-8 string to single byte string in the given charset
+  function utf8ToStr($utf){
+    $chars = unpack('C*', $utf);
+    $cnt = count($chars);
+    $res = ""; //No simple way to do it in place... concatenate char by char
+    for ($i=1;$i<=$cnt;$i++){
+      $res .= $this->_utf8ToChar($chars, $i);
+    }
+    return $res;
+  }
+
+  //Char to UTF-8 sequence
+  function _charToUtf8(&$char){
+    $c = (int)$this->ascMap[$this->charset][$char];
+    if ($c < 0x80){
+      $char = chr($c);
+    }
+    else if($c<0x800) // 2 bytes
+      $char = (chr(0xC0 | $c>>6) . chr(0x80 | $c & 0x3F));
+    else if($c<0x10000) // 3 bytes
+      $char = (chr(0xE0 | $c>>12) . chr(0x80 | $c>>6 & 0x3F) . chr(0x80 | $c & 0x3F));
+    else if($c<0x200000) // 4 bytes
+      $char = (chr(0xF0 | $c>>18) . chr(0x80 | $c>>12 & 0x3F) . chr(0x80 | $c>>6 & 0x3F) . chr(0x80 | $c & 0x3F));
+  }
+
+  //UTF-8 sequence to single byte character
+  function _utf8ToChar(&$chars, &$idx){
+    if(($chars[$idx] >= 240) && ($chars[$idx] <= 255)){ // 4 bytes
+      $utf =    (intval($chars[$idx]-240)   << 18) +
+                (intval($chars[++$idx]-128) << 12) +
+                (intval($chars[++$idx]-128) << 6) +
+                (intval($chars[++$idx]-128) << 0);
+    }
+    else if (($chars[$idx] >= 224) && ($chars[$idx] <= 239)){ // 3 bytes
+      $utf =    (intval($chars[$idx]-224)   << 12) +
+                (intval($chars[++$idx]-128) << 6) +
+                (intval($chars[++$idx]-128) << 0);
+    }
+    else if (($chars[$idx] >= 192) && ($chars[$idx] <= 223)){ // 2 bytes
+      $utf =    (intval($chars[$idx]-192)   << 6) +
+                (intval($chars[++$idx]-128) << 0);
+    }
+    else{ // 1 byte
+      $utf = $chars[$idx];
+    }
+    if(array_key_exists($utf,$this->utfMap))
+      return chr($this->utfMap[$utf]);
+    else
+      return "?";
+  }
+
+}
+
+?>
\ No newline at end of file
diff --git a/program/localization/de_CH/labels.inc b/program/localization/de_CH/labels.inc
new file mode 100644 (file)
index 0000000..49069d6
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/de_CH/labels.inc                                             |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author:      Thomas Bruederli <roundcube@gmail.com>                   |
+ | Corrections: Alexander Stiebing <ja.stiebing[NOSPAM]@web.de>          |
+ +-----------------------------------------------------------------------+
+
+ $Id: labels.inc 282 2006-07-25 22:11:50Z thomasb $
+
+*/
+
+$labels = array();
+
+// login page // Login-Seite
+$labels['welcome']   = 'Willkommen bei $product';
+$labels['username']  = 'Benutzername';
+$labels['password']  = 'Passwort';
+$labels['server']    = 'Server';
+$labels['login']     = 'Login';
+
+// taskbar // Aktionsleiste
+$labels['logout']   = 'Logout';
+$labels['mail']     = 'E-Mail';
+$labels['settings'] = 'Einstellungen';
+$labels['addressbook'] = 'Adressbuch';
+
+// mailbox names // E-Mail-Ordnernamen
+$labels['inbox']  = 'Posteingang';
+$labels['drafts'] = 'Entwürfe';
+$labels['sent']   = 'Gesendet';
+$labels['trash']  = 'Gelöscht';
+$labels['junk']   = 'Junk';
+
+// message listing // Nachrichtenliste
+$labels['subject'] = 'Betreff';
+$labels['from']    = 'Absender';
+$labels['to']      = 'Empfänger';
+$labels['cc']      = 'Kopie (CC)';
+$labels['bcc']     = 'Blind-Kopie';
+$labels['replyto'] = 'Antwort an';
+$labels['date']    = 'Datum';
+$labels['size']    = 'Grösse';
+$labels['priority'] = 'Priorität';
+$labels['organization'] = 'Organisation';
+
+// aliases // [Platzhalter]
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Ordner';
+$labels['messagesfromto'] = 'Nachrichten $from bis $to von $count';
+$labels['messagenrof'] = 'Nachricht $nr von $count';
+
+$labels['moveto']   = 'Verschieben nach...';
+$labels['download'] = 'Download';
+
+$labels['filename'] = 'Dateiname';
+$labels['filesize'] = 'Dateigrösse';
+
+$labels['preferhtml'] = 'HTML bevorzugen';
+$labels['htmlmessage'] = 'HTML Nachricht';
+$labels['prettydate'] = 'Kurze Datumsanzeige';
+
+$labels['addtoaddressbook'] = 'Ins Adressbuch übernehmen';
+
+// weekdays short // Wochentage (Abkürzungen) 
+$labels['sun'] = 'So';
+$labels['mon'] = 'Mo';
+$labels['tue'] = 'Di';
+$labels['wed'] = 'Mi';
+$labels['thu'] = 'Do';
+$labels['fri'] = 'Fr';
+$labels['sat'] = 'Sa';
+
+// weekdays long // Wochentage (normal)
+$labels['sunday']    = 'Sonntag';
+$labels['monday']    = 'Montag';
+$labels['tuesday']   = 'Dienstag';
+$labels['wednesday'] = 'Mittwoch';
+$labels['thursday']  = 'Donnerstag';
+$labels['friday']    = 'Freitag';
+$labels['saturday']  = 'Samstag';
+
+$labels['today'] = 'Heute';
+
+// toolbar buttons // Symbolleisten-Tipps
+$labels['checkmail']        = 'Überprüfung auf neue Anzeigen';
+$labels['writenewmessage']  = 'Neue Nachricht schreiben';
+$labels['replytomessage']   = 'Antwort verfassen';
+$labels['replytoallmessage'] = 'Antwort an Absender und alle Empfänger';
+$labels['forwardmessage']   = 'Nachricht weiterleiten';
+$labels['deletemessage']    = 'In den Papierkorb verschieben';
+$labels['printmessage']     = 'Diese Nachricht drucken';
+$labels['previousmessages'] = 'Vorherige Nachrichten anzeigen';
+$labels['nextmessages']     = 'Weitere Nachrichten anzeigen';
+$labels['backtolist']       = 'Zurück zur Liste';
+
+$labels['select'] = 'Auswählen';
+$labels['all']    = 'Alle';
+$labels['none']   = 'Keine';
+$labels['unread'] = 'Ungelesene';
+
+$labels['compact'] = 'Packen';
+$labels['empty'] = 'Leeren';
+$labels['purge'] = 'Aufräumen';
+
+$labels['quota'] = 'Verwendeter Speicherplatz';
+$labels['unknown']  = 'unbekannt';
+$labels['unlimited']  = 'unlimitiert';
+
+$labels['quicksearch']  = 'Schnellsuche';
+$labels['resetsearch']  = 'Löschen';
+
+
+// message compose // Nachrichten erstellen
+$labels['compose']  = 'Neue Nachricht verfassen';
+$labels['savemessage']  = 'Nachricht speichern';
+$labels['sendmessage']  = 'Nachricht jetzt senden';
+$labels['addattachment']  = 'Datei anfügen';
+$labels['charset']  = 'Zeichensatz';
+$labels['returnreceipt'] = 'Empfangsbestätigung';
+
+$labels['checkspelling'] = 'Rechtschreibung prüfen';
+$labels['resumeediting'] = 'Bearbeitung fortzetzen';
+$labels['revertto'] = 'Zurück zu';
+
+$labels['attachments'] = 'Anhänge';
+$labels['upload'] = 'Hochladen';
+$labels['close']  = 'Schliessen';
+
+$labels['low']     = 'Niedrig';
+$labels['lowest']  = 'Niedrigste';
+$labels['normal']  = 'Normal';
+$labels['high']    = 'Hoch';
+$labels['highest'] = 'Höchste';
+
+$labels['nosubject']  = '(kein Betreff)';
+$labels['showimages'] = 'Bilder anzeigen';
+
+
+// address book // Adressbuch
+$labels['name']      = 'Anzeigename';
+$labels['firstname'] = 'Vorname';
+$labels['surname']   = 'Nachname';
+$labels['email']     = 'E-Mail';
+
+$labels['addcontact'] = 'Kontakt hinzufügen';
+$labels['editcontact'] = 'Kontakt bearbeiten';
+
+$labels['edit']   = 'Bearbeiten';
+$labels['cancel'] = 'Abbrechen';
+$labels['save']   = 'Speichern';
+$labels['delete'] = 'Löschen';
+
+$labels['newcontact']     = 'Neuen Kontakt erfassen';
+$labels['deletecontact']  = 'Gewählte Kontakte löschen';
+$labels['composeto']      = 'Nachricht verfassen';
+$labels['contactsfromto'] = 'Kontakte $from bis $to von $count';
+$labels['print']          = 'Drucken';
+$labels['export']         = 'Exportieren';
+
+$labels['previouspage']   = 'Eine Seite zurück';
+$labels['nextpage']       = 'Nächste Seite';
+
+// LDAP search
+$labels['ldapsearch'] = 'LDAP Verzeichnis-Suche';
+
+$labels['ldappublicsearchname']   = 'Kontakt-Name';
+$labels['ldappublicsearchtype']   = 'Genaue Übereinstimmung';
+$labels['ldappublicserverselect'] = 'Server-Auswahl';
+$labels['ldappublicsearchfield']  = 'Suche in';
+$labels['ldappublicsearchform']   = 'Adressen suchen';
+$labels['ldappublicsearch'] = 'Suchen';
+
+// settings // Einstellungen
+$labels['settingsfor']  = 'Einstellungen für';
+
+$labels['preferences']  = 'Einstellungen';
+$labels['userpreferences']  = 'Benutzereinstellungen';
+$labels['editpreferences']  = 'Einstellungen bearbeiten';
+
+$labels['identities']  = 'Absender';
+$labels['manageidentities']  = 'Absender für dieses Konto verwalten';
+$labels['newidentity']  = 'Neuer Absender';
+
+$labels['newitem']  = 'Neuer Eintrag';
+$labels['edititem']  = 'Eintrag bearbeiten';
+
+$labels['setdefault']  = 'Als Standard';
+$labels['language']  = 'Sprache';
+$labels['timezone']  = 'Zeitzone';
+$labels['pagesize']  = 'Einträge pro Seite';
+$labels['signature'] = 'Signatur';
+$labels['dstactive']  = 'Sommerzeit';
+
+$labels['folder']  = 'Ordner';
+$labels['folders']  = 'Ordner';
+$labels['foldername']  = 'Ordnername';
+$labels['subscribed']  = 'Abonniert';
+$labels['create']      = 'Erstellen';
+$labels['createfolder']  = 'Neuen Ordner erstellen';
+$labels['rename'] = 'Umbenennen';
+$labels['renamefolder'] = 'Ordner umbenennen';
+$labels['deletefolder']  = 'Ordner löschen';
+$labels['managefolders']  = 'Ordner verwalten';
+
+$labels['sortby'] = 'Sortieren nach';
+$labels['sortasc']  = 'aufsteigend sortieren';
+$labels['sortdesc'] = 'absteigend sortieren';
+
+?>
\ No newline at end of file
diff --git a/program/localization/de_CH/messages.inc b/program/localization/de_CH/messages.inc
new file mode 100644 (file)
index 0000000..89bf714
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/de_CH/messages.inc                                           |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: messages.inc 285 2006-07-30 19:38:06Z thomasb $
+
+*/
+
+$messages = array();
+
+$messages['loginfailed']  = 'Login fehlgeschlagen';
+
+$messages['cookiesdisabled'] = 'Ihr Browser akzeptiert keine Cookies';
+
+$messages['sessionerror'] = 'Ihre Session ist ungültig oder abgelaufen';
+
+$messages['imaperror'] = 'Keine Verbindung zum IMAP Server';
+
+$messages['nomessagesfound'] = 'Keine Nachrichten in diesem Ordner';
+
+$messages['loggedout'] = 'Sie haben Ihre Session erfolgreich beendet. Auf Wiedersehen!';
+
+$messages['mailboxempty'] = 'Ordner ist leer';
+
+$messages['loading'] = $messages['loadingdata'] = 'Daten werden geladen...';
+
+$messages['checkingmail'] = 'Überprüfung auf neue Nachrichten...';
+
+$messages['sendingmessage'] = 'Nachricht wird gesendet...';
+
+$messages['messagesent'] = 'Nachricht erfolgreich gesendet';
+
+$messages['savingmessage'] = 'Nachricht wird gespeichert...';
+
+$messages['messagesaved'] = 'Nachricht als Entwurf gespeichert';
+
+$messages['successfullysaved'] = 'Erfolgreich gespeichert';
+
+$messages['addedsuccessfully'] = 'Kontakt zum Adressbuch hinzugefügt';
+
+$messages['contactexists'] = 'Es existiert bereits ein Eintrag mit dieser E-Mail-Adresse';
+
+$messages['blockedimages'] = 'Um Ihre Privatsphäre zur schützen, wurden externe Bilder blockiert.';
+
+$messages['encryptedmessage'] = 'Dies ist eine verschlüsselte Nachricht und kann leider nicht angezeigt werden.';
+
+$messages['nocontactsfound'] = 'Keine Kontakte gefunden';
+
+$messages['sendingfailed'] = 'Versand der Nachricht fehlgeschlagen';
+
+$messages['errorsaving'] = 'Beim Speichern ist ein Fehler aufgetreten';
+
+$messages['errormoving'] = 'Nachricht konnte nicht verschoben werden';
+
+$messages['errordeleting'] = 'Nachricht konnte nicht gelöscht werden';
+
+$messages['deletecontactconfirm']  = 'Wollen Sie die ausgewählten Kontakte wirklich löschen';
+
+$messages['deletefolderconfirm']  = 'Wollen Sie diesen Ordner wirklich löschen?';
+
+$messages['purgefolderconfirm']  = 'Wollen Sie diesen Ordner wirklich leeren?';
+
+$messages['formincomplete']    = 'Das Formular wurde nicht vollständig ausgefüllt';
+
+$messages['noemailwarning']    = 'Bitte geben Sie eine gültige E-Mail-Adresse ein';
+
+$messages['nonamewarning']     = 'Bitte geben Sie einen Namen ein';
+
+$messages['nopagesizewarning'] = 'Bitte geben Sie eine Einträge pro Seite ein';
+
+$messages['norecipientwarning'] = 'Bitte geben Sie mindestens einen Empfänger an';
+
+$messages['nosubjectwarning']  = 'Die Betreffzeile ist leer. Möchten Sie jetzt einen Betreff eingeben?';
+
+$messages['nobodywarning'] = 'Diese Nachricht ohne Inhalt senden?';
+
+$messages['notsentwarning'] = 'Ihre Nachricht wurde nicht gesendet. Wollen Sie die Nachricht verwerfen?';
+
+$messages['noldapserver'] = 'Bitte wählen Sie einen LDAP-Server aus';
+
+$messages['nocontactsreturned'] = 'Es wurden keine Kontakte gefunden';
+
+$messages['nosearchname'] = 'Bitte geben Sie einen Namen oder eine E-Mail-Adresse ein';
+
+$messages['searchsuccessful'] = '$nr Nachrichten gefunden';
+
+$messages['searchnomatch'] = 'Keine Treffer';
+
+$messages['searching'] = 'Suche...';
+
+$messages['checking'] = 'Prüfe...';
+
+$messages['nospellerrors'] = 'Keine Rechtschreibfehler gefunden';
+
+$messages['folderdeleted'] = 'Ordner erfolgreich gelöscht';
+
+
+?>
\ No newline at end of file
diff --git a/program/localization/de_DE/labels.inc b/program/localization/de_DE/labels.inc
new file mode 100644 (file)
index 0000000..9865ab2
--- /dev/null
@@ -0,0 +1,219 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/de_DE/labels.inc                                             |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author:      Thomas Bruederli <roundcube@gmail.com>                   |
+ +-----------------------------------------------------------------------+
+ | de_DE translation: Stephan Diehl <info@sd-edv.de>                     |
+ +-----------------------------------------------------------------------+
+
+ $Id: labels.inc 282 2006-07-25 22:11:50Z thomasb $
+
+*/
+
+$labels = array();
+
+// login page // Login-Seite
+$labels['welcome']   = 'Willkommen bei $product';
+$labels['username']  = 'Benutzername';
+$labels['password']  = 'Passwort';
+$labels['server']    = 'Server';
+$labels['login']     = 'Anmelden';
+
+// taskbar // Aktionsleiste
+$labels['logout']   = 'Abmelden';
+$labels['mail']     = 'E-Mail';
+$labels['settings'] = 'Einstellungen';
+$labels['addressbook'] = 'Adressbuch';
+
+// mailbox names // E-Mail-Ordnernamen
+$labels['inbox']  = 'Posteingang';
+$labels['drafts'] = 'Entwürfe';
+$labels['sent']   = 'Gesendet';
+$labels['trash']  = 'Gelöscht';
+$labels['junk']   = 'Spam';
+
+// message listing // Nachrichtenliste
+$labels['subject'] = 'Betreff';
+$labels['from']    = 'Absender';
+$labels['to']      = 'Empfänger';
+$labels['cc']      = 'Kopie (CC)';
+$labels['bcc']     = 'Blind-Kopie';
+$labels['replyto'] = 'Antwort an';
+$labels['date']    = 'Datum';
+$labels['size']    = 'Größe';
+$labels['priority'] = 'Priorität';
+$labels['organization'] = 'Organisation';
+
+// aliases // [Platzhalter]
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Ordner';
+$labels['messagesfromto'] = 'Nachrichten $from bis $to von $count';
+$labels['messagenrof'] = 'Nachricht $nr von $count';
+
+$labels['moveto']   = 'Verschieben nach...';
+$labels['download'] = 'Download';
+
+$labels['filename'] = 'Dateiname';
+$labels['filesize'] = 'Dateigröße';
+
+$labels['preferhtml'] = 'HTML bevorzugen';
+$labels['htmlmessage'] = 'HTML Nachricht';
+$labels['prettydate'] = 'Kurze Datumsanzeige';
+
+$labels['addtoaddressbook'] = 'Ins Adressbuch übernehmen';
+
+// weekdays short // Wochentage (Abkürzungen) 
+$labels['sun'] = 'So';
+$labels['mon'] = 'Mo';
+$labels['tue'] = 'Di';
+$labels['wed'] = 'Mi';
+$labels['thu'] = 'Do';
+$labels['fri'] = 'Fr';
+$labels['sat'] = 'Sa';
+
+// weekdays long // Wochentage (normal)
+$labels['sunday']    = 'Sonntag';
+$labels['monday']    = 'Montag';
+$labels['tuesday']   = 'Dienstag';
+$labels['wednesday'] = 'Mittwoch';
+$labels['thursday']  = 'Donnerstag';
+$labels['friday']    = 'Freitag';
+$labels['saturday']  = 'Samstag';
+
+$labels['today'] = 'Heute';
+
+// toolbar buttons // Symbolleisten-Tipps
+$labels['checkmail']        = 'Überprüfung auf neue Anzeigen';
+$labels['writenewmessage']  = 'Neue Nachricht schreiben';
+$labels['replytomessage']   = 'Antwort verfassen';
+$labels['replytoallmessage'] = 'Antwort an Absender und alle Empfänger';
+$labels['forwardmessage']   = 'Nachricht weiterleiten';
+$labels['deletemessage']    = 'In den Papierkorb verschieben';
+$labels['printmessage']     = 'Diese Nachricht drucken';
+$labels['previousmessages'] = 'Vorherige Nachrichten anzeigen';
+$labels['nextmessages']     = 'Weitere Nachrichten anzeigen';
+$labels['backtolist']       = 'Zurück zur Liste';
+
+$labels['select'] = 'Auswählen';
+$labels['all']    = 'Alle';
+$labels['none']   = 'Keine';
+$labels['unread'] = 'Ungelesene';
+
+$labels['compact'] = 'Packen';
+$labels['empty'] = 'Leeren';
+$labels['purge'] = 'Bereinigen';
+
+$labels['quota'] = 'Verwendeter Speicherplatz';
+$labels['unknown']  = 'unbekannt';
+$labels['unlimited']  = 'unlimitiert';
+
+$labels['quicksearch']  = 'Schnellsuche';
+$labels['resetsearch']  = 'Löschen';
+
+
+// message compose // Nachrichten erstellen
+$labels['compose']  = 'Neue Nachricht verfassen';
+$labels['savemessage']  = 'Nachricht speichern';
+$labels['sendmessage']  = 'Nachricht jetzt senden';
+$labels['addattachment']  = 'Datei anfügen';
+$labels['charset']  = 'Zeichensatz';
+$labels['returnreceipt'] = 'Empfangsbestätigung';
+
+$labels['checkspelling'] = 'Rechtschreibung prüfen';
+$labels['resumeediting'] = 'Bearbeitung fortzetzen';
+$labels['revertto'] = 'Zurück zu';
+
+$labels['attachments'] = 'Anhänge';
+$labels['upload'] = 'Hochladen';
+$labels['close']  = 'Schließen';
+
+$labels['low']     = 'Niedrig';
+$labels['lowest']  = 'Niedrigste';
+$labels['normal']  = 'Normal';
+$labels['high']    = 'Hoch';
+$labels['highest'] = 'Höchste';
+
+$labels['nosubject']  = '(kein Betreff)';
+$labels['showimages'] = 'Bilder anzeigen';
+
+
+// address book // Adressbuch
+$labels['name']      = 'Anzeigename';
+$labels['firstname'] = 'Vorname';
+$labels['surname']   = 'Nachname';
+$labels['email']     = 'E-Mail';
+
+$labels['addcontact'] = 'Kontakt hinzufügen';
+$labels['editcontact'] = 'Kontakt bearbeiten';
+
+$labels['edit']   = 'Bearbeiten';
+$labels['cancel'] = 'Abbrechen';
+$labels['save']   = 'Speichern';
+$labels['delete'] = 'Löschen';
+
+$labels['newcontact']     = 'Neuen Kontakt erfassen';
+$labels['deletecontact']  = 'Gewählte Kontakte löschen';
+$labels['composeto']      = 'Nachricht verfassen';
+$labels['contactsfromto'] = 'Kontakte $from bis $to von $count';
+$labels['print']          = 'Drucken';
+$labels['export']         = 'Exportieren';
+
+$labels['previouspage']   = 'Eine Seite zurück';
+$labels['nextpage']       = 'Nächste Seite';
+
+// LDAP search
+$labels['ldapsearch'] = 'LDAP Verzeichnis-Suche';
+
+$labels['ldappublicsearchname']   = 'Kontakt-Name';
+$labels['ldappublicsearchtype']   = 'Genaue Übereinstimmung';
+$labels['ldappublicserverselect'] = 'Server-Auswahl';
+$labels['ldappublicsearchfield']  = 'Suche in';
+$labels['ldappublicsearchform']   = 'Adressen suchen';
+$labels['ldappublicsearch'] = 'Suchen';
+
+// settings // Einstellungen
+$labels['settingsfor']  = 'Einstellungen für';
+
+$labels['preferences']  = 'Einstellungen';
+$labels['userpreferences']  = 'Benutzereinstellungen';
+$labels['editpreferences']  = 'Einstellungen bearbeiten';
+
+$labels['identities']  = 'Absender';
+$labels['manageidentities']  = 'Absender für dieses Konto verwalten';
+$labels['newidentity']  = 'Neuer Absender';
+
+$labels['newitem']  = 'Neuer Eintrag';
+$labels['edititem']  = 'Eintrag bearbeiten';
+
+$labels['setdefault']  = 'Als Standard';
+$labels['language']  = 'Sprache';
+$labels['timezone']  = 'Zeitzone';
+$labels['pagesize']  = 'Einträge pro Seite';
+$labels['signature'] = 'Signatur';
+$labels['dstactive']  = 'Sommerzeit';
+
+$labels['folder']  = 'Ordner';
+$labels['folders']  = 'Ordner';
+$labels['foldername']  = 'Ordnername';
+$labels['subscribed']  = 'Abonniert';
+$labels['create']      = 'Erstellen';
+$labels['createfolder']  = 'Neuen Ordner erstellen';
+$labels['rename'] = 'Umbenennen';
+$labels['renamefolder'] = 'Ordner umbenennen';
+$labels['deletefolder']  = 'Ordner löschen';
+$labels['managefolders']  = 'Ordner verwalten';
+
+$labels['sortby'] = 'Sortieren nach';
+$labels['sortasc']  = 'aufsteigend sortieren';
+$labels['sortdesc'] = 'absteigend sortieren';
+
+?>
diff --git a/program/localization/de_DE/messages.inc b/program/localization/de_DE/messages.inc
new file mode 100644 (file)
index 0000000..347d898
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/de_DE/messages.inc                                           |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+ | de_DE translation: Stephan Diehl <info@sd-edv.de>                     |
+ +-----------------------------------------------------------------------+
+
+ $Id: messages.inc 285 2006-07-30 19:38:06Z thomasb $
+
+*/
+
+$messages = array();
+
+$messages['loginfailed']  = 'Anmelden fehlgeschlagen';
+
+$messages['cookiesdisabled'] = 'Ihr Browser akzeptiert keine Cookies';
+
+$messages['sessionerror'] = 'Ihre Session ist ungültig oder abgelaufen';
+
+$messages['imaperror'] = 'Keine Verbindung zum IMAP Server';
+
+$messages['nomessagesfound'] = 'Keine Nachrichten in diesem Ordner';
+
+$messages['loggedout'] = 'Sie haben Ihre Session erfolgreich beendet. Auf Wiedersehen!';
+
+$messages['mailboxempty'] = 'Ordner ist leer';
+
+$messages['loading'] = $messages['loadingdata'] = 'Daten werden geladen...';
+
+$messages['checkingmail'] = 'Überprüfung auf neue Nachrichten...';
+
+$messages['sendingmessage'] = 'Nachricht wird gesendet...';
+
+$messages['messagesent'] = 'Nachricht erfolgreich gesendet';
+
+$messages['savingmessage'] = 'Nachricht wird gespeichert...';
+
+$messages['messagesaved'] = 'Nachricht als Entwurf gespeichert';
+
+$messages['successfullysaved'] = 'Erfolgreich gespeichert';
+
+$messages['addedsuccessfully'] = 'Kontakt zum Adressbuch hinzugefügt';
+
+$messages['contactexists'] = 'Es existiert bereits ein Eintrag mit dieser E-Mail-Adresse';
+
+$messages['blockedimages'] = 'Um Ihre Privatsphäre zur schützen, wurden externe Bilder blockiert.';
+
+$messages['encryptedmessage'] = 'Dies ist eine verschlüsselte Nachricht und kann leider nicht angezeigt werden.';
+
+$messages['nocontactsfound'] = 'Keine Kontakte gefunden';
+
+$messages['sendingfailed'] = 'Versand der Nachricht fehlgeschlagen';
+
+$messages['errorsaving'] = 'Beim Speichern ist ein Fehler aufgetreten';
+
+$messages['errormoving'] = 'Nachricht konnte nicht verschoben werden';
+
+$messages['errordeleting'] = 'Nachricht konnte nicht gelöscht werden';
+
+$messages['deletecontactconfirm']  = 'Wollen Sie die ausgewählten Kontakte wirklich löschen';
+
+$messages['deletefolderconfirm']  = 'Wollen Sie diesen Ordner wirklich löschen?';
+
+$messages['purgefolderconfirm']  = 'Wollen Sie diesen Ordner wirklich leeren?';
+
+$messages['formincomplete']    = 'Das Formular wurde nicht vollständig ausgefüllt';
+
+$messages['noemailwarning']    = 'Bitte geben Sie eine gültige E-Mail-Adresse ein';
+
+$messages['nonamewarning']     = 'Bitte geben Sie einen Namen ein';
+
+$messages['nopagesizewarning'] = 'Bitte geben Sie eine Einträge pro Seite ein';
+
+$messages['norecipientwarning'] = 'Bitte geben Sie mindestens einen Empfänger an';
+
+$messages['nosubjectwarning']  = 'Die Betreffzeile ist leer. Möchten Sie jetzt einen Betreff eingeben?';
+
+$messages['nobodywarning'] = 'Diese Nachricht ohne Inhalt senden?';
+
+$messages['notsentwarning'] = 'Ihre Nachricht wurde nicht gesendet. Wollen Sie die Nachricht verwerfen?';
+
+$messages['noldapserver'] = 'Bitte wählen Sie einen LDAP-Server aus';
+
+$messages['nocontactsreturned'] = 'Es wurden keine Kontakte gefunden';
+
+$messages['nosearchname'] = 'Bitte geben Sie einen Namen oder eine E-Mail-Adresse ein';
+
+$messages['searchsuccessful'] = '$nr Nachrichten gefunden';
+
+$messages['searchnomatch'] = 'Keine Treffer';
+
+$messages['searching'] = 'Suche...';
+
+$messages['checking'] = 'Prüfe...';
+
+$messages['nospellerrors'] = 'Keine Rechtschreibfehler gefunden';
+
+$messages['folderdeleted'] = 'Ordner erfolgreich gelöscht';
+
+?>
\ No newline at end of file
diff --git a/program/localization/en_GB/labels.inc b/program/localization/en_GB/labels.inc
new file mode 100644 (file)
index 0000000..9d4e76b
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/en_GB/labels.inc                                             |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Weiran Zhang (weiran@weiran.co.uk)                            |
+ +-----------------------------------------------------------------------+
+
+ $Id: labels.inc 296 2006-08-06 15:53:41Z thomasb $
+
+*/
+
+$labels = array();
+
+// login page
+$labels['welcome']   = 'Welcome to $product';
+$labels['username']  = 'Username';
+$labels['password']  = 'Password';
+$labels['server']    = 'Server';
+$labels['login']     = 'Login';
+
+// taskbar
+$labels['logout']   = 'Logout';
+$labels['mail']     = 'E-Mail';
+$labels['settings'] = 'Personal Settings';
+$labels['addressbook'] = 'Address Book';
+
+// mailbox names
+$labels['inbox']  = 'Inbox';
+$labels['drafts'] = 'Drafts';
+$labels['sent']   = 'Sent';
+$labels['trash']  = 'Deleted Items';
+$labels['drafts'] = 'Drafts';
+$labels['junk']   = 'Junk';
+
+// message listing
+$labels['subject'] = 'Subject';
+$labels['from']    = 'Sender';
+$labels['to']      = 'Recipient';
+$labels['cc']      = 'Copy';
+$labels['bcc']     = 'Bcc';
+$labels['replyto'] = 'Reply-To';
+$labels['date']    = 'Date';
+$labels['size']    = 'Size';
+$labels['priority'] = 'Priority';
+$labels['organization'] = 'Organisation';
+
+// aliases
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Folders';
+$labels['messagesfromto'] = 'Messages $from to $to of $count';
+$labels['messagenrof'] = 'Message $nr of $count';
+
+$labels['moveto']   = 'move to...';
+$labels['download'] = 'download';
+
+$labels['filename'] = 'File name';
+$labels['filesize'] = 'File size';
+
+$labels['preferhtml'] = 'Prefer HTML';
+$labels['htmlmessage'] = 'HTML Message';
+$labels['prettydate'] = 'Pretty dates';
+
+$labels['addtoaddressbook'] = 'Add to address book';
+
+// weekdays short
+$labels['sun'] = 'Sun';
+$labels['mon'] = 'Mon';
+$labels['tue'] = 'Tue';
+$labels['wed'] = 'Wed';
+$labels['thu'] = 'Thu';
+$labels['fri'] = 'Fri';
+$labels['sat'] = 'Sat';
+
+// weekdays long
+$labels['sunday']    = 'Sunday';
+$labels['monday']    = 'Monday';
+$labels['tuesday']   = 'Tuesday';
+$labels['wednesday'] = 'Wednesday';
+$labels['thursday']  = 'Thursday';
+$labels['friday']    = 'Friday';
+$labels['saturday']  = 'Saturday';
+
+$labels['today'] = 'Today';
+
+// toolbar buttons
+$labels['checkmail']        = 'Check for new messages';
+$labels['writenewmessage']  = 'Create a new message';
+$labels['replytomessage']   = 'Reply to the message';
+$labels['replytoallmessage'] = 'Reply to sender and all recipients';
+$labels['forwardmessage']   = 'Forward the message';
+$labels['deletemessage']    = 'Move message to trash';
+$labels['printmessage']     = 'Print this message';
+$labels['previousmessages'] = 'Show previous set of messages';
+$labels['nextmessages']     = 'Show next set of messages';
+$labels['backtolist']       = 'Back to message list';
+$labels['viewsource']       = 'Show source';
+
+$labels['select'] = 'Select';
+$labels['all'] = 'All';
+$labels['none'] = 'None';
+$labels['unread'] = 'Unread';
+
+$labels['compact'] = 'Compact';
+$labels['empty'] = 'Empty';
+$labels['purge'] = 'Purge';
+
+$labels['quota'] = 'Disk usage';
+
+
+// message compose
+$labels['compose']  = 'Compose a message';
+$labels['savemessage']  = 'Save this draft';
+$labels['sendmessage']  = 'Send the message now';
+$labels['addattachment']  = 'Attach a file';
+$labels['charset']  = 'Charset';
+
+$labels['attachments'] = 'Attachments';
+$labels['upload'] = 'Upload';
+$labels['close']  = 'Close';
+
+$labels['low']     = 'Low';
+$labels['lowest']  = 'Lowest';
+$labels['normal']  = 'Normal';
+$labels['high']    = 'High';
+$labels['highest'] = 'Highest';
+
+$labels['nosubject']  = '(no subject)';
+$labels['showimages'] = 'Display images';
+
+
+// address boook
+$labels['name']      = 'Display name';
+$labels['firstname'] = 'First name';
+$labels['surname']   = 'Last name';
+$labels['email']     = 'E-Mail';
+
+$labels['addcontact'] = 'Add new contact';
+$labels['editcontact'] = 'Edit contact';
+
+$labels['edit']   = 'Edit';
+$labels['cancel'] = 'Cancel';
+$labels['save']   = 'Save';
+$labels['delete'] = 'Delete';
+
+$labels['newcontact']     = 'Create new contact card';
+$labels['deletecontact']  = 'Delete selected contacts';
+$labels['composeto']      = 'Compose mail to';
+$labels['contactsfromto'] = 'Contacts $from to $to of $count';
+$labels['print']          = 'Print';
+$labels['export']         = 'Export';
+
+// LDAP search
+$labels['ldapsearch'] = 'LDAP directory search';
+
+$labels['ldappublicsearchname']    = 'Contact name';
+$labels['ldappublicsearchtype'] = 'Exact match?';
+$labels['ldappublicserverselect'] = 'Select servers';
+$labels['ldappublicsearchfield'] = 'Search on';
+$labels['ldappublicsearchform'] = 'Look for a contact';
+$labels['ldappublicsearch'] = 'Search';
+
+// settings
+$labels['settingsfor']  = 'Settings for';
+
+$labels['preferences']  = 'Preferences';
+$labels['userpreferences']  = 'User preferences';
+$labels['editpreferences']  = 'Edit user preferences';
+
+$labels['identities']  = 'Identities';
+$labels['manageidentities']  = 'Manage identities for this account';
+$labels['newidentity']  = 'New identity';
+
+$labels['newitem']  = 'New item';
+$labels['edititem']  = 'Edit item';
+
+$labels['setdefault']  = 'Set default';
+$labels['language']  = 'Language';
+$labels['timezone']  = 'Time zone';
+$labels['pagesize']  = 'Rows per page';
+$labels['signature'] = 'Signature';
+
+$labels['folder']  = 'Folder';
+$labels['folders']  = 'Folders';
+$labels['foldername']  = 'Folder name';
+$labels['subscribed']  = 'Subscribed';
+$labels['create']  = 'Create';
+$labels['createfolder']  = 'Create new folder';
+$labels['deletefolder']  = 'Delete folder';
+$labels['managefolders']  = 'Manage folders';
+
+$labels['sortby'] = 'Sort by';
+$labels['sortasc']  = 'Sort ascending';
+$labels['sortdesc'] = 'Sort descending';
+
+?>
diff --git a/program/localization/en_GB/messages.inc b/program/localization/en_GB/messages.inc
new file mode 100644 (file)
index 0000000..875df4f
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/en_GB/messages.inc                                           |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Weiran Zhang (weiran@weiran.co.uk)                            |
+ +-----------------------------------------------------------------------+
+
+ $Id: messages.inc 285 2006-07-30 19:38:06Z thomasb $
+
+*/
+
+$messages = array();
+
+$messages['loginfailed']  = 'Login failed';
+
+$messages['cookiesdisabled'] = 'Your browser does not accept cookies';
+
+$messages['sessionerror'] = 'Your session is invalid or expired';
+
+$messages['imaperror'] = 'Connection to IMAP server failed';
+
+$messages['nomessagesfound'] = 'No messages found in this mailbox';
+
+$messages['loggedout'] = 'You have successfully terminated the session. Good bye!';
+
+$messages['mailboxempty'] = 'Mailbox is empty';
+
+$messages['loading'] = 'Loading...';
+
+$messages['loadingdata'] = 'Loading data...';
+
+$messages['checkingmail'] = 'Checking for new messages...';
+
+$messages['messagesent'] = 'Message sent successfully';
+
+$messages['savingmessage'] = 'Saving message...';
+
+$messages['messagesaved'] = 'Message saved to Drafts';
+
+$messages['successfullysaved'] = 'Successfully saved';
+
+$messages['addedsuccessfully'] = 'Contact added successfully to address book';
+
+$messages['contactexists'] = 'A contact with this e-mail address already exists';
+
+$messages['blockedimages'] = 'To protect your privacy, remote images are blocked in this message.';
+
+$messages['encryptedmessage'] = 'This is an encrypted message and can not be displayed. Sorry!';
+
+$messages['nocontactsfound'] = 'No contacts found';
+
+$messages['sendingfailed'] = 'Failed to send message';
+
+$messages['errorsaving'] = 'An error occured while saving';
+
+$messages['errormoving'] = 'Could not move the message';
+
+$messages['errordeleting'] = 'Could not delete the message';
+
+$messages['deletecontactconfirm']  = 'Do you really want to delete the selected contact(s)?';
+
+$messages['deletefolderconfirm']  = 'Do you really want to delete this folder?';
+
+$messages['formincomplete'] = 'The form was not completely filled out';
+
+$messages['noemailwarning'] = 'Please enter a valid email address';
+
+$messages['nonamewarning']  = 'Please enter a name';
+
+$messages['nopagesizewarning'] = 'Please enter a page size';
+
+$messages['norecipientwarning'] = 'Please enter at least one recipient';
+
+$messages['nosubjectwarning']  = 'The "Subject" field is empty. Would you like to enter one now?';
+
+$messages['nobodywarning'] = 'Send this message without text?';
+
+$messages['notsentwarning'] = 'Message has not been sent. Do you want to discard your message?';
+
+$messages['noldapserver'] = 'Please select an ldap server to search';
+
+$messages['nocontactsreturned'] = 'No contacts were found';
+
+$messages['nosearchname'] = 'Please enter a contact name or email address';
+
+$messages['searchsuccessful'] = '$nr messages found';
+
+$messages['searchnomatch'] = 'Search returned no matches';
+
+$messages['searching'] = 'Searching...';
+
+$messages['checking'] = 'Checking...';
+
+$messages['nospellerrors'] = 'No spelling errors found';
+
+$messages['folderdeleted'] = 'Folder successfully deleted';
+
+
+?>
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
new file mode 100644 (file)
index 0000000..8f51769
--- /dev/null
@@ -0,0 +1,220 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/en_US/labels.inc                                             |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: labels.inc 296 2006-08-06 15:53:41Z thomasb $
+
+*/
+
+$labels = array();
+
+// login page
+$labels['welcome']   = 'Welcome to $product';
+$labels['username']  = 'Username';
+$labels['password']  = 'Password';
+$labels['server']    = 'Server';
+$labels['login']     = 'Login';
+
+// taskbar
+$labels['logout']   = 'Logout';
+$labels['mail']     = 'E-Mail';
+$labels['settings'] = 'Personal Settings';
+$labels['addressbook'] = 'Address Book';
+
+// mailbox names
+$labels['inbox']  = 'Inbox';
+$labels['drafts'] = 'Drafts';
+$labels['sent']   = 'Sent';
+$labels['trash']  = 'Trash';
+$labels['junk']   = 'Junk';
+
+// message listing
+$labels['subject'] = 'Subject';
+$labels['from']    = 'Sender';
+$labels['to']      = 'Recipient';
+$labels['cc']      = 'Copy';
+$labels['bcc']     = 'Bcc';
+$labels['replyto'] = 'Reply-To';
+$labels['date']    = 'Date';
+$labels['size']    = 'Size';
+$labels['priority'] = 'Priority';
+$labels['organization'] = 'Organization';
+
+// aliases
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Folders';
+$labels['messagesfromto'] = 'Messages $from to $to of $count';
+$labels['messagenrof'] = 'Message $nr of $count';
+
+$labels['moveto']   = 'move to...';
+$labels['download'] = 'download';
+
+$labels['filename'] = 'File name';
+$labels['filesize'] = 'File size';
+
+$labels['preferhtml'] = 'Prefer HTML';
+$labels['htmlmessage'] = 'HTML Message';
+$labels['prettydate'] = 'Pretty dates';
+
+$labels['addtoaddressbook'] = 'Add to address book';
+
+// weekdays short
+$labels['sun'] = 'Sun';
+$labels['mon'] = 'Mon';
+$labels['tue'] = 'Tue';
+$labels['wed'] = 'Wed';
+$labels['thu'] = 'Thu';
+$labels['fri'] = 'Fri';
+$labels['sat'] = 'Sat';
+
+// weekdays long
+$labels['sunday']    = 'Sunday';
+$labels['monday']    = 'Monday';
+$labels['tuesday']   = 'Tuesday';
+$labels['wednesday'] = 'Wednesday';
+$labels['thursday']  = 'Thursday';
+$labels['friday']    = 'Friday';
+$labels['saturday']  = 'Saturday';
+
+$labels['today'] = 'Today';
+
+// toolbar buttons
+$labels['checkmail']        = 'Check for new messages';
+$labels['writenewmessage']  = 'Create a new message';
+$labels['replytomessage']   = 'Reply to the message';
+$labels['replytoallmessage'] = 'Reply to sender and all recipients';
+$labels['forwardmessage']   = 'Forward the message';
+$labels['deletemessage']    = 'Move message to trash';
+$labels['printmessage']     = 'Print this message';
+$labels['previousmessages'] = 'Show previous set of messages';
+$labels['nextmessages']     = 'Show next set of messages';
+$labels['backtolist']       = 'Back to message list';
+$labels['viewsource']       = 'Show source';
+
+$labels['select'] = 'Select';
+$labels['all'] = 'All';
+$labels['none'] = 'None';
+$labels['unread'] = 'Unread';
+
+$labels['compact'] = 'Compact';
+$labels['empty'] = 'Empty';
+$labels['purge'] = 'Purge';
+
+$labels['quota'] = 'Disk usage';
+$labels['unknown']  = 'unknown';
+$labels['unlimited']  = 'unlimited';
+
+$labels['quicksearch']  = 'Quick search';
+$labels['resetsearch']  = 'Reset search';
+
+
+// message compose
+$labels['compose']  = 'Compose a message';
+$labels['savemessage']  = 'Save this draft';
+$labels['sendmessage']  = 'Send the message now';
+$labels['addattachment']  = 'Attach a file';
+$labels['charset']  = 'Charset';
+$labels['returnreceipt'] = 'Return receipt';
+
+$labels['checkspelling'] = 'Check spelling';
+$labels['resumeediting'] = 'Resume editing';
+$labels['revertto'] = 'Revert to';
+
+$labels['attachments'] = 'Attachments';
+$labels['upload'] = 'Upload';
+$labels['close']  = 'Close';
+
+$labels['low']     = 'Low';
+$labels['lowest']  = 'Lowest';
+$labels['normal']  = 'Normal';
+$labels['high']    = 'High';
+$labels['highest'] = 'Highest';
+
+$labels['nosubject']  = '(no subject)';
+$labels['showimages'] = 'Display images';
+
+
+// address boook
+$labels['name']      = 'Display name';
+$labels['firstname'] = 'First name';
+$labels['surname']   = 'Last name';
+$labels['email']     = 'E-Mail';
+
+$labels['addcontact'] = 'Add new contact';
+$labels['editcontact'] = 'Edit contact';
+
+$labels['edit']   = 'Edit';
+$labels['cancel'] = 'Cancel';
+$labels['save']   = 'Save';
+$labels['delete'] = 'Delete';
+
+$labels['newcontact']     = 'Create new contact card';
+$labels['deletecontact']  = 'Delete selected contacts';
+$labels['composeto']      = 'Compose mail to';
+$labels['contactsfromto'] = 'Contacts $from to $to of $count';
+$labels['print']          = 'Print';
+$labels['export']         = 'Export';
+
+$labels['previouspage']   = 'Show previous set';
+$labels['nextpage']       = 'Show next set';
+
+
+// LDAP search
+$labels['ldapsearch'] = 'LDAP directory search';
+
+$labels['ldappublicsearchname']    = 'Contact name';
+$labels['ldappublicsearchtype'] = 'Exact match?';
+$labels['ldappublicserverselect'] = 'Select servers';
+$labels['ldappublicsearchfield'] = 'Search on';
+$labels['ldappublicsearchform'] = 'Look for a contact';
+$labels['ldappublicsearch'] = 'Search';
+
+
+// settings
+$labels['settingsfor']  = 'Settings for';
+
+$labels['preferences']  = 'Preferences';
+$labels['userpreferences']  = 'User preferences';
+$labels['editpreferences']  = 'Edit user preferences';
+
+$labels['identities']  = 'Identities';
+$labels['manageidentities']  = 'Manage identities for this account';
+$labels['newidentity']  = 'New identity';
+
+$labels['newitem']  = 'New item';
+$labels['edititem']  = 'Edit item';
+
+$labels['setdefault']  = 'Set default';
+$labels['language']  = 'Language';
+$labels['timezone']  = 'Time zone';
+$labels['pagesize']  = 'Rows per page';
+$labels['signature'] = 'Signature';
+$labels['dstactive']  = 'Daylight savings';
+
+$labels['folder']  = 'Folder';
+$labels['folders']  = 'Folders';
+$labels['foldername']  = 'Folder name';
+$labels['subscribed']  = 'Subscribed';
+$labels['create']  = 'Create';
+$labels['createfolder']  = 'Create new folder';
+$labels['rename'] = 'Rename';
+$labels['renamefolder'] = 'Rename folder';
+$labels['deletefolder']  = 'Delete folder';
+$labels['managefolders']  = 'Manage folders';
+
+$labels['sortby'] = 'Sort by';
+$labels['sortasc']  = 'Sort ascending';
+$labels['sortdesc'] = 'Sort descending';
+
+?>
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
new file mode 100644 (file)
index 0000000..ada229c
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/en/messages.inc                                              |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: messages.inc 285 2006-07-30 19:38:06Z thomasb $
+
+*/
+
+$messages = array();
+
+$messages['loginfailed']  = 'Login failed';
+
+$messages['cookiesdisabled'] = 'Your browser does not accept cookies';
+
+$messages['sessionerror'] = 'Your session is invalid or expired';
+
+$messages['imaperror'] = 'Connection to IMAP server failed';
+
+$messages['nomessagesfound'] = 'No messages found in this mailbox';
+
+$messages['loggedout'] = 'You have successfully terminated the session. Good bye!';
+
+$messages['mailboxempty'] = 'Mailbox is empty';
+
+$messages['loading'] = 'Loading...';
+
+$messages['loadingdata'] = 'Loading data...';
+
+$messages['checkingmail'] = 'Checking for new messages...';
+
+$messages['sendingmessage'] = 'Sending message...';
+
+$messages['messagesent'] = 'Message sent successfully';
+
+$messages['savingmessage'] = 'Saving message...';
+
+$messages['messagesaved'] = 'Message saved to Drafts';
+
+$messages['successfullysaved'] = 'Successfully saved';
+
+$messages['addedsuccessfully'] = 'Contact added successfully to address book';
+
+$messages['contactexists'] = 'A contact with this e-mail address already exists';
+
+$messages['blockedimages'] = 'To protect your privacy, remote images are blocked in this message.';
+
+$messages['encryptedmessage'] = 'This is an encrypted message and can not be displayed. Sorry!';
+
+$messages['nocontactsfound'] = 'No contacts found';
+
+$messages['sendingfailed'] = 'Failed to send message';
+
+$messages['errorsaving'] = 'An error occured while saving';
+
+$messages['errormoving'] = 'Could not move the message';
+
+$messages['errordeleting'] = 'Could not delete the message';
+
+$messages['deletecontactconfirm']  = 'Do you really want to delete the selected contact(s)?';
+
+$messages['deletefolderconfirm']  = 'Do you really want to delete this folder?';
+
+$messages['purgefolderconfirm']  = 'Do you really want to delete all messages in this folder?';
+
+$messages['formincomplete'] = 'The form was not completely filled out';
+
+$messages['noemailwarning'] = 'Please enter a valid email address';
+
+$messages['nonamewarning']  = 'Please enter a name';
+
+$messages['nopagesizewarning'] = 'Please enter a page size';
+
+$messages['norecipientwarning'] = 'Please enter at least one recipient';
+
+$messages['nosubjectwarning']  = 'The "Subject" field is empty. Would you like to enter one now?';
+
+$messages['nobodywarning'] = 'Send this message without text?';
+
+$messages['notsentwarning'] = 'Message has not been sent. Do you want to discard your message?';
+
+$messages['noldapserver'] = 'Please select an ldap server to search';
+
+$messages['nocontactsreturned'] = 'No contacts were found';
+
+$messages['nosearchname'] = 'Please enter a contact name or email address';
+
+$messages['searchsuccessful'] = '$nr messages found';
+
+$messages['searchnomatch'] = 'Search returned no matches';
+
+$messages['searching'] = 'Searching...';
+
+$messages['checking'] = 'Checking...';
+
+$messages['nospellerrors'] = 'No spelling errors found';
+
+$messages['folderdeleted'] = 'Folder successfully deleted';
+
+
+?>
diff --git a/program/localization/es/labels.inc b/program/localization/es/labels.inc
new file mode 100644 (file)
index 0000000..dde6033
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/es/labels.inc                                                |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: David Grajal Blanco <dgrabla@gmail.com>                       |
+ |         http://david.grajal.net                                       |
+ +-----------------------------------------------------------------------+
+ | Changelog:                                                            |
+ |  - 6/2/2006 Translations of new features and improvements)            |
+ |  - 17/9/2005 First release                                            |
+ +-----------------------------------------------------------------------+
+
+ $Id: labels.inc 282 2006-07-25 22:11:50Z thomasb $
+
+*/
+
+$labels = array();
+
+// login page
+$labels['welcome']   = 'Bienvenido a $product';
+$labels['username']  = 'Nombre de usuario';
+$labels['password']  = 'Contraseña';
+$labels['server']    = 'Servidor';
+$labels['login']     = 'Entrar';
+
+// taskbar
+$labels['logout']   = 'Cerrar sesión';
+$labels['mail']     = 'E-Mail';
+$labels['settings'] = 'Configuración';
+$labels['addressbook'] = 'Contactos';
+
+// mailbox names
+$labels['inbox']  = 'Entrada';
+$labels['drafts'] = 'Borradores';
+$labels['sent']   = 'Enviados';
+$labels['trash']  = 'Papelera';
+$labels['junk']   = 'Basura';
+
+// message listing
+$labels['subject'] = 'Asunto';
+$labels['from']    = 'Remitente';
+$labels['to']      = 'Destinatario';
+$labels['cc']      = 'Copia';
+$labels['bcc']     = 'Bcc';
+$labels['replyto'] = 'Responder';
+$labels['date']    = 'Fecha';
+$labels['size']    = 'Tamaño';
+$labels['priority'] = 'Prioridad';
+$labels['organization'] = 'Organización';
+
+// aliases
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Carpetas';
+$labels['messagesfromto'] = 'Mensajes desde $from a $to de $count';
+$labels['messagenrof'] = 'Mensaje $nr de $count';
+
+$labels['moveto']   = 'mover a...';
+$labels['download'] = 'descargar';
+
+$labels['filename'] = 'Nombre del fichero';
+$labels['filesize'] = 'Tamaño del fichero';
+
+$labels['preferhtml'] = 'Prefiero HTML';
+$labels['htmlmessage'] = 'Mensaje HTML';
+$labels['prettydate'] = 'Fecha detallada';
+
+$labels['addtoaddressbook'] = 'Añadir a contactos';
+
+// weekdays short
+$labels['sun'] = 'Dom';
+$labels['mon'] = 'Lun';
+$labels['tue'] = 'Mar';
+$labels['wed'] = 'Mie';
+$labels['thu'] = 'Jue';
+$labels['fri'] = 'Vie';
+$labels['sat'] = 'Sáb';
+
+// weekdays long
+$labels['sunday']    = 'Domingo';
+$labels['monday']    = 'Lunes';
+$labels['tuesday']   = 'Martes';
+$labels['wednesday'] = 'Miercoles';
+$labels['thursday']  = 'Jueves';
+$labels['friday']    = 'Viernes';
+$labels['saturday']  = 'Sábado';
+
+$labels['today'] = 'Hoy';
+
+// toolbar buttons
+$labels['checkmail']        = 'Revisar si hay nuevos mensajes';
+$labels['writenewmessage']  = 'Crear nuevo mensaje';
+$labels['replytomessage']   = 'Responder al mensaje';
+$labels['replytoallmessage'] = 'Responder al emisor y a todos los destinatarios';
+$labels['forwardmessage']   = 'Reenviar mensaje';
+$labels['deletemessage']    = 'Mover mensaje a la papelera';
+$labels['printmessage']     = 'Imprimir este mensaje';
+$labels['previousmessages'] = 'Mostrar mensajes anteriores';
+$labels['nextmessages']     = 'Mostrar mensajes siguientes';
+$labels['backtolist']       = 'Volver a la lista de mensajes';
+
+$labels['viewsource']       = 'Mostrar código';
+
+$labels['select'] = 'Seleccionar';
+$labels['all'] = 'Todos';
+$labels['none'] = 'Ninguno';
+$labels['unread'] = 'No leidos';
+
+$labels['compact'] = 'Compactar';
+$labels['empty'] = 'Vaciar';
+$labels['purge'] = 'Eliminar';
+
+$labels['quota'] = 'Uso de disco';
+$labels['unknown']  = 'desconocido';
+$labels['unlimited']  = 'sin límite';
+
+$labels['quicksearch']  = 'Búsqueda rápida';
+$labels['resetsearch']  = 'Reajustar la búsqueda';
+
+
+// message compose
+$labels['compose']  = 'Escribir un mensaje';
+$labels['savemessage']  = 'Almacenar como borrador';
+$labels['sendmessage']  = 'Enviar ahora el mensaje';
+$labels['addattachment']  = 'Añadir un fichero';
+$labels['charset']  = 'Codigo';
+$labels['returnreceipt'] = 'Recibo de entrega';
+
+$labels['checkspelling'] = 'Revisar la ortografía';
+$labels['resumeediting'] = 'Continuar el editaje';
+$labels['revertto'] = 'Revertir a';
+
+$labels['attachments'] = 'Adjuntos';
+$labels['upload'] = 'Subir';
+$labels['close']  = 'Cerrar';
+
+$labels['low']     = 'Bajo';
+$labels['lowest']  = 'Bajísimo';
+$labels['normal']  = 'Normal';
+$labels['high']    = 'Alto';
+$labels['highest'] = 'Altísimo';
+
+
+$labels['nosubject']  = '(sin asunto)';
+$labels['showimages'] = 'Mostrar imágenes';
+
+
+// address boook
+$labels['name']      = 'Nombre completo';
+$labels['firstname'] = 'Nombre';
+$labels['surname']   = 'Apellido';
+$labels['email']     = 'E-Mail';
+
+$labels['edit']   = 'Editar';
+$labels['cancel'] = 'Cancelar';
+$labels['save']   = 'Almacenar';
+$labels['delete'] = 'Eliminar';
+
+$labels['newcontact']     = 'Crear nuevo contacto';
+$labels['addcontact']     = 'Añadir nuevo contacto';
+$labels['editcontact']    = 'Editar contacto';
+$labels['deletecontact']  = 'Eliminar contactos seleccionados';
+$labels['composeto']      = 'Redactar correo a';
+$labels['contactsfromto'] = 'Contactos $from a $to de $count';
+$labels['print']          = 'Imprimir';
+$labels['export']         = 'Exportar';
+
+$labels['previouspage']   = 'Mostrar grupo anterior';
+$labels['nextpage']       = 'Mostrar grupo siguiente';
+
+// LDAP search
+$labels['ldapsearch'] = 'Búsqueda en el directorio LDAP';
+
+$labels['ldappublicsearchname'] = 'Nombre';
+$labels['ldappublicsearchtype'] = '¿Búsqueda exacta?';
+$labels['ldappublicserverselect'] = 'Elegir servidores';
+$labels['ldappublicsearchfield'] = 'Buscando';
+$labels['ldappublicsearchform'] = 'Buscar un contacto';
+$labels['ldappublicsearch'] = 'Buscar';
+
+
+// settings
+$labels['settingsfor']  = 'Configuración para';
+
+$labels['preferences']  = 'Preferencias';
+$labels['userpreferences']  = 'Preferencias de usuario';
+$labels['editpreferences']  = 'Editar preferencias de usuario';
+
+$labels['identities']  = 'Identidades';
+$labels['manageidentities']  = 'Gestionar identidades para esta cuenta';
+$labels['newidentity']  = 'Nueva identidad';
+
+$labels['newitem']  = 'Nuevo';
+$labels['edititem']  = 'Editar';
+
+$labels['setdefault']  = 'Seleccionar opción por defecto';
+$labels['language']  = 'Idioma';
+$labels['timezone']  = 'Zona horaria';
+$labels['pagesize']  = 'Filas por página';
+$labels['signature'] = 'Firma';
+$labels['dstactive']  = 'Cambio de horario';
+
+$labels['folder']  = 'Carpeta';
+$labels['folders']  = 'Carpetas';
+$labels['foldername']  = 'Nombre de carpeta';
+$labels['subscribed']  = 'Suscribirse';
+$labels['create']  = 'Crear';
+$labels['createfolder']  = 'Crear nueva carpeta';
+$labels['rename'] = 'Renombrar';
+$labels['renamefolder'] = 'Renombrar carpeta';
+$labels['deletefolder']  = 'Eliminar carpeta';
+$labels['managefolders']  = 'Gestionar carpetas';
+
+$labels['sortby'] = 'Ordenar por';
+$labels['sortasc']  = 'Orden ascendente';
+$labels['sortdesc'] = 'Orden descendente';
+
+?>
\ No newline at end of file
diff --git a/program/localization/es/messages.inc b/program/localization/es/messages.inc
new file mode 100644 (file)
index 0000000..ff21785
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/es/messages.inc                                              |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: David Grajal Blanco <dgrabla@gmail.com>                       |
+ |         http://david.grajal.net                                       |
+ +-----------------------------------------------------------------------+
+ | Changelog:                                                            |
+ |  - 6/2/2006 Translations of new features and improvements             |
+ |  - 17/9/2005 First release                                            |
+ +-----------------------------------------------------------------------+
+
+
+ $Id: messages.inc 282 2006-07-25 22:11:50Z thomasb $
+
+*/
+
+$messages = array();
+
+
+$messages['loginfailed']  = 'Contraseña incorrecta';
+
+$messages['cookiesdisabled'] = 'Su navegador no acepta cookies';
+
+$messages['sessionerror'] = 'Su sesión no existe o ha expirado';
+
+$messages['imaperror'] = 'Fallo de conexión con el servidor IMAP';
+
+$messages['nomessagesfound'] = 'No se han encontrado mensajes en este buzón';
+
+$messages['loggedout'] = 'Ha cerrado la sesión. ¡Hasta pronto!';
+
+$messages['mailboxempty'] = 'El buzón esta vacio';
+
+$messages['loading'] = 'Cargando...';
+
+$messages['loadingdata'] = 'Cargando datos...';
+
+$messages['checkingmail'] = 'Verificar si hay nuevos mensajes...';
+
+$messages['sendingmessage'] = 'Enviando mensaje...';
+
+$messages['messagesent'] = 'Mensaje enviado correctamente';
+
+$messages['savingmessage'] = 'Guardar mensaje...';
+
+$messages['messagesaved'] = 'Mensaje guardado en los bosquejos';
+
+$messages['successfullysaved'] = 'Guardado correctamente';
+
+$messages['addedsuccessfully'] = 'Contacto añadido correctamente a la libreta de direcciones';
+
+$messages['contactexists'] = 'Ya existe un contacto con esta dirección de correo';
+
+$messages['blockedimages'] = 'Para proteger su privacidad, las imágenes externas han sido bloqueadas en este mensaje';
+
+$messages['encryptedmessage'] = 'Este es un mensaje cifrado y no puede ser mostrado. ¡Lo siento!';
+
+$messages['nocontactsfound'] = 'No hay contactos';
+
+$messages['sendingfailed'] = 'Error al enviar mensaje';
+
+$messages['errorsaving'] = 'Ocurrió un error mientras se guardaba';
+
+$messages['errormoving'] = 'No se ha podido mover el mensaje';
+
+$messages['errordeleting'] = 'No se ha podido eliminar el mensaje';
+
+$messages['deletecontactconfirm']  = '¿Realmente quiere eliminar los contactos seleccionados?';
+
+$messages['deletefolderconfirm']  = '¿Esta seguro de que quiere eliminar esta carpeta?';
+
+$messages['purgefolderconfirm']  = '¿Esta seguro de que quiere eliminar todos los mensajes de esta carpeta?';
+
+$messages['formincomplete'] = 'No han sido rellenados todos los campos del formulario';
+
+$messages['noemailwarning'] = 'Por favor, introduzca un email válido';
+
+$messages['nonamewarning']  = 'Por favor, introduzca su nombre';
+
+$messages['nopagesizewarning'] = 'Por favor, introduzca un tamaño de página';
+
+$messages['norecipientwarning'] = 'Por favor, introduzca al menos un destinatario';
+
+$messages['nosubjectwarning']  = 'El campo "Asunto" esta vacio. ¿Desea redactarlo en este momento?';
+
+$messages['nobodywarning'] = '¿Quiere enviar este mensaje sin texto?';
+
+$messages['notsentwarning'] = 'El mensaje no ha sido enviado. ¿Desea descartar su mensaje?';
+
+$messages['noldapserver'] = 'Por favor, seleccione un servidor LDAP para buscar';
+
+$messages['nocontactsreturned'] = 'No se han encontrado contactos';
+
+$messages['nosearchname'] = 'Por favor, introduzca un nombre o la dirección email';
+
+$messages['searchsuccessful'] = 'Se encontró $nr mensajes';
+
+$messages['searchnomatch'] = 'La busqueda no obtuvo resultados';
+
+$messages['searching'] = 'Buscando...';
+
+$messages['checking'] = 'Revisando...';
+
+$messages['nospellerrors'] = 'No se encontró errores ortográficos';
+
+?>
\ No newline at end of file
diff --git a/program/localization/fr/labels.inc b/program/localization/fr/labels.inc
new file mode 100644 (file)
index 0000000..9e95f29
--- /dev/null
@@ -0,0 +1,221 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/fr/labels.inc                                                |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: aldweb <info@aldweb.com>, Pierre HAEGELI <pierre@haegeli.net> |
+ +-----------------------------------------------------------------------+
+
+ $Id: labels.inc 220 2006-06-12 17:33:21Z roundcube $
+
+*/
+
+$labels = array();
+
+// login page
+$labels['welcome']   = 'Bienvenue à $product';
+$labels['username']  = 'ID utilisateur';
+$labels['password']  = 'Mot de passe';
+$labels['server']    = 'Serveur';
+$labels['login']     = 'Connexion';
+
+// taskbar
+$labels['logout']   = 'Quitter';
+$labels['mail']     = 'e-Mail';
+$labels['settings'] = 'Préférences';
+$labels['addressbook'] = 'Carnet d\'adresses';
+
+// mailbox names
+$labels['inbox']  = 'Boîte de réception';
+$labels['sent']   = 'Messages envoyés';
+$labels['trash']  = 'Corbeille';
+$labels['drafts'] = 'Brouillons';
+$labels['junk']   = 'A trier';
+
+// message listing
+$labels['subject'] = 'Objet';
+$labels['from']    = 'De';
+$labels['to']      = 'A';
+$labels['cc']      = 'Cc';
+$labels['bcc']     = 'Cci';
+$labels['replyto'] = 'Répondre à';
+$labels['date']    = 'Date';
+$labels['size']    = 'Taille';
+$labels['priority'] = 'Priorité';
+$labels['organization'] = 'Organisation';
+
+// aliases
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Dossiers';
+$labels['messagesfromto'] = 'Messages $from à $to sur $count';
+$labels['messagenrof'] = 'Message $nr sur $count';
+
+$labels['moveto']   = 'Déplacer vers...';
+$labels['download'] = 'Télécharger';
+
+$labels['filename'] = 'Nom du fichier';
+$labels['filesize'] = 'Taille du fichier';
+
+$labels['preferhtml'] = 'Préférer HTML';
+$labels['htmlmessage'] = 'Message HTML';
+$labels['prettydate'] = 'Affichage réduit des dates';
+
+$labels['addtoaddressbook'] = 'Ajouter au carnet d\'adresses';
+
+// weekdays short
+$labels['sun'] = 'Dim';
+$labels['mon'] = 'Lun';
+$labels['tue'] = 'Mar';
+$labels['wed'] = 'Mer';
+$labels['thu'] = 'Jeu';
+$labels['fri'] = 'Ven';
+$labels['sat'] = 'Sam';
+
+// weekdays long
+$labels['sunday']    = 'Dimanche';
+$labels['monday']    = 'Lundi';
+$labels['tuesday']   = 'Mardi';
+$labels['wednesday'] = 'Mercredi';
+$labels['thursday']  = 'Jeudi';
+$labels['friday']    = 'Vendredi';
+$labels['saturday']  = 'Samedi';
+
+$labels['today'] = 'Aujourd\'hui';
+
+// toolbar buttons
+$labels['checkmail'] = 'Vérification des nouveaux messages';
+$labels['writenewmessage']  = 'Créer un nouveau message';
+$labels['replytomessage']   = 'Répondre au message';
+$labels['replytoallmessage'] = 'Répondre à tous';
+$labels['forwardmessage']   = 'Transmettre le message';
+$labels['deletemessage']    = 'Déplacer le message dans la corbeille';
+$labels['printmessage']     = 'Imprimer ce message';
+$labels['previousmessages'] = 'Voir les messages précédents';
+$labels['nextmessages']     = 'Voir les messages suivants';
+$labels['backtolist']       = 'Retourner à la liste des messages';
+$labels['viewsource']       = 'Voir le code source';
+
+$labels['select'] = 'Sélectionner';
+$labels['all'] = 'Tous';
+$labels['none'] = 'Aucun';
+$labels['unread'] = 'Non lus';
+
+//$labels['compact'] = 'Compresser';
+$labels['empty'] = 'Vider';
+$labels['purge'] = 'Purger';
+
+$labels['quota'] = 'Utilisation Disque';
+$labels['unknown'] = 'inconnue';
+$labels['unlimited'] = 'illimitée';
+
+$labels['quicksearch']  = 'Recherche rapide';
+$labels['resetsearch']  = 'Réinitialiser la recherche';
+
+
+// message compose
+$labels['compose']  = 'Composer un nouveau message';
+$labels['savemessage']  = 'Sauvegarder ce brouillon';
+$labels['sendmessage']  = 'Envoyer le message maintenant';
+$labels['addattachment']  = 'Joindre un fichier';
+$labels['charset']  = 'Encodage';
+$labels['returnreceipt'] = 'Accusé de réception';
+
+$labels['checkspelling'] = 'Vérifier l\'orthographe';
+$labels['resumeediting'] = 'Retourner à l\'édition';
+$labels['revertto'] = 'Revenir à';
+
+$labels['attachments'] = 'Fichiers joints';
+$labels['upload'] = 'Joindre';
+$labels['close']  = 'Fermer';
+
+$labels['low']     = 'Basse';
+$labels['lowest']  = 'La plus basse';
+$labels['normal']  = 'Normale';
+$labels['high']    = 'Elevée';
+$labels['highest'] = 'La plus élevée';
+
+$labels['nosubject']  = '(pas de sujet)';
+$labels['showimages'] = 'Montrer les images';
+
+
+// address boook
+$labels['name']      = 'Nom à afficher';
+$labels['firstname'] = 'Prénom';
+$labels['surname']   = 'Nom';
+$labels['email']     = 'e-Mail';
+
+$labels['addcontact'] = 'Ajouter un nouveau contact';
+$labels['editcontact'] = 'Editer le contact';
+
+$labels['edit']   = 'Editer';
+$labels['cancel'] = 'Annuler';
+$labels['save']   = 'Sauvegarder';
+$labels['delete'] = 'Supprimer';
+
+$labels['newcontact']     = 'Créer un nouveau contact';
+$labels['addcontact']     = 'Ajouter le contact sélectionné à votre Carnet d\'adresses';
+$labels['deletecontact']  = 'Supprimer les contacts sélectionnés';
+$labels['composeto']      = 'Ecrire un message à';
+$labels['contactsfromto'] = 'Contacts $from à $to sur $count';
+$labels['print']          = 'Imprimer';
+$labels['export']         = 'Exporter';
+
+$labels['previouspage'] = 'Montrer page précédente';
+$labels['nextpage']     = 'Montrer page suivante';
+
+
+// LDAP search
+$labels['ldapsearch'] = 'Recherche dans répertoires LDAP';
+
+$labels['ldappublicsearchname']    = 'Nom du contact';
+$labels['ldappublicsearchtype'] = 'Correspondance exacte ?';
+$labels['ldappublicserverselect'] = 'Sélectionnez les serveurs';
+$labels['ldappublicsearchfield'] = 'Recherche sur';
+$labels['ldappublicsearchform'] = 'Chercher un contact';
+$labels['ldappublicsearch'] = 'Recherche';
+
+
+// settings
+$labels['settingsfor']  = 'Paramètres pour';
+
+$labels['preferences']  = 'Préférences';
+$labels['userpreferences']  = 'Préférences utilisateur';
+$labels['editpreferences']  = 'Editer les préférences utilisateur';
+
+$labels['identities']  = 'Identités';
+$labels['manageidentities']  = 'Gérer les identités pour ce compte';
+$labels['newidentity']  = 'Nouvelle identité';
+
+$labels['newitem']  = 'Nouvel élément';
+$labels['edititem']  = 'Editer l\'élément';
+
+$labels['setdefault']  = 'Paramètres par défaut';
+$labels['language']  = 'Langue';
+$labels['timezone']  = 'Fuseau horaire';
+$labels['pagesize']  = 'Nombre de lignes par page';
+$labels['signature'] = 'Signature';
+$labels['dstactive'] = 'Heure d\'été';
+
+$labels['folder']  = 'Dossier';
+$labels['folders']  = 'Dossiers';
+$labels['foldername']  = 'Nom du dossier';
+$labels['subscribed']  = 'Abonné';
+$labels['create']  = 'Créer';
+$labels['createfolder']  = 'Créer un nouveau dossier';
+$labels['rename'] = 'Renommer';
+$labels['renamefolder'] = 'Renommer le dossier';
+$labels['deletefolder']  = 'Supprimer le dossier';
+$labels['managefolders']  = 'Gérer les dossiers';
+
+$labels['sortby'] = 'Trier par';
+$labels['sortasc']  = 'Tri ascendant';
+$labels['sortdesc'] = 'Tri descendant';
+
+?>
diff --git a/program/localization/fr/messages.inc b/program/localization/fr/messages.inc
new file mode 100644 (file)
index 0000000..0b14ec3
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/fr/messages.inc                                              |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: aldweb <info@aldweb.com>, Pierre HAEGELI <pierre@haegeli.net> |
+ +-----------------------------------------------------------------------+
+
+ $Id: messages.inc 139 2006-06-12 17:32:27Z roundcube $
+
+*/
+
+$messages = array();
+
+$messages['loginfailed']  = 'Erreur de connexion';
+
+$messages['cookiesdisabled'] = 'Votre navigateur n\'accepte pas les cookies';
+
+$messages['sessionerror'] = 'Votre session n\'est pas valide ou a expiré';
+
+$messages['imaperror'] = 'Erreur de connexion au serveur IMAP';
+
+$messages['nomessagesfound'] = 'Aucun message trouvé dans cette boîte aux lettres';
+
+$messages['loggedout'] = 'Vous venez de vous déconnecter avec succès. Au revoir !';
+
+$messages['mailboxempty'] = 'Boîte aux lettres vide';
+
+$messages['loading'] = 'Chargement en cours...';
+
+$messages['loadingdata'] = 'Chargement des données en cours...';
+
+$messages['checkingmail'] = 'Vérification des nouveaux messages ...';
+
+$messages['sendingmessage'] = 'Message en cours d\'envoi...';
+
+$messages['messagesent'] = 'Message envoyé';
+
+$messages['savingmessage'] = 'Sauvegarde du message...';
+
+$messages['messagesaved'] = 'Message sauvegardé dans Brouillons';
+
+$messages['successfullysaved'] = 'Sauvegarde effectuée';
+
+$messages['addedsuccessfully'] = 'Contact ajouté dans le carnet d\'adresses';
+
+$messages['contactexists'] = 'Un contact avec cette adresse e-Mail existe déjà';
+
+$messages['blockedimages'] = 'Afin de préserver votre vie privée, les images distantes ont été bloquées dans ce message.';
+
+$messages['encryptedmessage'] = 'Ceci est un message crypté et il ne peut pas être affiché. Désolé !';
+
+$messages['nocontactsfound'] = 'Aucun contact trouvé';
+
+$messages['sendingfailed'] = 'Le message n\'a pas été envoyé';
+
+$messages['errorsaving'] = 'Une erreur est apparue pendant la sauvegarde';
+
+$messages['errormoving'] = 'Impossible de déplacer le message';
+
+$messages['errordeleting'] = 'Impossible d\'effacer le message';
+
+$messages['deletecontactconfirm']  = 'Voulez-vous vraiment effacer les contacts sélectionnés ?';
+
+$messages['deletefolderconfirm']  = 'Voulez-vous vraiment effacer ce dossier ?';
+
+$messages['purgefolderconfirm']  = 'Voulez-vous vraiment effacer tous les messages dans ce dossier ?';
+
+$messages['formincomplete'] = 'Le formulaire n\'a pas été entièrement rempli';
+
+$messages['noemailwarning'] = 'Entrez une adresse e-Mail valide SVP';
+
+$messages['nonamewarning']  = 'Entrez un nom SVP';
+
+$messages['nopagesizewarning'] = 'Entrez une taille de page SVP';
+
+$messages['norecipientwarning'] = 'Entrez au moins un destinataire SVP';
+
+$messages['nosubjectwarning']  = 'Le champ "Objet" est vide. Souhaitez-vous le compléter maintenant ?';
+
+$messages['nobodywarning'] = 'Envoyer ce message sans texte ?';
+
+$messages['notsentwarning'] = 'Le message n\'a pas été envoyé. Voulez-vous abandonner ce message ?';
+
+$messages['noldapserver'] = 'Choisissez un serveur ldap pour la recherche';
+
+$messages['nocontactsreturned'] = 'Aucun contact trouvé';
+
+$messages['nosearchname'] = 'Entrez un nom ou une adresse e-Mail de contact';
+
+$messages['searchsuccessful'] = '$nr messages trouvés';
+
+$messages['searchnomatch'] = 'Aucun résultat trouvé';
+
+$messages['searching'] = 'En cours de recherche...';
+
+$messages['checking'] = 'Vérification...';
+
+$messages['nospellerrors'] = 'Aucune faute trouvée';
+
+$messages['folderdeleted'] = 'Dossier effacé';
+
+
+?>
diff --git a/program/localization/index.inc b/program/localization/index.inc
new file mode 100644 (file)
index 0000000..249c972
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/localization/index.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide centralized location for keeping track of                   |
+ |   available languages                                                 |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: index.inc 258 2006-06-02 18:16:48Z thomasb $
+ */
+ $rcube_languages = array(
+       'en_US' => 'English (US)',
+       'en_GB' => 'English (GB)',
+       'ar'    => 'Arabic',
+       'am'    => 'Armenian',
+       'bg'    => 'Bulgarian',
+       'bs_BA' => 'Bosnian (Bosanski)',
+       'tw'    => 'Chinese (BIG5)',
+       'cn'    => 'Chinese (GB2312)',
+       'cz'    => 'Czech',
+       'ca'    => 'Català',
+       'da'    => 'Dansk',
+       'de_DE' => 'Deutsch (Deutsch)',
+       'de_CH' => 'Deutsch (Schweiz)',
+       'es'    => 'Español',
+       'et_EE' => 'Estonian',
+       'eu'    => 'Euskara',
+       'fr'    => 'Français', 
+       'ga'    => 'Galician',
+       'el'    => 'Greek',
+       'hr'    => 'Hrvatski',
+       'hu'    => 'Hungarian',
+       'it'    => 'Italiano',
+       'ja'    => 'Japanese (日本語)',
+       'kr'    => 'Korean',
+       'lv'    => 'Latvian',
+       'lt'    => 'Lithuanian',
+       'nl_NL' => 'Nederlands',
+       'nl_BE' => 'Flemish',
+       'nb_NO' => 'Norsk (bokmål)',
+       'nn_NO' => 'Norsk (nynorsk)',
+       'fa'    => 'Persian',
+       'pl'    => 'Polski',
+       'pt_PT' => 'Portuguese (Standard)',
+       'pt_BR' => 'Portuguese (Brazilian)',
+       'ro'    => 'Romanian',
+       'ru'    => 'Russian',
+       'fi'    => 'Suomi',
+       'se'    => 'Svenska',
+       'si'    => 'Slovenian',
+       'sk'    => 'Slovak',
+       'tr'    => 'Turkish',
+       'vn'    => 'Vietnamese'
+);
+
+$rcube_language_aliases = array(
+       'ee' => 'et_EE',
+       'bs' => 'bs_BA',
+       'de' => 'de_DE',
+       'en' => 'en_US',
+       'nl' => 'nl_NL',
+       'no' => 'nn_NO',
+       'pt' => 'pt_PT'
+);
+
+$rcube_charsets = array();
+
+
+?>
\ No newline at end of file
diff --git a/program/localization/pt_BR/labels.inc b/program/localization/pt_BR/labels.inc
new file mode 100644 (file)
index 0000000..bac1668
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/pt_BR/labels.inc                                             |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundQube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Anderson S. Ferreira <anderson@cnpm.embrapa.br>               |
+ +-----------------------------------------------------------------------+
+
+ $Id: labels.inc 296 2006-08-06 15:53:41Z thomasb $
+
+*/
+
+$labels = array();
+
+// Página de login
+$labels['username']  = 'Usuário';
+$labels['password']  = 'Senha';
+$labels['server']    = 'Servidor';
+$labels['login']     = 'Entrar';
+
+// taskbar
+$labels['logout']   = 'Sair';
+$labels['mail']     = 'E-mail';
+$labels['settings'] = 'Configurações pessoais';
+$labels['addressbook'] = 'Catálogo de endereços';
+
+// Nome das pastas de correio
+$labels['inbox']  = 'Caixa de entrada';
+$labels['sent']   = 'Enviados';
+$labels['trash']  = 'Lixeira';
+$labels['drafts'] = 'Rascunhos';
+$labels['junk']   = 'Junk';
+
+// message listing
+$labels['subject'] = 'Assunto';
+$labels['from']    = 'Remetente';
+$labels['to']      = 'Para';
+$labels['cc']      = 'Cópia';
+$labels['bcc']     = 'Bcc';
+$labels['replyto'] = 'Responder para';
+$labels['date']    = 'Data';
+$labels['size']    = 'Tamanho';
+$labels['priority'] = 'Prioridade';
+$labels['organization'] = 'Organização';
+
+// aliases
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Pastas';
+$labels['messagesfromto'] = 'Mensagens $from - $to de $count';
+$labels['messagenrof'] = 'Mensagem $nr de $count';
+
+$labels['moveto']   = 'mover para...';
+$labels['download'] = 'download';
+
+$labels['filename'] = 'Arquivo';
+$labels['filesize'] = 'Tamanho';
+
+$labels['preferhtml'] = 'Prefere HTML';
+$labels['htmlmessage'] = 'Mensagem HTML';
+$labels['prettydate'] = 'Formatar datas';
+
+$labels['addtoaddressbook'] = 'Incluir no catálogo de endereços';
+
+// Dias da semana abreviados
+$labels['sun'] = 'Dom';
+$labels['mon'] = 'Seg';
+$labels['tue'] = 'Ter';
+$labels['wed'] = 'Qua';
+$labels['thu'] = 'Qui';
+$labels['fri'] = 'Sex';
+$labels['sat'] = 'Sáb';
+
+// Dias da semana completos
+$labels['sunday']    = 'Domingo';
+$labels['monday']    = 'Segunda-feira';
+$labels['tuesday']   = 'Terça-feira';
+$labels['wednesday'] = 'Quarta-feira';
+$labels['thursday']  = 'Quinta-feira';
+$labels['friday']    = 'Sexta-feira';
+$labels['saturday']  = 'Sábado';
+
+$labels['today'] = 'Hoje';
+
+// toolbar buttons
+$labels['writenewmessage']  = 'Criar nova mensagem';
+$labels['replytomessage']   = 'Responder';
+$labels['replytoallmessage'] = 'Responder para todos';
+$labels['forwardmessage']   = 'Encaminhar';
+$labels['deletemessage']    = 'Mover para lixeira';
+$labels['printmessage']     = 'Imprimir';
+$labels['previousmessages'] = 'Anterior';
+$labels['nextmessages']     = 'Próximo';
+$labels['backtolist']       = 'Voltar';
+$labels['viewsource']       = 'Exibir código fonte';
+
+$labels['select'] = 'Selecionar';
+$labels['all'] = 'Todas';
+$labels['none'] = 'Nenhuma';
+$labels['unread'] = 'Não lidas';
+
+$labels['compact'] = 'Compactar';
+$labels['empty'] = 'Vazio';
+$labels['purge'] = 'Apagar';
+
+$labels['quota'] = 'Utilização';
+
+
+// message compose
+$labels['compose']  = 'Escrever mensagem';
+$labels['sendmessage']  = 'Enviar';
+$labels['addattachment']  = 'Anexar';
+$labels['charset']  = 'Charset';
+
+$labels['attachments'] = 'Anexos';
+$labels['upload'] = 'Upload';
+$labels['close']  = 'Fechar';
+
+$labels['low']     = 'Mais baixo';
+$labels['lowest']  = 'Baixo';
+$labels['normal']  = 'Normal';
+$labels['high']    = 'Alta';
+$labels['highest'] = 'Mais alta';
+
+$labels['nosubject']  = '(no assunto)';
+$labels['showimages'] = 'Exibir imagens';
+
+
+// address boook
+$labels['name']      = 'Nome completo';
+$labels['firstname'] = 'Primeiro nome';
+$labels['surname']   = 'Sobrenome';
+$labels['email']     = 'E-Mail';
+
+$labels['addcontact'] = 'Incluir novo contato';
+$labels['editcontact'] = 'Editar contato';
+
+$labels['edit']   = 'Editar';
+$labels['cancel'] = 'Cancelar';
+$labels['save']   = 'Salvar';
+$labels['delete'] = 'Apagar';
+
+$labels['newcontact']     = 'Criar novo contato';
+$labels['deletecontact']  = 'Apagar contatos selecionados';
+$labels['composeto']      = 'Escrever mensagem para';
+$labels['contactsfromto'] = 'Contatos $from - $to of $count';
+$labels['print']          = 'Imprimir';
+$labels['export']         = 'Exportar';
+
+// LDAP search
+$labels['ldapsearch'] = 'Pesquisa no diretório LDAP';
+
+$labels['ldappublicsearchname']    = 'Nome do contado';
+$labels['ldappublicsearchtype'] = 'Pesquisa exata?';
+$labels['ldappublicserverselect'] = 'Selecionar servidores';
+$labels['ldappublicsearchfield'] = 'Pesquisar em';
+$labels['ldappublicsearchform'] = 'Procurar por um contato';
+$labels['ldappublicsearch'] = 'Pesquisar';
+
+
+// settings
+$labels['settingsfor']  = 'Configurações para';
+
+$labels['preferences']  = 'Preferências';
+$labels['userpreferences']  = 'Preferências do usuário';
+$labels['editpreferences']  = 'Editar preferências do usuário';
+
+$labels['identities']  = 'Identidades';
+$labels['manageidentities']  = 'Gerenciar identidades para essa conta';
+$labels['newidentity']  = 'Nova identidade';
+
+$labels['newitem']  = 'Novo item';
+$labels['edititem']  = 'Editar item';
+
+$labels['setdefault']  = 'Padrão';
+$labels['language']  = 'Idioma';
+$labels['timezone']  = 'Time zone';
+$labels['pagesize']  = 'Linhas por página';
+$labels['signature'] = 'Assinatura';
+
+$labels['folder']  = 'Pasta';
+$labels['folders']  = 'Pastas';
+$labels['foldername']  = 'Nome da pasta';
+$labels['subscribed']  = 'Assinado';
+$labels['create']  = 'Criar';
+$labels['createfolder']  = 'Criar nova pasta';
+$labels['deletefolder']  = 'Apagar pasta';
+$labels['managefolders']  = 'Gerenciar pastas';
+
+$labels['sortby'] = 'Ordenado por';
+$labels['sortasc']  = 'Ascendente';
+$labels['sortdesc'] = 'Descendente';
+
+// New labels since 0.1beta
+
+$labels['welcome'] = 'Bem-vindo ao $product';
+
+$labels['unknown'] = 'Desconhecido';
+$labels['unlimited'] = 'Ilimitado';
+$labels['dstactive'] = 'Horário de verão';
+
+$labels['previouspage'] = 'Exibir página anterior';
+$labels['nextpage']     = 'Exibir próxima página';
+
+$labels['returnreceipt'] = 'Confirmação de leitura';
+
+$labels['checkmail'] = 'Verificando novas mensagens';
+$labels['checkspelling'] = 'Verificando ortografia';
+$labels['resumeediting'] = 'Continuar a edição';
+$labels['revertto'] = 'Reverter para';
+
+$labels['savemessage']  = 'Salvar rascunho';
+$labels['rename'] = 'Renomear';
+$labels['renamefolder'] = 'Renomear pasta';
+
+// I use an additional description field - this might be used somewhere else
+$labels['description'] = 'Descrição';
+
+?>
diff --git a/program/localization/pt_BR/messages.inc b/program/localization/pt_BR/messages.inc
new file mode 100644 (file)
index 0000000..a1d2972
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/pt_BR/messages.inc                                           |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Anderson S. Ferreira <anderson@cnpm.embrapa.br>               |
+ +-----------------------------------------------------------------------+
+
+ $Id: messages.inc 296 2006-08-06 15:53:41Z thomasb $
+
+*/
+
+$messages = array();
+
+$messages['loginfailed']  = 'Falha no login';
+
+$messages['cookiesdisabled'] = 'Seu navegador não suporta cookies';
+
+$messages['sessionerror'] = 'A sessão do seu navegador é inválida ou expirou';
+
+$messages['imaperror'] = 'A conexão com o servidor IMAP falhou';
+
+$messages['nomessagesfound'] = 'Nenhuma mensagem foi encontrada na caixa postal';
+
+$messages['loggedout'] = 'Sua sessão foi finalizada com sucesso. Até breve!';
+
+$messages['mailboxempty'] = 'A caixa postal está vazia';
+
+$messages['loadingdata'] = 'Carregando informações...';
+
+$messages['messagesent'] = 'Mensagem enviada';
+
+$messages['successfullysaved'] = 'Salvo com sucesso';
+
+$messages['addedsuccessfully'] = 'Contato incluído com sucesso';
+
+$messages['contactexists'] = 'Um contato com esse e-mail já existe';
+
+$messages['blockedimages'] = 'Para proteger sua privacidade, as imagens desta mensagem foram bloqueadas';
+
+$messages['encryptedmessage'] = 'Esta mensagem está criptografada e não pode ser exibida. Desculpe.';
+
+$messages['nocontactsfound'] = 'Nenhum contato encontrado';
+
+$messages['sendingfailed'] = 'Falha no envio da mensagem';
+
+$messages['errorsaving'] = 'Um erro ocorreu durante o salvamento';
+
+$messages['errormoving'] = 'Não foi possível mover a mensagem';
+
+$messages['errordeleting'] = 'Não foi possível apagar a mensagem';
+
+$messages['deletecontactconfirm']  = 'Deseja realmente excluir os contatos selecionados?';
+
+$messages['deletefolderconfirm']  = 'Deseja realmente excluir esta pasta?';
+
+$messages['purgefolderconfirm']  = 'Deseja realmente excluir todas mensagens desta pasta';
+
+$messages['formincomplete'] = 'Os campos não foram completamente preenchidos';
+
+$messages['noemailwarning'] = 'Por favor, informe um endereço de e-mail válido';
+
+$messages['nonamewarning']  = 'Por favor, informe o nome';
+
+$messages['nopagesizewarning'] = 'Por favor, informe o tamanho da página';
+
+$messages['norecipientwarning'] = 'Por favor, informe pelo menos um destinatário';
+
+$messages['nosubjectwarning']  = 'O campo assunto não foi informado. Deseja incluí-lo agora?';
+
+$messages['nobodywarning'] = 'Enviar a mensagem se texto?';
+
+$messages['notsentwarning'] = 'A mensegem não foi enviada, deseja excluí-la?';
+
+$messages['noldapserver'] = 'Por favor, selecione um servidor LDAP para a pesquisa';
+
+$messages['nocontactsreturned'] = 'Nenhum contato foi encontrado';
+
+$messages['nosearchname'] = 'Por favor, informe o nome do contado ou seu endereço de e-mail';
+
+// New messages since 0.1beta
+$messages['checking'] = 'Verificando...';
+
+$messages['nospellerrors'] = 'Nenhum erro ortográfico foi encontrado';
+
+$messages['checkingmail'] = 'Verificando novas mensagens...';
+
+$messages['savingmessage'] = 'Salvando a mensagem...';
+
+$messages['messagesaved'] = 'Mensagem salva como rascunho';
+
+$messages['folderdeleted'] = 'Pasta excluida com sucesso';
+
+?>
diff --git a/program/localization/pt_PT/labels.inc b/program/localization/pt_PT/labels.inc
new file mode 100644 (file)
index 0000000..5d1784a
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/pt_PT/labels.inc                                             |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ +-----------------------------------------------------------------------+
+ | Author: Pedro M. Gouveia <pbodymind@gmail.com>                           |
+ +-----------------------------------------------------------------------+
+ $Id: labels.inc 267 2006-06-27 21:56:44Z richs $
+
+*/
+
+$labels = array();
+
+// Página de login
+$labels['username']  = 'Utilizador';
+$labels['password']  = 'Senha';
+$labels['server']    = 'Servidor';
+$labels['login']     = 'Entrar';
+
+// taskbar
+$labels['logout']   = 'Sair';
+$labels['mail']     = 'E-mail';
+$labels['settings'] = 'Configurações pessoais';
+$labels['addressbook'] = 'Catálogo de endereços';
+
+// Nome das pastas de correio
+$labels['inbox']  = 'Caixa de entrada';
+$labels['drafts'] = 'Esboços';
+$labels['sent']   = 'Enviados';
+$labels['trash']  = 'Lixeira';
+$labels['drafts'] = 'Rascunhos';
+$labels['junk']   = 'Spam';
+
+// message listing
+$labels['subject'] = 'Assunto';
+$labels['from']    = 'Remetente';
+$labels['to']      = 'Para';
+$labels['cc']      = 'Cc';
+$labels['bcc']     = 'Bcc';
+$labels['replyto'] = 'Responder para';
+$labels['date']    = 'Data';
+$labels['size']    = 'Tamanho';
+$labels['priority'] = 'Prioridade';
+$labels['organization'] = 'Organização';
+
+// aliases
+$labels['reply-to'] = $labels['replyto'];
+
+$labels['mailboxlist'] = 'Pastas';
+$labels['messagesfromto'] = 'Mensagens de $from para $to $count';
+$labels['messagenrof'] = 'Mensagem $nr de $count';
+
+$labels['moveto']   = 'mover para...';
+$labels['download'] = 'download';
+
+$labels['filename'] = 'Arquivo';
+$labels['filesize'] = 'Tamanho';
+
+$labels['preferhtml'] = 'Prefere HTML';
+$labels['htmlmessage'] = 'Mensagem HTML';
+$labels['prettydate'] = 'Formatar datas';
+
+$labels['addtoaddressbook'] = 'Incluir no catálogo de endereços';
+
+// Dias da semana abreviados
+$labels['sun'] = 'Dom';
+$labels['mon'] = 'Seg';
+$labels['tue'] = 'Ter';
+$labels['wed'] = 'Qua';
+$labels['thu'] = 'Qui';
+$labels['fri'] = 'Sex';
+$labels['sat'] = 'Sáb';
+
+// Dias da semana completos
+$labels['sunday']    = 'Domingo';
+$labels['monday']    = 'Segunda-feira';
+$labels['tuesday']   = 'Terça-feira';
+$labels['wednesday'] = 'Quarta-feira';
+$labels['thursday']  = 'Quinta-feira';
+$labels['friday']    = 'Sexta-feira';
+$labels['saturday']  = 'Sábado';
+
+$labels['today'] = 'Hoje';
+
+// toolbar buttons
+$labels['checkmail']        = 'Verificar para ver se há mensagens novas';
+$labels['writenewmessage']  = 'Criar nova mensagem';
+$labels['replytomessage']   = 'Responder';
+$labels['replytoallmessage'] = 'Responder para todos';
+$labels['forwardmessage']   = 'Encaminhar';
+$labels['deletemessage']    = 'Mover para lixeira';
+$labels['printmessage']     = 'Imprimir';
+$labels['previousmessages'] = 'Anterior';
+$labels['nextmessages']     = 'Próximo';
+$labels['backtolist']       = 'Voltar';
+$labels['viewsource']       = 'Exibir código fonte';
+
+$labels['select'] = 'Selecionar';
+$labels['all'] = 'Todas';
+$labels['none'] = 'Nenhuma';
+$labels['unread'] = 'Não lidas';
+
+$labels['compact'] = 'Compactar';
+$labels['empty'] = 'Vazio';
+$labels['purge'] = 'Apagar';
+
+$labels['quota'] = 'Quota de espaço';
+
+
+// message compose
+$labels['compose']  = 'Escrever mensagem';
+$labels['savemessage']  = 'Excepto este esboço';
+$labels['sendmessage']  = 'Enviar';
+$labels['addattachment']  = 'Anexar';
+$labels['charset']  = 'Charset';
+
+$labels['attachments'] = 'Anexos';
+$labels['upload'] = 'Upload';
+$labels['close']  = 'Fechar';
+
+$labels['low']     = 'Baixo';
+$labels['lowest']  = 'Muito Baixo';
+$labels['normal']  = 'Normal';
+$labels['high']    = 'Alta';
+$labels['highest'] = 'Muito alta';
+
+$labels['nosubject']  = '(sem assunto)';
+$labels['showimages'] = 'Exibir imagens';
+
+
+// address boook
+$labels['name']      = 'Nome completo';
+$labels['firstname'] = 'Primeiro nome';
+$labels['surname']   = 'Apelido';
+$labels['email']     = 'E-Mail';
+
+$labels['addcontact'] = 'Incluir novo contacto';
+$labels['editcontact'] = 'Editar contacto';
+
+$labels['edit']   = 'Editar';
+$labels['cancel'] = 'Cancelar';
+$labels['save']   = 'Salvar';
+$labels['delete'] = 'Apagar';
+
+$labels['newcontact']     = 'Criar novo contacto';
+$labels['addcontact']     = 'Adicionar contacto seleccionado ao catálogo de endereços';
+$labels['deletecontact']  = 'Apagar contactos seleccionados';
+$labels['composeto']      = 'Escrever mensagem para';
+$labels['contactsfromto'] = 'Contactos $from - $to de $count';
+$labels['print']          = 'Imprimir';
+$labels['export']         = 'Exportar';
+
+// LDAP search
+$labels['ldapsearch'] = 'Pesquisa no diretório LDAP';
+
+$labels['ldappublicsearchname']    = 'Nome do contacto';
+$labels['ldappublicsearchtype'] = 'Pesquisa certa?';
+$labels['ldappublicserverselect'] = 'Seleccionar servidores';
+$labels['ldappublicsearchfield'] = 'Pesquisar em';
+$labels['ldappublicsearchform'] = 'Procurar por contacto';
+$labels['ldappublicsearch'] = 'Pesquisar';
+
+
+// settings
+$labels['settingsfor']  = 'Configurações para';
+
+$labels['preferences']  = 'Preferências';
+$labels['userpreferences']  = 'Preferências do tilizador';
+$labels['editpreferences']  = 'Editar preferências do utilizador';
+
+$labels['identities']  = 'Identidades';
+$labels['manageidentities']  = 'Gerenciar identidades para a sua conta';
+$labels['newidentity']  = 'Nova identidade';
+
+$labels['newitem']  = 'Novo item';
+$labels['edititem']  = 'Editar item';
+
+$labels['setdefault']  = 'Padrão';
+$labels['language']  = 'Idioma';
+$labels['timezone']  = 'Hora Local';
+$labels['pagesize']  = 'Linhas por página';
+$labels['signature'] = 'Assinatura';
+
+$labels['folder']  = 'Pasta';
+$labels['folders']  = 'Pastas';
+$labels['foldername']  = 'Nome da pasta';
+$labels['subscribed']  = 'Assinado';
+$labels['create']  = 'Criar';
+$labels['createfolder']  = 'Criar nova pasta';
+$labels['deletefolder']  = 'Apagar pasta';
+$labels['managefolders']  = 'Gerenciar pastas';
+
+$labels['sortby'] = 'Ordenado por';
+$labels['sortasc']  = 'Ascendente';
+$labels['sortdesc'] = 'Descendente';
+
+?>
diff --git a/program/localization/pt_PT/messages.inc b/program/localization/pt_PT/messages.inc
new file mode 100644 (file)
index 0000000..407b1c6
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | language/pt_PT/messages.inc                                           |
+ |                                                                       |
+ | Language file of the RoundCube Webmail client                         |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ +-----------------------------------------------------------------------+
+ | Author: Pedro M. Gouveia <pbodymind@gmail.com>                           |
+ +-----------------------------------------------------------------------+
+
+ $Id: messages.inc 267 2006-06-27 21:56:44Z richs $
+*/
+
+$messages = array();
+
+$messages['loginfailed']  = 'Falha no login';
+
+$messages['cookiesdisabled'] = 'O seu navegador não suporta cookies';
+
+$messages['sessionerror'] = 'A sua sessão é inválida ou expirou';
+
+$messages['imaperror'] = 'O servidor IMAP falhou';
+
+$messages['nomessagesfound'] = 'Nenhuma mensagem foi encontrada na caixa postal';
+
+$messages['loggedout'] = 'Sua sessão foi finalizada com sucesso. Até breve!';
+
+$messages['mailboxempty'] = 'A caixa postal está vazia';
+
+$messages['loading'] = 'Carregando...';
+
+$messages['loadingdata'] = 'Carregando Informações...';
+
+$messages['checkingmail'] = 'Verificar para ver se há mensagens novas...';
+
+$messages['sendingmessage'] = 'A enviar mensagem...';
+
+$messages['messagesent'] = 'Mensagem enviada';
+
+$messages['savingmessage'] = 'Mensagem do Saving...';
+
+$messages['messagesaved'] = 'Mensagem conservada aos esboços';
+
+$messages['successfullysaved'] = 'Ficheiro gravado';
+
+$messages['addedsuccessfully'] = 'Contacto adicionado ao catálogo de endereços';
+
+$messages['contactexists'] = 'Um contacto com esse e-mail já existe';
+
+$messages['blockedimages'] = 'Para proteger sua privacidade, as imagens desta mensagem foram bloqueadas';
+
+$messages['encryptedmessage'] = 'Esta mensagem está criptografada e não pode ser exibida. Desculpe.';
+
+$messages['nocontactsfound'] = 'Nenhum contacto encontrado';
+
+$messages['sendingfailed'] = 'Falha no envio da mensagem';
+
+$messages['errorsaving'] = 'Ocorreu um erro a gravar o ficheiro';
+
+$messages['errormoving'] = 'Não foi possível mover a mensagem';
+
+$messages['errordeleting'] = 'Não foi possível apagar a mensagem';
+
+$messages['deletecontactconfirm']  = 'Deseja realmente excluir os contatos seleccionados?';
+
+$messages['deletefolderconfirm']  = 'Deseja realmente excluir esta pasta?';
+
+$messages['purgefolderconfirm']  = 'Deseja realmente excluir todas mensagens desta pasta';
+
+$messages['formincomplete'] = 'Alguns campos não foram preenchidos';
+
+$messages['noemailwarning'] = 'Por favor, insira um endereço de e-mail válido';
+
+$messages['nonamewarning']  = 'Por favor, insira o nome';
+
+$messages['nopagesizewarning'] = 'Por favor, insira o tamanho da página';
+
+$messages['norecipientwarning'] = 'Por favor, informe pelo menos um destinatário';
+
+$messages['nosubjectwarning']  = 'O campo assunto não foi preenchido. Deseja preenche-lo agora?';
+
+$messages['nobodywarning'] = 'Enviar a mensagem sem texto?';
+
+$messages['notsentwarning'] = 'A mensagem não foi enviada, deseja excluí-la?';
+
+$messages['noldapserver'] = 'Por favor, selecione um servidor LDAP para a pesquisa';
+
+$messages['nocontactsreturned'] = 'Nenhum contacto foi encontrado';
+
+$messages['nosearchname'] = 'Por favor, informe o nome do contacto ou o seu endereço de e-mail';
+
+?>
diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc
new file mode 100644 (file)
index 0000000..2bec51e
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/delete.inc                                  |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Delete the submitted contacts (CIDs) from the users address book    |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: delete.inc 429 2006-12-22 22:26:24Z thomasb $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+if ($_GET['_cid'] && preg_match('/^[0-9]+(,[0-9]+)*$/',$_GET['_cid']))
+  {
+  $DB->query("UPDATE ".get_table_name('contacts')."
+              SET    del=1
+              WHERE  user_id=?
+              AND    contact_id IN (".$_GET['_cid'].")",
+              $_SESSION['user_id']);
+                     
+  $count = $DB->affected_rows();
+  if (!$count)
+    {
+    // send error message
+    exit;
+    }
+
+
+  // count contacts for this user
+  $sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
+                            FROM ".get_table_name('contacts')."
+                            WHERE  del<>1
+                            AND    user_id=?",
+                            $_SESSION['user_id']);
+                                   
+  $sql_arr = $DB->fetch_assoc($sql_result);
+  $rowcount = $sql_arr['rows'];    
+
+  // update message count display
+  $pages = ceil($rowcount/$CONFIG['pagesize']);
+  $commands = sprintf("this.set_rowcount('%s');\n", rcmail_get_rowcount_text($rowcount));
+  $commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
+
+
+  // add new rows from next page (if any)
+  if ($_GET['_from']!='show' && $pages>1 && $_SESSION['page'] < $pages)
+    {
+    $start_row = ($_SESSION['page'] * $CONFIG['pagesize']) - $count;
+
+    // get contacts from DB
+    $sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
+                                   WHERE  del<>1
+                                   AND    user_id=?
+                                   ORDER BY name",
+                                   $start_row,
+                                   $count,
+                                   $_SESSION['user_id']);
+                                     
+    $commands .= rcmail_js_contacts_list($sql_result);
+
+/*
+    // define list of cols to be displayed
+    $a_show_cols = array('name', 'email');
+    
+    while ($sql_arr = $DB->fetch_assoc($sql_result))
+      {
+      $a_row_cols = array();
+            
+      // format each col
+      foreach ($a_show_cols as $col)
+        {
+        $cont = rep_specialchars_output($sql_arr[$col]);
+        $a_row_cols[$col] = $cont;
+        }
+  
+      $commands .= sprintf("this.add_contact_row(%s, %s);\n",
+                           $sql_arr['contact_id'],
+                           array2js($a_row_cols));
+      }
+*/
+    }
+
+  // send response
+  rcube_remote_response($commands);
+  }
+
+exit;
+?>
diff --git a/program/steps/addressbook/edit.inc b/program/steps/addressbook/edit.inc
new file mode 100644 (file)
index 0000000..c71e2be
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/edit.inc                                    |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Show edit form for a contact entry or to add a new one              |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: edit.inc 88 2005-12-03 16:54:12Z roundcube $
+
+*/
+
+
+if (($_GET['_cid'] || $_POST['_cid']) && $_action=='edit')
+  {
+  $cid = $_POST['_cid'] ? $_POST['_cid'] : $_GET['_cid'];
+  $DB->query("SELECT * FROM ".get_table_name('contacts')."
+             WHERE  contact_id=?
+             AND    user_id=?
+             AND    del<>1",
+             $cid,
+             $_SESSION['user_id']);
+  
+  $CONTACT_RECORD = $DB->fetch_assoc();
+
+  if (is_array($CONTACT_RECORD))
+    $OUTPUT->add_script(sprintf("%s.set_env('cid', '%s');", $JS_OBJECT_NAME, $CONTACT_RECORD['contact_id']));
+  }
+
+
+
+function rcmail_contact_editform($attrib)
+  {
+  global $CONTACT_RECORD, $JS_OBJECT_NAME;
+
+  if (!$CONTACT_RECORD && $GLOBALS['_action']!='add')
+    return rcube_label('contactnotfound');
+
+  // add some labels to client
+  rcube_add_label('noemailwarning');
+  rcube_add_label('nonamewarning');
+
+  list($form_start, $form_end) = get_form_tags($attrib);
+  unset($attrib['form']);
+  
+
+  // a specific part is requested
+  if ($attrib['part'])
+    {
+    $out = $form_start;
+    $out .= rcmail_get_edit_field($attrib['part'], $CONTACT_RECORD[$attrib['part']], $attrib); 
+    return $out;
+    }
+
+
+  // return the complete address edit form as table
+  $out = "$form_start<table>\n\n";
+
+  $a_show_cols = array('name', 'firstname', 'surname', 'email');
+  foreach ($a_show_cols as $col)
+    {
+    $attrib['id'] = 'rcmfd_'.$col;
+    $title = rcube_label($col);
+    $value = rcmail_get_edit_field($col, $CONTACT_RECORD[$col], $attrib);
+    $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                    $attrib['id'],
+                    $title,
+                    $value);
+    }
+
+  $out .= "\n</table>$form_end";
+
+  return $out;  
+  }
+
+
+// similar function as in /steps/settings/edit_identity.inc
+function get_form_tags($attrib)
+  {
+  global $CONTACT_RECORD, $OUTPUT, $JS_OBJECT_NAME, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
+
+  $form_start = '';
+  if (!strlen($EDIT_FORM))
+    {
+    $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
+    $hiddenfields->add(array('name' => '_action', 'value' => 'save'));
+    
+    if ($_GET['_framed'] || $_POST['_framed'])
+      $hiddenfields->add(array('name' => '_framed', 'value' => 1));
+    
+    if ($CONTACT_RECORD['contact_id'])
+      $hiddenfields->add(array('name' => '_cid', 'value' => $CONTACT_RECORD['contact_id']));
+    
+    $form_start = !strlen($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
+    $form_start .= "\n$SESS_HIDDEN_FIELD\n";
+    $form_start .= $hiddenfields->show();
+    }
+    
+  $form_end = (strlen($EDIT_FORM) && !strlen($attrib['form'])) ? '</form>' : '';
+  $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
+  
+  if (!strlen($EDIT_FORM))
+    $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('editform', '$form_name');");
+  
+  $EDIT_FORM = $form_name;
+
+  return array($form_start, $form_end);  
+  }
+
+
+
+if (!$CONTACT_RECORD && template_exists('addcontact'))
+  parse_template('addcontact');
+
+// this will be executed if no template for addcontact exists
+parse_template('editcontact');
+?>
\ No newline at end of file
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
new file mode 100644 (file)
index 0000000..efa64f7
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/func.inc                                    |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide addressbook functionality and GUI objects                   |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: func.inc 127 2006-01-25 22:56:53Z roundcube $
+
+*/
+
+$CONTACTS_LIST = array();
+
+// set list properties and session vars
+if (strlen($_GET['_page']))
+  {
+  $CONTACTS_LIST['page'] = $_GET['_page'];
+  $_SESSION['page'] = $_GET['_page'];
+  }
+else
+  $CONTACTS_LIST['page'] = $_SESSION['page'] ? $_SESSION['page'] : 1;
+
+// disable the ldap public search button if there's no servers configured
+$enable_ldap = 'true';
+if (!$CONFIG['ldap_public'])
+  $enable_ldap = 'false';
+  
+$OUTPUT->add_script("$JS_OBJECT_NAME.set_env('ldappublicsearch', $enable_ldap);");  
+
+// return the message list as HTML table
+function rcmail_contacts_list($attrib)
+  {
+  global $DB, $CONFIG, $OUTPUT, $CONTACTS_LIST, $JS_OBJECT_NAME;
+  
+  //$skin_path = $CONFIG['skin_path'];
+  //$image_tag = '<img src="%s%s" alt="%s" border="0" />';
+  
+  // count contacts for this user
+  $sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
+                            FROM ".get_table_name('contacts')."
+                            WHERE  del<>1
+                            AND    user_id=?",
+                            $_SESSION['user_id']);
+
+  $sql_arr = $DB->fetch_assoc($sql_result);
+  $rowcount = $sql_arr['rows'];
+
+  if ($rowcount)
+    {
+    $start_row = ($CONTACTS_LIST['page']-1) * $CONFIG['pagesize'];
+
+    // get contacts from DB
+    $sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
+                                   WHERE  del<>1
+                                   AND    user_id= ?
+                                   ORDER BY name",
+                                   $start_row,
+                                   $CONFIG['pagesize'],
+                                   $_SESSION['user_id']);
+    }
+  else
+    $sql_result = NULL;
+
+
+  // add id to message list table if not specified
+  if (!strlen($attrib['id']))
+    $attrib['id'] = 'rcmAddressList';
+
+  // define list of cols to be displayed
+  $a_show_cols = array('name', 'email');
+
+  // create XHTML table  
+  $out = rcube_table_output($attrib, $sql_result, $a_show_cols, 'contact_id');
+
+  // set client env
+  $javascript = sprintf("%s.gui_object('contactslist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
+  $javascript .= sprintf("%s.set_env('current_page', %d);\n", $JS_OBJECT_NAME, $CONTACTS_LIST['page']);
+  $javascript .= sprintf("%s.set_env('pagecount', %d);\n", $JS_OBJECT_NAME, ceil($rowcount/$CONFIG['pagesize']));
+  $javascript .= "rcmail.set_env('newcontact', '" . rcube_label('newcontact') . "');";
+  //$javascript .= sprintf("%s.set_env('contacts', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
+  
+  $OUTPUT->add_script($javascript);  
+
+  // add some labels to client
+  rcube_add_label('deletecontactconfirm');
+
+  return $out;
+  }
+
+
+
+function rcmail_js_contacts_list($sql_result, $obj_name='this')
+  {
+  global $DB;
+
+  $commands = '';
+  
+  if (!$sql_result)
+    return '';
+
+  // define list of cols to be displayed
+  $a_show_cols = array('name', 'email');
+    
+  while ($sql_arr = $DB->fetch_assoc($sql_result))
+    {
+    $a_row_cols = array();
+            
+    // format each col
+    foreach ($a_show_cols as $col)
+      {
+      $cont = rep_specialchars_output($sql_arr[$col]);
+      $a_row_cols[$col] = $cont;
+      }
+  
+    $commands .= sprintf("%s.add_contact_row(%s, %s);\n",
+                         $obj_name,
+                         $sql_arr['contact_id'],
+                         array2js($a_row_cols));
+    }
+    
+  return $commands;
+  }
+
+
+// similar function as /steps/settings/identities.inc::rcmail_identity_frame()
+function rcmail_contact_frame($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmcontactframe';
+    
+  $attrib['name'] = $attrib['id'];
+
+  $OUTPUT->add_script(sprintf("%s.set_env('contentframe', '%s');", $JS_OBJECT_NAME, $attrib['name']));
+
+  $attrib_str = create_attrib_string($attrib, array('name', 'id', 'class', 'style', 'src', 'width', 'height', 'frameborder'));
+  $out = '<iframe'. $attrib_str . '></iframe>';
+    
+  return $out;
+  }
+
+
+function rcmail_rowcount_display($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+  
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmcountdisplay';
+
+  $OUTPUT->add_script(sprintf("%s.gui_object('countdisplay', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+
+  // allow the following attributes to be added to the <span> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
+
+  
+  $out = '<span' . $attrib_str . '>';
+  $out .= rcmail_get_rowcount_text();
+  $out .= '</span>';
+  return $out;
+  }
+
+
+
+function rcmail_get_rowcount_text($max=NULL)
+  {
+  global $CONTACTS_LIST, $CONFIG, $DB;
+  
+  $start_row = ($CONTACTS_LIST['page']-1) * $CONFIG['pagesize'] + 1;
+
+  // get nr of contacts
+  if ($max===NULL)
+    {
+    $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
+                              WHERE  del<>1
+                              AND    user_id=?",
+                              $_SESSION['user_id']);
+
+    $max = $DB->num_rows($sql_result);
+    }
+
+  if ($max==0)
+    $out = rcube_label('nocontactsfound');
+  else
+    $out = rcube_label(array('name' => 'contactsfromto',
+                             'vars' => array('from'  => $start_row,
+                                             'to'    => min($max, $start_row + $CONFIG['pagesize'] - 1),
+                                             'count' => $max)));
+
+  return $out;
+  }
+
+?>
diff --git a/program/steps/addressbook/ldapsearchform.inc b/program/steps/addressbook/ldapsearchform.inc
new file mode 100644 (file)
index 0000000..7633bde
--- /dev/null
@@ -0,0 +1,277 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/ldapsearch.inc                              |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Show an ldap search form in the addressbook                         |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Justin Randell <justin.randell@gmail.com>                     |
+ +-----------------------------------------------------------------------+
+
+ $Id: ldapsearchform.inc 159 2006-03-03 16:34:35Z roundcube $
+
+*/
+require_once 'include/rcube_ldap.inc';
+
+/**
+ * draw the ldap public search form
+ */
+function rcmail_ldap_public_search_form($attrib)
+  {
+  global $CONFIG, $JS_OBJECT_NAME, $OUTPUT; 
+  if (!isset($CONFIG['ldap_public']))
+    {
+    // no ldap servers to search
+    show_message('noldapserver', 'warning');
+    rcmail_overwrite_action('add');
+    return false;
+    }
+  else
+    {
+    // store some information in the session
+    $_SESSION['ldap_public']['server_count'] = $server_count = count($CONFIG['ldap_public']);
+    $_SESSION['ldap_public']['server_names'] = $server_names = array_keys($CONFIG['ldap_public']);
+    }
+  
+  list($form_start, $form_end) = get_form_tags($attrib);
+  $out = "$form_start<table id=\"ldap_public_search_table\">\n\n";
+  
+  // search name field
+  $search_name = new textfield(array('name' => '_ldap_public_search_name',
+                                     'id'   => 'rcmfd_ldap_public_search_name'));
+  $out .= "<tr><td class=\"title\"><label for=\"rcmfd_ldap_public_search_name\">" . 
+          rep_specialchars_output(rcube_label('ldappublicsearchname')) . 
+          "</label></td><td>" . $search_name->show() . "</td></tr>\n";
+
+
+  // there's more than one server to search for, show a dropdown menu
+  if ($server_count > 1)
+    {
+    $select_server = new select(array('name' => '_ldap_public_servers', 
+                                      'id'   => 'rcfmd_ldap_public_servers'));
+     
+    $select_server->add($server_names, $server_names);
+
+    $out .= '<tr><td class="title"><label for="rcfmd_ldap_public_servers">' .
+            rep_specialchars_output(rcube_label('ldappublicserverselect')) .
+            "</label></td><td>" . $select_server->show() . "</td></tr>\n";
+    }
+  
+  // foreach configured ldap server, set up the search fields
+  for ($i = 0; $i < $server_count; $i++)
+    {
+    $server = $CONFIG['ldap_public'][$server_names[$i]];
+    
+    // only display one search fields select - js takes care of the rest
+    if (!$i)
+      {
+      $field_name = '_ldap_public_search_field';
+      $field_id   = 'rcfmd_ldap_public_search_field';
+
+      $search_fields = new select(array('name' => $field_name, 
+                                        'id'   => $field_id));
+
+      $search_fields->add(array_keys($server['search_fields']), array_values($server['search_fields']));
+      $out .= '<tr><td class="title"><label for="' . $field_id . '">' .
+              rep_specialchars_output(rcube_label('ldappublicsearchfield')) . 
+              "</label></td><td>" . $search_fields->show() . "</td></tr>\n";
+      
+      $attributes = array('name'  => '_ldap_public_search_type', 
+                          'id'    => 'rcmfd_ldap_public_search_type');
+
+      // if there's only one server, and it doesn't accept fuzzy searches,
+      // then check and disable the check box - thanks pieter
+      if ($server_count == 1 && !$server['fuzzy_search'])
+        {
+        $attributes['CHECKED'] = 'CHECKED'; 
+        $attributes['disabled'] = 'disabled'; 
+        }
+
+      $search_type = new checkbox($attributes);
+
+      $out .= '<tr id="ldap_fuzzy_search"><td class="title"><label for="rcmfd_ldap_public_search_type">' .
+              rep_specialchars_output(rcube_label('ldappublicsearchtype')) .
+              "</label></td><td>" . $search_type->show() . "</td></tr>\n";
+      }
+    
+    if ($server_count > 1)
+      {
+      // store the search fields in a js array for each server
+      $js = '';
+      foreach ($server['search_fields'] as $search_name => $search_value)
+        $js .= "['$search_name', '$search_value'], ";
+
+      // store whether this server accepts fuzzy search as last item in array
+      $js .= $server['fuzzy_search'] ? "'fuzzy'" : "'exact'";
+      $OUTPUT->add_script("rcmail.set_env('{$server_names[$i]}_search_fields', new Array($js));");
+      }
+    }
+
+  // add contact button label text
+  $OUTPUT->add_script("rcmail.set_env('addcontact', '" . rcube_label('addcontact') . "');");
+
+  $out .= "\n</table>$form_end";
+  return $out;  
+  }
+
+/**
+ * get search values and return ldap contacts
+ */
+function rcmail_ldap_public_list()
+  {
+  // just return if we are not being called from a search form
+  if (!isset($_POST['_action']))
+    return null;
+
+  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+  
+  // show no search name warning and exit
+  if (empty($_POST['_ldap_public_search_name']) || trim($_POST['_ldap_public_search_name']) == '')
+    {
+    show_message('nosearchname', 'warning');
+    return false;
+    }
+  
+  // set up ldap server(s) array or bail
+  if ($_SESSION['ldap_public']['server_count'] > 1)
+    // show no ldap server warning and exit
+    if (empty($_POST['_ldap_public_servers']))
+      {
+      show_message('noldappublicserver', 'warning');
+      return false;
+      }
+    else
+      $server_name = $_POST['_ldap_public_servers'];
+  else if ($_SESSION['ldap_public']['server_count'] == 1)
+    $server_name = $_SESSION['ldap_public']['server_names'][0];
+  else
+    return false;
+
+  // get search parameters
+  $search_value = $_POST['_ldap_public_search_name'];
+  $search_field = $_POST['_ldap_public_search_field'];
+
+  // only use the post var for search type if the ldap server allows 'like'
+  $exact = true;
+  if ($CONFIG['ldap_public'][$server_name]['fuzzy_search'])
+    $exact = isset($_POST['_ldap_public_search_type']) ? true : false; 
+  
+  // perform an ldap search
+  $contacts = rcmail_ldap_contact_search($search_value, 
+                                         $search_field, 
+                                         $CONFIG['ldap_public'][$server_name], 
+                                         $exact);
+  
+  // if no results, show a warning and return
+  if (!$contacts)
+    {
+    show_message('nocontactsreturned', 'warning');
+    return false;
+    }
+
+  // add id to message list table if not specified
+  if (!strlen($attrib['id']))
+    $attrib['id'] = 'ldapAddressList';
+  
+  // define table class
+  $attrib['class'] = 'records-table';
+  $attrib['cellspacing'] = 0;
+
+  // define list of cols to be displayed
+  $a_show_cols = array('name', 'email');
+
+  // create XHTML table  
+  $out = rcube_table_output($attrib, $contacts, $a_show_cols, 'row_id');
+
+  // set client env
+  $javascript = "$JS_OBJECT_NAME.gui_object('ldapcontactslist', '{$attrib['id']}');\n";
+  $OUTPUT->add_script($javascript);  
+  
+  return $out;  
+  }
+
+/**
+ * perform search for contacts from given public ldap server
+ */
+function rcmail_ldap_contact_search($search_value, $search_field, $server, $exact=true)
+  {
+  global $CONFIG;
+  
+  $attributes = array($server['name_field'], $server['mail_field']); 
+
+  $LDAP = new rcube_ldap();
+  if ($LDAP->connect($server['hosts'], $server['port'], $server['protocol']))
+    {
+    $filter = "$search_field=" . ($exact ? $search_value : "*$search_value*"); 
+    $result = $LDAP->search($server['base_dn'],
+                            $filter, 
+                            $attributes, 
+                            $server['scope'], 
+                            $sort=null);
+         
+    // add any results to contact array
+    if ($result['count'])
+      {
+      for ($n = 0; $n < $result['count']; $n++)
+        {
+        $contacts[$n]['name']   = $result[$n][$server['name_field']][0];
+        $contacts[$n]['email']  = $result[$n][$server['mail_field']][0];
+        $contacts[$n]['row_id'] = $n + 1;
+        }
+      }
+    }
+  else
+    return false;
+
+  // cleanup
+  $LDAP->close();
+
+  if (!$result['count'])
+    return false;
+  // weed out duplicate emails
+  for ($n = 0; $n < $result['count']; $n++)
+    for ($i = 0; $i < $result['count']; $i++)
+      if ($contacts[$i]['email'] == $contacts[$n]['email'] && $i != $n)
+        unset($contacts[$n]);
+
+  return $contacts;
+  }
+
+function get_form_tags($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
+
+  $form_start = '';
+  if (!strlen($EDIT_FORM))
+    {
+    $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
+    $hiddenfields->add(array('name' => '_action', 'value' => 'ldappublicsearch'));
+    
+    if ($_framed)
+      $hiddenfields->add(array('name' => '_framed', 'value' => 1));
+    
+    $form_start .= !strlen($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
+    $form_start .= "\n$SESS_HIDDEN_FIELD\n";
+    $form_start .= $hiddenfields->show();
+    }
+    
+  $form_end = (strlen($EDIT_FORM) && !strlen($attrib['form'])) ? '</form>' : '';
+  $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
+  
+  $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('ldappublicsearchform', '$form_name');");
+  
+  $EDIT_FORM = $form_name;
+
+  return array($form_start, $form_end);  
+  }
+
+parse_template('ldappublicsearch');
+?>
diff --git a/program/steps/addressbook/list.inc b/program/steps/addressbook/list.inc
new file mode 100644 (file)
index 0000000..78a69c4
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/list.inc                                    |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Send contacts list to client (as remote response)                   |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: list.inc 88 2005-12-03 16:54:12Z roundcube $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+// count contacts for this user
+$sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
+                          FROM ".get_table_name('contacts')."
+                          WHERE  del<>1
+                          AND    user_id=?",
+                          $_SESSION['user_id']);
+                                   
+$sql_arr = $DB->fetch_assoc($sql_result);
+$rowcount = $sql_arr['rows'];    
+
+// update message count display
+$pages = ceil($rowcount/$CONFIG['pagesize']);
+$commands = sprintf("this.set_rowcount('%s');\n", rcmail_get_rowcount_text($rowcount));
+$commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
+
+$start_row = ($CONTACTS_LIST['page']-1) * $CONFIG['pagesize'];
+
+// get contacts from DB
+$sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
+                               WHERE  del<>1
+                               AND    user_id=?
+                               ORDER BY name",
+                               $start_row,
+                               $CONFIG['pagesize'],
+                               $_SESSION['user_id']);
+                                 
+$commands .= rcmail_js_contacts_list($sql_result);
+  
+// send response
+rcube_remote_response($commands);
+
+exit;
+?>
\ No newline at end of file
diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc
new file mode 100644 (file)
index 0000000..1627d07
--- /dev/null
@@ -0,0 +1,260 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/save.inc                                    |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Save a contact entry or to add a new one                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: save.inc 159 2006-03-03 16:34:35Z roundcube $
+
+*/
+
+// check input
+if ((empty($_POST['_name']) || empty($_POST['_email'])) && empty($_GET['_framed']))
+  {
+  show_message('formincomplete', 'warning');
+  rcmail_overwrite_action(empty($_POST['_cid']) ? 'add' : 'show');
+  return;
+  }
+
+// setup some vars we need
+$a_save_cols = array('name', 'firstname', 'surname', 'email');
+$contacts_table = get_table_name('contacts');
+
+// update an existing contact
+if (!empty($_POST['_cid']))
+  {
+  $a_write_sql = array();
+
+  foreach ($a_save_cols as $col)
+    {
+    $fname = '_'.$col;
+    if (!isset($_POST[$fname]))
+      continue;
+    
+    $a_write_sql[] = sprintf("%s=%s",
+                             $DB->quoteIdentifier($col),
+                             $DB->quote(get_input_value($fname, RCUBE_INPUT_POST)));
+    }
+
+  if (sizeof($a_write_sql))
+    {
+    $DB->query("UPDATE $contacts_table
+                SET    changed=now(), ".join(', ', $a_write_sql)."
+                WHERE  contact_id=?
+                AND    user_id=?
+                AND    del<>1",
+                $_POST['_cid'],
+                $_SESSION['user_id']);
+                       
+    $updated = $DB->affected_rows();
+    }
+       
+  if ($updated)
+    {
+    $_action = 'show';
+    show_message('successfullysaved', 'confirmation');    
+    
+    if ($_framed)
+      {
+      // define list of cols to be displayed
+      $a_show_cols = array('name', 'email');
+      $a_js_cols = array();
+  
+      $sql_result = $DB->query("SELECT * FROM $contacts_table
+                                WHERE  contact_id=?
+                                AND    user_id=?
+                                AND    del<>1",
+                               $_POST['_cid'],
+                               $_SESSION['user_id']);
+                         
+      $sql_arr = $DB->fetch_assoc($sql_result);
+      foreach ($a_show_cols as $col)
+        $a_js_cols[] = (string)$sql_arr[$col];
+
+      // update the changed col in list
+      $OUTPUT->add_script(sprintf("if(parent.%s)parent.%s.update_contact_row('%d', %s);",
+                          $JS_OBJECT_NAME,
+                          $JS_OBJECT_NAME,
+                          $_POST['_cid'],
+                          array2js($a_js_cols)));
+
+      // show confirmation
+      show_message('successfullysaved', 'confirmation');
+      }
+    }
+  else
+    {
+    // show error message
+    show_message('errorsaving', 'error');
+    rcmail_overwrite_action('show');
+    }
+  }
+
+// insert a new contact
+else
+  {
+  $a_insert_cols = $a_insert_values = array();
+
+  // check for existing contacts
+  $sql = "SELECT 1 FROM $contacts_table
+          WHERE  user_id = {$_SESSION['user_id']}
+          AND del <> '1' ";
+
+  // get email and name, build sql for existing user check
+  if (isset($_GET['_emails']) && isset($_GET['_names']))
+    {
+    $sql   .= "AND email IN (";
+    $emails = explode(',', get_input_value('_emails', RCUBE_INPUT_GET));
+    $names  = explode(',', get_input_value('_names', RCUBE_INPUT_GET));
+    $count  = count($emails);
+    $n = 0;
+    foreach ($emails as $email)
+      {
+      $end  = (++$n == $count) ? '' : ',';
+      $sql .= $DB->quote($email) . $end;
+      }
+    $sql .= ")";
+    $ldap_form = true; 
+    }
+  else if (isset($_POST['_email'])) 
+    $sql  .= "AND email = " . $DB->quote(get_input_value('_email', RCUBE_INPUT_POST));
+
+  $sql_result = $DB->query($sql);
+
+  // show warning message
+  if ($DB->num_rows($sql_result))
+    {
+    show_message('contactexists', 'warning');
+
+    if ($ldap_form)
+      rcmail_overwrite_action('ldappublicsearch');
+    else
+      rcmail_overwrite_action('add');
+
+    return;
+    }
+
+  if ($ldap_form)
+    {
+    $n = 0; 
+    foreach ($emails as $email) 
+      {
+      $DB->query("INSERT INTO $contacts_table 
+                 (user_id, name, email
+                 VALUES ({$_SESSION['user_id']}," . $DB->quote($names[$n++]) . "," . 
+                                      $DB->quote($email) . ")");
+      $insert_id[] = $DB->insert_id();
+      }
+    }
+  else
+    {
+    foreach ($a_save_cols as $col)
+      {
+      $fname = '_'.$col;
+      if (!isset($_POST[$fname]))
+        continue;
+    
+      $a_insert_cols[] = $col;
+      $a_insert_values[] = $DB->quote(get_input_value($fname, RCUBE_INPUT_POST));
+      }
+    
+    if (sizeof($a_insert_cols))
+      {
+      $DB->query("INSERT INTO $contacts_table
+                (user_id, changed, del, ".join(', ', $a_insert_cols).")
+                VALUES (?, now(), 0, ".join(', ', $a_insert_values).")",
+                $_SESSION['user_id']);
+                       
+      $insert_id = $DB->insert_id(get_sequence_name('contacts'));
+      }
+    }
+    
+  if ($insert_id)
+    {
+    if (!$ldap_form)
+      {
+      $_action = 'show';
+      $_GET['_cid'] = $insert_id;
+
+      if ($_framed)
+        {
+        // add contact row or jump to the page where it should appear
+        $commands = sprintf("if(parent.%s)parent.", $JS_OBJECT_NAME);
+        $sql_result = $DB->query("SELECT * FROM $contacts_table
+                                  WHERE  contact_id=?
+                                  AND    user_id=?",
+                                  $insert_id,
+                                  $_SESSION['user_id']);
+        $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
+
+        $commands .= sprintf("if(parent.%s)parent.%s.select('%d');\n",
+                             $JS_OBJECT_NAME, 
+                             $JS_OBJECT_NAME,
+                             $insert_id);
+      
+        // update record count display
+        $commands .= sprintf("if(parent.%s)parent.%s.set_rowcount('%s');\n",
+                             $JS_OBJECT_NAME, 
+                             $JS_OBJECT_NAME,
+                             rcmail_get_rowcount_text());
+
+        $OUTPUT->add_script($commands);
+        }
+
+      // show confirmation
+      show_message('successfullysaved', 'confirmation');      
+      }
+    else 
+      {
+      // add contact row or jump to the page where it should appear
+      $commands = '';
+      foreach ($insert_id as $id) 
+        {
+        $sql_result = $DB->query("SELECT * FROM $contacts_table
+                                  WHERE  contact_id = $id
+                                  AND    user_id    = {$_SESSION['user_id']}");
+        
+        $commands .= sprintf("if(parent.%s)parent.", $JS_OBJECT_NAME);
+        $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
+        $last_id = $id;
+        }
+
+      // display the last insert id
+      $commands .= sprintf("if(parent.%s)parent.%s.select('%d');\n",
+                            $JS_OBJECT_NAME, 
+                            $JS_OBJECT_NAME,
+                            $last_id);
+
+      // update record count display
+      $commands .= sprintf("if(parent.%s)parent.%s.set_rowcount('%s');\n",
+                           $JS_OBJECT_NAME, 
+                           $JS_OBJECT_NAME,
+                           rcmail_get_rowcount_text());
+
+      $OUTPUT->add_script($commands);
+      rcmail_overwrite_action('ldappublicsearch');
+      }
+
+    // show confirmation
+    show_message('successfullysaved', 'confirmation');      
+    }
+  else
+    {
+    // show error message
+    show_message('errorsaving', 'error');
+    rcmail_overwrite_action('add');
+    }
+  }
+
+?>
diff --git a/program/steps/addressbook/show.inc b/program/steps/addressbook/show.inc
new file mode 100644 (file)
index 0000000..a779b62
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/show.inc                                    |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Show contact details                                                |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: show.inc 88 2005-12-03 16:54:12Z roundcube $
+
+*/
+
+
+if ($_GET['_cid'] || $_POST['_cid'])
+  {
+  $cid = $_POST['_cid'] ? $_POST['_cid'] : $_GET['_cid'];
+  $DB->query("SELECT * FROM ".get_table_name('contacts')."
+              WHERE  contact_id=?
+              AND    user_id=?
+              AND    del<>1",
+              $cid,
+              $_SESSION['user_id']);
+  
+  $CONTACT_RECORD = $DB->fetch_assoc();
+  
+  if (is_array($CONTACT_RECORD))
+    $OUTPUT->add_script(sprintf("%s.set_env('cid', '%s');", $JS_OBJECT_NAME, $CONTACT_RECORD['contact_id']));
+  }
+
+
+
+function rcmail_contact_details($attrib)
+  {
+  global $CONTACT_RECORD, $JS_OBJECT_NAME;
+
+  if (!$CONTACT_RECORD)
+    return show_message('contactnotfound');
+  
+  // a specific part is requested
+  if ($attrib['part'])
+    return rep_specialchars_output($CONTACT_RECORD[$attrib['part']]);
+
+
+  // return the complete address record as table
+  $out = "<table>\n\n";
+
+  $a_show_cols = array('name', 'firstname', 'surname', 'email');
+  foreach ($a_show_cols as $col)
+    {
+    if ($col=='email' && $CONTACT_RECORD[$col])
+      $value = sprintf('<a href="#compose" onclick="%s.command(\'compose\', %d)" title="%s">%s</a>',
+                       $JS_OBJECT_NAME,
+                       $CONTACT_RECORD['contact_id'],
+                       rcube_label('composeto'),
+                       $CONTACT_RECORD[$col]);
+    else
+      $value = rep_specialchars_output($CONTACT_RECORD[$col]);
+    
+    $title = rcube_label($col);
+    $out .= sprintf("<tr><td class=\"title\">%s</td><td>%s</td></tr>\n", $title, $value);
+    }
+
+
+  $out .= "\n</table>";
+  
+  return $out;  
+  }
+
+
+parse_template('showcontact');
+?>
\ No newline at end of file
diff --git a/program/steps/error.inc b/program/steps/error.inc
new file mode 100644 (file)
index 0000000..03dd324
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/error.inc                                               |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Display error message page                                          |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: error.inc 50 2005-10-21 12:12:23Z roundcube $
+
+*/
+
+
+// browser is not compatible with this application
+if ($ERROR_CODE==409)
+  {
+  $user_agent = $GLOBALS['HTTP_SERVER_VARS']['HTTP_USER_AGENT'];
+  $__error_title = 'Your browser does not suit the requirements for this application';
+  $__error_text = <<<EOF
+<i>Supported browsers:</i><br />
+&raquo; &nbsp;Netscape 7+<br />
+&raquo; &nbsp;Microsoft Internet Explorer 6+<br />
+&raquo; &nbsp;Mozilla Firefox 1.0+<br />
+&raquo; &nbsp;Opera 8.0+<br />
+&raquo; &nbsp;Safari 1.2+<br />
+<br />
+&raquo; &nbsp;JavaScript enabled<br />
+&raquo; &nbsp;Support for XMLHTTPRequest<br />
+
+<p><i>Your configuration:</i><br />
+$user_agent</p>
+EOF;
+  }
+
+// authorization error
+else if ($ERROR_CODE==401)
+  {
+  $__error_title = "AUTHORIZATION FAILED";
+  $__error_text  = "Could not verify that you are authorized to access this service!<br />\n".
+                   "Please contact your server-administrator.";
+  }
+  
+// failed request (wrong step in URL)
+else if ($ERROR_CODE==404)
+  {
+  $__error_title = "REQUEST FAILED/FILE NOT FOUND";
+  $request_url = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
+  $__error_text  = <<<EOF
+The requested page was not found!<br />
+Please contact your server-administrator.
+
+<p><i>Failed request:</i><br />
+http://$request_url</p>
+EOF;
+  }
+
+
+// system error
+else
+  {
+  $__error_title = "SERVICE CURRENTLY NOT AVAILABLE!";
+  $__error_text  = "Please contact your server-administrator.";
+  
+  if (($CONFIG['debug_level'] & 4) && $ERROR_MESSAGE)
+    $__error_text = $ERROR_MESSAGE;
+  else
+    $__error_text = 'Error No. '.dechex($ERROR_CODE).')';
+  }
+
+
+// compose page content
+
+$__page_content = <<<EOF
+<div>
+<h3 class="error-title">$__error_title</h3>
+<p class="error-text">$__error_text</p>
+</div>
+EOF;
+
+
+
+if (template_exists('error'))
+  {
+  $OUTPUT->scripts = array();
+  $OUTPUT->script_files = array();
+  parse_template('error');
+  }
+
+
+// print system error page
+print <<<EOF
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+<title>RoundCube|Mail : ERROR $ERROR_CODE</title>
+<link rel="stylesheet" type="text/css" href="program/style.css" />
+</head>
+<body>
+
+<table border="0" cellsapcing="0" cellpadding="0" width="100%" height="80%"><tr><td align="center">
+
+$__page_content
+
+</td></tr></table>
+
+</body>
+</html>
+EOF;
+
+?>
\ No newline at end of file
diff --git a/program/steps/mail/addcontact.inc b/program/steps/mail/addcontact.inc
new file mode 100644 (file)
index 0000000..afb3279
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/addcontact.inc                                     |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Add the submitted contact to the users address book                 |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: addcontact.inc 160 2006-03-03 16:36:22Z roundcube $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+if (!empty($_GET['_address']))
+  {
+  $contact_arr = $IMAP->decode_address_list(get_input_value('_address', RCUBE_INPUT_GET, TRUE));
+  if (sizeof($contact_arr))
+    {
+    $contact = $contact_arr[1];
+
+    if ($contact['mailto'])
+      $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
+                                WHERE  user_id=?
+                                AND    email=?
+                                AND    del<>1",
+                                $_SESSION['user_id'],$contact['mailto']);
+
+    // contact entry with this mail address exists
+    if ($sql_result && $DB->num_rows($sql_result))
+      $existing_contact = TRUE;
+
+    else if ($contact['mailto'])
+      {
+      $DB->query("INSERT INTO ".get_table_name('contacts')."
+                  (user_id, changed, del, name, email)
+                  VALUES (?, now(), 0, ?, ?)",
+                  $_SESSION['user_id'],
+                  $contact['name'],
+                  $contact['mailto']);
+
+      $added = $DB->insert_id(get_sequence_name('contacts'));
+      }
+    }
+
+  if ($added)
+    $commands = show_message('addedsuccessfully', 'confirmation');
+  else if ($existing_contact)
+    $commands = show_message('contactexists', 'warning');
+  }
+
+
+if (!$commands)
+  $commands = show_message('errorsavingcontact', 'warning');
+
+rcube_remote_response($commands);  
+exit;
+?>
\ No newline at end of file
diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc
new file mode 100644 (file)
index 0000000..5846699
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/check_recent.inc                                   |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Check for recent messages, in all mailboxes                         |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: check_recent.inc 233 2006-06-26 17:31:20Z richs $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+$a_mailboxes = $IMAP->list_mailboxes();
+
+foreach ($a_mailboxes as $mbox_name)
+  {
+  if ($mbox_name == $IMAP->get_mailbox_name())
+    {
+    if ($recent_count = $IMAP->messagecount(NULL, 'RECENT', TRUE))
+      {
+      $count = $IMAP->messagecount();
+      $unread_count = $IMAP->messagecount(NULL, 'UNSEEN', TRUE);
+
+      $commands .= sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_name), $unread_count);
+      $commands .= sprintf("this.set_env('messagecount', %d);\n", $count);
+      $commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text());
+      $commands .= sprintf("this.set_quota('%s');\n", $IMAP->get_quota());
+
+      // add new message headers to list
+      $a_headers = array();
+      for ($i=$recent_count, $id=$count-$recent_count+1; $i>0; $i--, $id++)
+        {
+        $header = $IMAP->get_headers($id, NULL, FALSE);
+        if ($header->recent)
+          $a_headers[] = $header;
+        }
+
+      $commands .= rcmail_js_message_list($a_headers, TRUE);
+      }
+    }
+  else
+    {
+    if ($IMAP->messagecount($mbox_name, 'RECENT'))
+      $commands .= sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_name), $IMAP->messagecount($mbox_name, 'UNSEEN'));
+    }
+  }
+
+rcube_remote_response($commands);
+?>
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
new file mode 100644 (file)
index 0000000..bab8d3d
--- /dev/null
@@ -0,0 +1,798 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/compose.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Compose a new mail message with all headers and attachments         |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: compose.inc 297 2006-08-06 15:55:11Z thomasb $
+
+*/
+
+
+require_once('Mail/mimeDecode.php');
+
+// remove an attachment
+if ($_action=='remove-attachment' && preg_match('/^rcmfile([0-9]+)$/', $_GET['_file'], $regs))
+  {
+  $id = $regs[1];
+  if (is_array($_SESSION['compose']['attachments'][$id]))
+    {
+    @unlink($_SESSION['compose']['attachments'][$id]['path']);
+    $_SESSION['compose']['attachments'][$id] = NULL;
+    $commands = sprintf("parent.%s.remove_from_attachment_list('rcmfile%d');\n", $JS_OBJECT_NAME, $id);
+    rcube_remote_response($commands);  
+    exit;
+    }
+  }
+
+
+$MESSAGE_FORM = NULL;
+$REPLY_MESSAGE = NULL;
+$FORWARD_MESSAGE = NULL;
+$DRAFT_MESSAGE = NULL;
+
+// nothing below is called during message composition, only at "new/forward/reply/draft" initialization
+// since there are many ways to leave the compose page improperly, it seems necessary to clean-up an old
+// compose when a "new/forward/reply/draft" is called - otherwise the old session attachments will appear
+
+rcmail_compose_cleanup();
+$_SESSION['compose'] = array('id' => uniqid(rand()));
+
+// add some labels to client
+rcube_add_label('nosubject', 'norecipientwarning', 'nosubjectwarning', 'nobodywarning', 'notsentwarning', 'savingmessage', 'sendingmessage', 'messagesaved');
+
+
+if ($_GET['_reply_uid'] || $_GET['_forward_uid'] || $_GET['_draft_uid'])
+  {
+  $msg_uid = ($_GET['_reply_uid'] ? $_GET['_reply_uid'] : ($_GET['_forward_uid'] ? $_GET['_forward_uid'] : $_GET['_draft_uid']));
+
+  // similar as in program/steps/mail/show.inc
+  $MESSAGE = array();
+  $MESSAGE['headers'] = $IMAP->get_headers($msg_uid);
+  
+  $MESSAGE['source'] = rcmail_message_source($msg_uid);
+  
+  $mmd = new Mail_mimeDecode($MESSAGE['source']);
+  $MESSAGE['structure'] = $mmd->decode(array('include_bodies' => TRUE,
+                                             'decode_headers' => TRUE,
+                                             'decode_bodies' => FALSE));
+
+  $MESSAGE['subject'] = $IMAP->decode_header($MESSAGE['headers']->subject);
+  $MESSAGE['parts'] = $mmd->getMimeNumbers($MESSAGE['structure']);
+
+  if ($_GET['_reply_uid'])
+    {
+    $REPLY_MESSAGE = &$MESSAGE;
+    $_SESSION['compose']['reply_uid'] = $_GET['_reply_uid'];
+    $_SESSION['compose']['reply_msgid'] = $REPLY_MESSAGE['headers']->messageID;
+    $_SESSION['compose']['references']  = $REPLY_MESSAGE['headers']->reference;
+    $_SESSION['compose']['references'] .= !empty($REPLY_MESSAGE['headers']->reference) ? ' ' : '';
+    $_SESSION['compose']['references'] .= $REPLY_MESSAGE['headers']->messageID;
+
+    if ($_GET['_all'])
+      $REPLY_MESSAGE['reply_all'] = 1;
+
+    }
+  else if ($_GET['_forward_uid'])
+    {
+    $FORWARD_MESSAGE = $MESSAGE;
+    $_SESSION['compose']['forward_uid'] = $_GET['_forward_uid'];
+    }
+  else
+    {
+    $DRAFT_MESSAGE = $MESSAGE;
+    $_SESSION['compose']['draft_uid'] = $_GET['_draft_uid'];
+    }
+
+  }
+
+/****** compose mode functions ********/
+
+
+function rcmail_compose_headers($attrib)
+  {
+  global $IMAP, $REPLY_MESSAGE, $DRAFT_MESSAGE, $DB;
+  static $sa_recipients = array();
+
+  list($form_start, $form_end) = get_form_tags($attrib);
+  
+  $out = '';
+  $part = strtolower($attrib['part']);
+  
+  switch ($part)
+    {
+    case 'from':
+      return rcmail_compose_header_from($attrib);
+
+    case 'to':
+      $fname = '_to';
+      $header = 'to';
+
+      // we have contact id's as get parameters
+      if (!empty($_GET['_to']) && preg_match('/^[0-9]+(,[0-9]+)*$/', $_GET['_to']))
+        {
+        $a_recipients = array();
+        $sql_result = $DB->query("SELECT name, email
+                                  FROM ".get_table_name('contacts')."
+                                  WHERE user_id=?
+                                  AND    del<>1
+                                  AND    contact_id IN (".$_GET['_to'].")",
+                                  $_SESSION['user_id']);
+                                         
+        while ($sql_arr = $DB->fetch_assoc($sql_result))
+          $a_recipients[] = format_email_recipient($sql_arr['email'], $sql_arr['name']);
+          
+        if (sizeof($a_recipients))
+          $fvalue = join(', ', $a_recipients);
+        }
+      else if (!empty($_GET['_to']))
+        $fvalue = $_GET['_to'];
+        
+    case 'cc':
+      if (!$fname)
+        {
+        $fname = '_cc';
+        $header = 'cc';
+        }
+    case 'bcc':
+      if (!$fname)
+        $fname = '_bcc';
+        
+      $allow_attrib = array('id', 'class', 'style', 'cols', 'rows', 'wrap', 'tabindex');
+      $field_type = 'textarea';            
+      break;
+
+    case 'replyto':
+    case 'reply-to':
+      $fname = '_replyto';
+      $allow_attrib = array('id', 'class', 'style', 'size', 'tabindex');
+      $field_type = 'textfield';
+      break;
+    
+    }
+  if ($fname && !empty($_POST[$fname]))
+    $fvalue = get_input_value($fname, RCUBE_INPUT_POST, TRUE);
+  else if ($header && is_object($REPLY_MESSAGE['headers']))
+    {
+    // get recipent address(es) out of the message headers
+    if ($header=='to' && $REPLY_MESSAGE['headers']->replyto)
+      $fvalue = $IMAP->decode_header($REPLY_MESSAGE['headers']->replyto);
+
+    else if ($header=='to' && $REPLY_MESSAGE['headers']->from)
+      $fvalue = $IMAP->decode_header($REPLY_MESSAGE['headers']->from);
+
+    // add recipent of original message if reply to all
+    else if ($header=='cc' && $REPLY_MESSAGE['reply_all'])
+      {
+      if ($IMAP->decode_header($REPLY_MESSAGE['headers']->to))
+        $fvalue .= $IMAP->decode_header($REPLY_MESSAGE['headers']->to);
+
+      if ($IMAP->decode_header($REPLY_MESSAGE['headers']->cc))
+        {
+        if($fvalue)
+          $fvalue .= ', ';
+
+        $fvalue .= $IMAP->decode_header($REPLY_MESSAGE['headers']->cc);
+        }
+      }
+
+    // split recipients and put them back together in a unique way
+    if (!empty($fvalue))
+      {
+      $to_addresses = $IMAP->decode_address_list($fvalue);
+      $fvalue = '';
+      foreach ($to_addresses as $addr_part)
+        {
+        if (!in_array($addr_part['mailto'], $sa_recipients) && (!$REPLY_MESSAGE['FROM'] || !in_array($addr_part['mailto'], $REPLY_MESSAGE['FROM'])))
+          {
+          $fvalue .= (strlen($fvalue) ? ', ':'').$addr_part['string'];
+          $sa_recipients[] = $addr_part['mailto'];
+          }
+        }
+      }
+    }
+  else if ($header && is_object($DRAFT_MESSAGE['headers']))
+    {
+    // get drafted headers
+    if ($header=='to' && $DRAFT_MESSAGE['headers']->to)
+      $fvalue = $IMAP->decode_header($DRAFT_MESSAGE['headers']->to);
+
+    if ($header=='cc' && $DRAFT_MESSAGE['headers']->cc)
+      $fvalue = $IMAP->decode_header($DRAFT_MESSAGE['headers']->cc);
+
+    if ($header=='bcc' && $DRAFT_MESSAGE['headers']->bcc)
+      $fvalue = $IMAP->decode_header($DRAFT_MESSAGE['headers']->bcc);
+
+    }
+
+        
+  if ($fname && $field_type)
+    {
+    // pass the following attributes to the form class
+    $field_attrib = array('name' => $fname);
+    foreach ($attrib as $attr => $value)
+      if (in_array($attr, $allow_attrib))
+        $field_attrib[$attr] = $value;
+
+    // create teaxtarea object
+    $input = new $field_type($field_attrib);
+    $out = $input->show($fvalue);    
+    }
+  
+  if ($form_start)
+    $out = $form_start.$out;
+
+  return $out;  
+  }
+
+
+
+function rcmail_compose_header_from($attrib)
+  {
+  global $IMAP, $REPLY_MESSAGE, $DRAFT_MESSAGE, $DB, $OUTPUT, $JS_OBJECT_NAME;
+    
+  // pass the following attributes to the form class
+  $field_attrib = array('name' => '_from');
+  foreach ($attrib as $attr => $value)
+    if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex')))
+      $field_attrib[$attr] = $value;
+
+  // extract all recipients of the reply-message
+  $a_recipients = array();
+  if ($REPLY_MESSAGE && is_object($REPLY_MESSAGE['headers']))
+    {
+    $REPLY_MESSAGE['FROM'] = array();
+
+    $a_to = $IMAP->decode_address_list($REPLY_MESSAGE['headers']->to);        
+    foreach ($a_to as $addr)
+      {
+      if (!empty($addr['mailto']))
+        $a_recipients[] = $addr['mailto'];
+      }
+
+    if (!empty($REPLY_MESSAGE['headers']->cc))
+      {
+      $a_cc = $IMAP->decode_address_list($REPLY_MESSAGE['headers']->cc);
+      foreach ($a_cc as $addr)
+        {
+        if (!empty($addr['mailto']))
+          $a_recipients[] = $addr['mailto'];
+        }
+      }
+    }
+
+  // get this user's identities
+  $sql_result = $DB->query("SELECT identity_id, name, email, signature
+                            FROM   ".get_table_name('identities')."
+                            WHERE user_id=?
+                            AND    del<>1
+                            ORDER BY ".$DB->quoteIdentifier('standard')." DESC, name ASC",
+                           $_SESSION['user_id']);
+                                   
+  if ($DB->num_rows($sql_result))
+    {
+    $from_id = 0;
+    $a_signatures = array();
+    
+    $field_attrib['onchange'] = "$JS_OBJECT_NAME.change_identity(this)";
+    $select_from = new select($field_attrib);
+    
+    while ($sql_arr = $DB->fetch_assoc($sql_result))
+      {
+      $select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $sql_arr['identity_id']);
+
+      // add signature to array
+      if (!empty($sql_arr['signature']))
+        $a_signatures[$sql_arr['identity_id']] = $sql_arr['signature'];
+      
+      // set identity if it's one of the reply-message recipients
+      if (in_array($sql_arr['email'], $a_recipients))
+        $from_id = $sql_arr['identity_id'];
+        
+      if ($REPLY_MESSAGE && is_array($REPLY_MESSAGE['FROM']))
+        $REPLY_MESSAGE['FROM'][] = $sql_arr['email'];
+
+      if (strstr($DRAFT_MESSAGE['headers']->from,$sql_arr['email']))
+        $from_id = $sql_arr['identity_id'];
+
+      }
+
+    // overwrite identity selection with post parameter
+    if (isset($_POST['_from']))
+      $from_id = $_POST['_from'];
+
+    $out = $select_from->show($from_id);
+    
+
+    // add signatures to client
+    $OUTPUT->add_script(sprintf("%s.set_env('signatures', %s);", $JS_OBJECT_NAME, array2js($a_signatures)));  
+    }
+  else
+    {
+    $input_from = new textfield($field_attrib);
+    $out = $input_from->show($_POST['_from']);
+    }
+  
+  if ($form_start)
+    $out = $form_start.$out;
+
+  return $out;
+  }
+
+  
+
+function rcmail_compose_body($attrib)
+  {
+  global $CONFIG, $OUTPUT, $REPLY_MESSAGE, $FORWARD_MESSAGE, $DRAFT_MESSAGE, $JS_OBJECT_NAME;
+  
+  list($form_start, $form_end) = get_form_tags($attrib);
+  unset($attrib['form']);
+  
+  if (empty($attrib['id']))
+    $attrib['id'] = 'rcmComposeMessage';
+  
+  $attrib['name'] = '_message';
+  $textarea = new textarea($attrib);
+
+  $body = '';
+  
+  // use posted message body
+  if (!empty($_POST['_message']))
+    $body = get_input_value('_message', RCUBE_INPUT_POST, TRUE);
+    
+  // compose reply-body
+  else if (is_array($REPLY_MESSAGE['parts']))
+    {
+    $body = rcmail_first_text_part($REPLY_MESSAGE['parts']);
+    if (strlen($body))
+      $body = rcmail_create_reply_body($body);
+    }
+
+  // forward message body inline
+  else if (is_array($FORWARD_MESSAGE['parts']))
+    {
+    $body = rcmail_first_text_part($FORWARD_MESSAGE['parts']);
+    if (strlen($body))
+      $body = rcmail_create_forward_body($body);
+    }
+
+  // forward message body inline
+  else if (is_array($DRAFT_MESSAGE['parts']))
+    {
+    $body = rcmail_first_text_part($DRAFT_MESSAGE['parts']);
+    if (strlen($body))
+      $body = rcmail_create_draft_body($body);
+    }
+  
+  $out = $form_start ? "$form_start\n" : '';
+
+  $saveid = new hiddenfield(array('name' => '_draft_saveid', 'value' => str_replace(array('<','>'),"",$DRAFT_MESSAGE['headers']->messageID) ));
+  $out .= $saveid->show();
+
+  $drafttoggle = new hiddenfield(array('name' => '_draft', 'value' => 'yes'));
+  $out .= $drafttoggle->show();
+
+  $out .= $textarea->show($body);
+  $out .= $form_end ? "\n$form_end" : '';
+  
+  // include GoogieSpell
+  if (!empty($CONFIG['enable_spellcheck']))
+    {
+    $OUTPUT->include_script('googiespell.js');
+    $OUTPUT->add_script(sprintf("var googie = new GoogieSpell('\$__skin_path/images/googiespell/','%s&_action=spell&lang=');\n".
+                                "googie.lang_chck_spell = \"%s\";\n".
+                                "googie.lang_rsm_edt = \"%s\";\n".
+                                "googie.lang_close = \"%s\";\n".
+                                "googie.lang_revert = \"%s\";\n".
+                                "googie.lang_no_error_found = \"%s\";\n".
+                                "googie.decorateTextarea('%s');\n".
+                                "%s.set_env('spellcheck', googie);",
+                                $GLOBALS['COMM_PATH'],
+                                rep_specialchars_output(rcube_label('checkspelling')),
+                                rep_specialchars_output(rcube_label('resumeediting')),
+                                rep_specialchars_output(rcube_label('close')),
+                                rep_specialchars_output(rcube_label('revertto')),
+                                rep_specialchars_output(rcube_label('nospellerrors')),
+                                $attrib['id'],
+                                $JS_OBJECT_NAME), 'foot');
+
+    rcube_add_label('checking');
+    }
+  $out .= "\n".'<iframe name="savetarget" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
+
+  return $out;
+  }
+
+
+function rcmail_create_reply_body($body)
+  {
+  global $IMAP, $REPLY_MESSAGE;
+
+  // soft-wrap message first
+  $body = wordwrap($body, 75);
+  
+  // split body into single lines
+  $a_lines = preg_split('/\r?\n/', $body);
+  
+  // add > to each line
+  for($n=0; $n<sizeof($a_lines); $n++)
+    {
+    if (strpos($a_lines[$n], '>')===0)
+      $a_lines[$n] = '>'.$a_lines[$n];
+    else
+      $a_lines[$n] = '> '.$a_lines[$n];
+    }
+  $body = join("\n", $a_lines);
+
+  // add title line
+  $pefix = sprintf("\n\n\nOn %s, %s wrote:\n",
+           $REPLY_MESSAGE['headers']->date,
+           $IMAP->decode_header($REPLY_MESSAGE['headers']->from));
+           
+
+  // try to remove the signature
+  if ($sp = strrpos($body, '-- '))
+    {
+    if ($body{$sp+3}==' ' || $body{$sp+3}=="\n" || $body{$sp+3}=="\r")
+      $body = substr($body, 0, $sp-1);
+    }
+
+  return $pefix.$body;
+  }
+
+
+function rcmail_create_forward_body($body)
+  {
+  global $IMAP, $FORWARD_MESSAGE;
+
+  // soft-wrap message first
+  $body = wordwrap($body, 80);
+  
+  $prefix = sprintf("\n\n\n-------- Original Message --------\nSubject: %s\nDate: %s\nFrom: %s\nTo: %s\n\n",
+                   $FORWARD_MESSAGE['subject'],
+                   $FORWARD_MESSAGE['headers']->date,
+                   $IMAP->decode_header($FORWARD_MESSAGE['headers']->from),
+                   $IMAP->decode_header($FORWARD_MESSAGE['headers']->to));
+
+  // add attachments
+  if (!isset($_SESSION['compose']['forward_attachments']) && is_array($FORWARD_MESSAGE['parts']) && sizeof($FORWARD_MESSAGE['parts'])>1)
+    {
+    $temp_dir = rcmail_create_compose_tempdir();
+
+    if (!is_array($_SESSION['compose']['attachments']))
+      $_SESSION['compose']['attachments'] = array();
+  
+    foreach ($FORWARD_MESSAGE['parts'] as $part)
+      {
+      if ($part->disposition=='attachment' || $part->disposition=='inline' || $part->headers['content-id'] ||
+               (empty($part->disposition) && ($part->d_parameters['filename'] || $part->ctype_parameters['name'])))
+        {
+        $tmp_path = tempnam($temp_dir, 'rcmAttmnt');
+        if ($fp = fopen($tmp_path, 'w'))
+          {
+          fwrite($fp, $IMAP->mime_decode($part->body, $part->headers['content-transfer-encoding']));
+          fclose($fp);
+
+          if ($part->d_parameters['filename'])
+            $_SESSION['compose']['attachments'][] = array('name' => $part->d_parameters['filename'],
+                                                          'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+                                                          'path' => $tmp_path);
+                                   
+          else if ($part->ctype_parameters['name'])
+           $_SESSION['compose']['attachments'][] = array('name' => $part->ctype_parameters['name'],
+                                                          'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+                                                          'path' => $tmp_path);
+                                                         
+          else if ($part->headers['content-description'])
+           $_SESSION['compose']['attachments'][] = array('name' => $part->headers['content-description'],
+                                                          'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+                                                          'path' => $tmp_path);
+          }
+       }
+      }
+
+    $_SESSION['compose']['forward_attachments'] = TRUE;
+    }
+
+  return $prefix.$body;
+  }
+
+function rcmail_create_draft_body($body)
+  {
+  global $IMAP, $DRAFT_MESSAGE;
+    
+  // add attachments
+  if (!isset($_SESSION['compose']['forward_attachments']) && is_array($DRAFT_MESSAGE['parts']) && sizeof($DRAFT_MESSAGE['parts'])>1)
+    { 
+    $temp_dir = rcmail_create_compose_tempdir();
+
+    if (!is_array($_SESSION['compose']['attachments']))
+      $_SESSION['compose']['attachments'] = array();
+  
+    foreach ($DRAFT_MESSAGE['parts'] as $part)
+      {
+      if ($part->disposition=='attachment' || $part->disposition=='inline' || $part->headers['content-id'] ||
+               (empty($part->disposition) && ($part->d_parameters['filename'] || $part->ctype_parameters['name'])))
+        {
+        $tmp_path = tempnam($temp_dir, 'rcmAttmnt');
+        if ($fp = fopen($tmp_path, 'w'))
+          {                     
+          fwrite($fp, $IMAP->mime_decode($part->body, $part->headers['content-transfer-encoding']));
+          fclose($fp);          
+                                
+          if ($part->d_parameters['filename'])
+            $_SESSION['compose']['attachments'][] = array('name' => $part->d_parameters['filename'],
+                                                          'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+                                                          'path' => $tmp_path);
+
+          else if ($part->ctype_parameters['name'])
+            $_SESSION['compose']['attachments'][] = array('name' => $part->ctype_parameters['name'],
+                                                          'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+                                                          'path' => $tmp_path);
+
+          else if ($part->headers['content-description'])
+            $_SESSION['compose']['attachments'][] = array('name' => $part->headers['content-description'],
+                                                          'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+                                                          'path' => $tmp_path);
+          }
+        }
+      }
+
+    $_SESSION['compose']['forward_attachments'] = TRUE;
+    }
+
+  return $body;
+  }
+
+
+function rcmail_compose_subject($attrib)
+  {
+  global $CONFIG, $REPLY_MESSAGE, $FORWARD_MESSAGE, $DRAFT_MESSAGE;
+  
+  list($form_start, $form_end) = get_form_tags($attrib);
+  unset($attrib['form']);
+  
+  $attrib['name'] = '_subject';
+  $textfield = new textfield($attrib);
+
+  $subject = '';
+
+  // use subject from post
+  if (isset($_POST['_subject']))
+    $subject = get_input_value('_subject', RCUBE_INPUT_POST, TRUE);
+    
+  // create a reply-subject
+  else if (isset($REPLY_MESSAGE['subject']))
+    {
+    if (eregi('^re:', $REPLY_MESSAGE['subject']))
+      $subject = $REPLY_MESSAGE['subject'];
+    else
+      $subject = 'Re: '.$REPLY_MESSAGE['subject'];
+    }
+
+  // create a forward-subject
+  else if (isset($FORWARD_MESSAGE['subject']))
+    {
+    if (eregi('^fwd:', $REPLY_MESSAGE['subject']))
+      $subject = $FORWARD_MESSAGE['subject'];
+    else
+      $subject = 'Fwd: '.$FORWARD_MESSAGE['subject'];
+    }
+
+  // creeate a draft-subject
+  else if (isset($DRAFT_MESSAGE['subject']))
+    $subject = $DRAFT_MESSAGE['subject'];
+  
+  $out = $form_start ? "$form_start\n" : '';
+  $out .= $textfield->show($subject);
+  $out .= $form_end ? "\n$form_end" : '';
+         
+  return $out;
+  }
+
+
+function rcmail_compose_attachment_list($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+  
+  // add ID if not given
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmAttachmentList';
+  
+  // allow the following attributes to be added to the <ul> tag
+  $attrib_str = create_attrib_string($attrib, array('id', 'class', 'style'));
+  $out = '<ul'. $attrib_str . ">\n";
+  
+  if (is_array($_SESSION['compose']['attachments']))
+    {
+    if ($attrib['deleteicon'])
+      $button = sprintf('<img src="%s%s" alt="%s" border="0" style="padding-right:2px;vertical-align:middle" />',
+                        $CONFIG['skin_path'],
+                        $attrib['deleteicon'],
+                        rcube_label('delete'));
+    else
+      $button = rcube_label('delete');
+
+    foreach ($_SESSION['compose']['attachments'] as $id => $a_prop)
+      $out .= sprintf('<li id="rcmfile%d"><a href="#delete" onclick="return %s.command(\'remove-attachment\',\'rcmfile%d\', this)" title="%s">%s</a>%s</li>',
+                      $id,
+                      $JS_OBJECT_NAME,
+                      $id,
+                      rcube_label('delete'), 
+                      $button,
+                      rep_specialchars_output($a_prop['name']));
+    }
+
+  $OUTPUT->add_script(sprintf("%s.gui_object('attachmentlist', '%s');", $JS_OBJECT_NAME, $attrib['id']));  
+    
+  $out .= '</ul>';
+  return $out;
+  }
+
+
+
+function rcmail_compose_attachment_form($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME, $SESS_HIDDEN_FIELD;
+
+  // add ID if not given
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmUploadbox';
+  
+  // allow the following attributes to be added to the <div> tag
+  $attrib_str = create_attrib_string($attrib, array('id', 'class', 'style'));
+  $input_field = rcmail_compose_attachment_field(array());
+  $label_send = rcube_label('upload');
+  $label_close = rcube_label('close');
+  
+  $out = <<<EOF
+<div$attrib_str>
+<form action="./" method="post" enctype="multipart/form-data">
+$SESS_HIDDEN_FIELD
+$input_field<br />
+<input type="button" value="$label_close" class="button" onclick="document.getElementById('$attrib[id]').style.visibility='hidden'" />
+<input type="button" value="$label_send" class="button" onclick="$JS_OBJECT_NAME.command('send-attachment', this.form)" />
+</form>
+</div>
+EOF;
+
+  
+  $OUTPUT->add_script(sprintf("%s.gui_object('uploadbox', '%s');", $JS_OBJECT_NAME, $attrib['id']));  
+  return $out;
+  }
+
+
+function rcmail_compose_attachment_field($attrib)
+  {
+  // allow the following attributes to be added to the <input> tag
+  $attrib_str = create_attrib_string($attrib, array('id', 'class', 'style', 'size'));
+  $out = '<input type="file" name="_attachments[]"'. $attrib_str . " />";
+  return $out;
+  }
+
+
+function rcmail_priority_selector($attrib)
+  {
+  list($form_start, $form_end) = get_form_tags($attrib);
+  unset($attrib['form']);
+  
+  $attrib['name'] = '_priority';
+  $selector = new select($attrib);
+
+  $selector->add(array(rcube_label('lowest'),
+                       rcube_label('low'),
+                       rcube_label('normal'),
+                       rcube_label('high'),
+                       rcube_label('highest')),
+                 array(5, 4, 0, 2, 1));
+                 
+  $sel = isset($_POST['_priority']) ? $_POST['_priority'] : 0;
+
+  $out = $form_start ? "$form_start\n" : '';
+  $out .= $selector->show($sel);
+  $out .= $form_end ? "\n$form_end" : '';
+         
+  return $out;
+  }
+
+
+function rcmail_receipt_checkbox($attrib)
+  {
+  list($form_start, $form_end) = get_form_tags($attrib);
+  unset($attrib['form']);
+  
+  if (!isset($attrib['id']))
+    $attrib['id'] = 'receipt';  
+
+  $attrib['name'] = '_receipt';
+  $attrib['value'] = '1';
+  $checkbox = new checkbox($attrib);
+
+  $out = $form_start ? "$form_start\n" : '';
+  $out .= $checkbox->show(0);
+  $out .= $form_end ? "\n$form_end" : '';
+
+  return $out;
+  }
+
+
+function get_form_tags($attrib)
+  {
+  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $MESSAGE_FORM, $SESS_HIDDEN_FIELD;  
+
+  $form_start = '';
+  if (!strlen($MESSAGE_FORM))
+    {
+    $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
+    $hiddenfields->add(array('name' => '_action', 'value' => 'send'));
+
+    $form_start = empty($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
+    $form_start .= "\n$SESS_HIDDEN_FIELD\n";
+    $form_start .= $hiddenfields->show();
+    }
+    
+  $form_end = (strlen($MESSAGE_FORM) && !strlen($attrib['form'])) ? '</form>' : '';
+  $form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
+  
+  if (!strlen($MESSAGE_FORM))
+    $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('messageform', '$form_name');");
+  
+  $MESSAGE_FORM = $form_name;
+
+  return array($form_start, $form_end);  
+  }
+
+
+function format_email_recipient($email, $name='')
+  {
+  if ($name && $name != $email)
+    return sprintf('%s <%s>', strpos($name, ",") ? '"'.$name.'"' : $name, $email);
+  else
+    return $email;
+  }
+
+
+function rcmail_charset_pulldown($selected='ISO-8859-1')
+  {
+  $select = new select();
+  
+  
+  return $select->show($selected);
+  }
+
+
+/****** get contacts for this user and add them to client scripts ********/
+
+$sql_result = $DB->query("SELECT name, email
+                          FROM ".get_table_name('contacts')." WHERE  user_id=?
+                          AND  del<>1",$_SESSION['user_id']);
+                                   
+if ($DB->num_rows($sql_result))
+  {        
+  $a_contacts = array();
+  while ($sql_arr = $DB->fetch_assoc($sql_result))
+    if ($sql_arr['email'])
+      $a_contacts[] = format_email_recipient($sql_arr['email'], rep_specialchars_output($sql_arr['name'], 'js'));
+  
+  $OUTPUT->add_script(sprintf("$JS_OBJECT_NAME.set_env('contacts', %s);", array2js($a_contacts)));
+  }
+
+
+parse_template('compose');
+?>
diff --git a/program/steps/mail/folders.inc b/program/steps/mail/folders.inc
new file mode 100644 (file)
index 0000000..3faf0f8
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/folders.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Implement folder operations line EXPUNGE and Clear                  |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: folders.inc 232 2006-05-18 15:46:50Z cmcnulty $
+*/
+
+$REMOTE_REQUEST = TRUE;
+$mbox_name = $IMAP->get_mailbox_name();
+
+
+// send EXPUNGE command
+if ($_action=='expunge')
+  {
+  $success = $IMAP->expunge($_GET['_mbox']);
+
+  // reload message list if current mailbox  
+  if ($success && $_GET['_reload'])
+    {
+    rcube_remote_response('this.clear_message_list();', TRUE);
+    $_action = 'list';
+    return;
+    }
+  else
+    $commands = "// expunged: $success\n";
+  }
+
+// clear mailbox
+else if ($_action=='purge')
+  {
+  $success = $IMAP->clear_mailbox($_GET['_mbox']);
+  
+  if ($success && $_GET['_reload'])
+    {
+    $commands = "this.clear_message_list();\n";
+    $commands .= "this.set_env('messagecount', 0);\n";
+    $commands .= "this.set_env('pagecount', 0);\n";
+    $commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text());
+    $commands .= sprintf("this.set_unread_count('%s', 0);\n", addslashes($mbox_name));
+    }
+  else
+    $commands = "// purged: $success";
+  }
+
+
+
+rcube_remote_response($commands);
+?>
\ No newline at end of file
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
new file mode 100644 (file)
index 0000000..3a971bd
--- /dev/null
@@ -0,0 +1,1528 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/func.inc                                           |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide webmail functionality and GUI objects                       |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: func.inc 429 2006-12-22 22:26:24Z thomasb $
+
+*/
+
+require_once('lib/html2text.inc');
+require_once('lib/enriched.inc');
+
+
+$EMAIL_ADDRESS_PATTERN = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i';
+
+if (empty($_SESSION['mbox'])){
+  $_SESSION['mbox'] = $IMAP->get_mailbox_name();
+}
+
+// set imap properties and session vars
+if (strlen($_GET['_mbox']))
+  {
+  $IMAP->set_mailbox($_GET['_mbox']);
+  $_SESSION['mbox'] = $_GET['_mbox'];
+  }
+
+if (strlen($_GET['_page']))
+  {
+  $IMAP->set_page($_GET['_page']);
+  $_SESSION['page'] = $_GET['_page'];
+  }
+
+// set mailbox to INBOX if not set
+if (empty($_SESSION['mbox']))
+  $_SESSION['mbox'] = $IMAP->get_mailbox_name();
+
+// set default sort col/order to session
+if (!isset($_SESSION['sort_col']))
+  $_SESSION['sort_col'] = $CONFIG['message_sort_col'];
+if (!isset($_SESSION['sort_order']))
+  $_SESSION['sort_order'] = $CONFIG['message_sort_order'];
+  
+
+// define url for getting message parts
+if (strlen($_GET['_uid']))
+  $GET_URL = sprintf('%s&_action=get&_mbox=%s&_uid=%d', $COMM_PATH, $IMAP->get_mailbox_name(), $_GET['_uid']);
+
+
+// set current mailbox in client environment
+$OUTPUT->add_script(sprintf("%s.set_env('mailbox', '%s');", $JS_OBJECT_NAME, $IMAP->get_mailbox_name()));
+
+if ($CONFIG['trash_mbox'])
+  $OUTPUT->add_script(sprintf("%s.set_env('trash_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['trash_mbox']));
+
+if ($CONFIG['drafts_mbox'])
+  $OUTPUT->add_script(sprintf("%s.set_env('drafts_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['drafts_mbox']));
+
+if ($CONFIG['junk_mbox'])
+  $OUTPUT->add_script(sprintf("%s.set_env('junk_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['junk_mbox']));
+
+// return the mailboxlist in HTML
+function rcmail_mailbox_list($attrib)
+  {
+  global $IMAP, $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $COMM_PATH;
+  static $s_added_script = FALSE;
+  static $a_mailboxes;
+
+  // add some labels to client
+  rcube_add_label('purgefolderconfirm');
+  
+// $mboxlist_start = rcube_timer();
+  
+  $type = $attrib['type'] ? $attrib['type'] : 'ul';
+  $add_attrib = $type=='select' ? array('style', 'class', 'id', 'name', 'onchange') :
+                                  array('style', 'class', 'id');
+                                  
+  if ($type=='ul' && !$attrib['id'])
+    $attrib['id'] = 'rcmboxlist';
+
+  // allow the following attributes to be added to the <ul> tag
+  $attrib_str = create_attrib_string($attrib, $add_attrib);
+  $out = '<' . $type . $attrib_str . ">\n";
+  
+  // add no-selection option
+  if ($type=='select' && $attrib['noselection'])
+    $out .= sprintf('<option value="0">%s</option>'."\n",
+                    rcube_label($attrib['noselection']));
+  
+  // get mailbox list
+  $mbox_name = $IMAP->get_mailbox_name();
+  
+  // for these mailboxes we have localized labels
+  $special_mailboxes = array('inbox', 'sent', 'drafts', 'trash', 'junk');
+
+
+  // build the folders tree
+  if (empty($a_mailboxes))
+    {
+    // get mailbox list
+    $a_folders = $IMAP->list_mailboxes();
+    $delimiter = $IMAP->get_hierarchy_delimiter();
+    $a_mailboxes = array();
+
+// rcube_print_time($mboxlist_start, 'list_mailboxes()');
+
+    foreach ($a_folders as $folder)
+      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
+    }
+
+// var_dump($a_mailboxes);
+
+  if ($type=='select')
+    $out .= rcmail_render_folder_tree_select($a_mailboxes, $special_mailboxes, $mbox_name, $attrib['maxlength']);
+   else
+    $out .= rcmail_render_folder_tree_html($a_mailboxes, $special_mailboxes, $mbox_name, $attrib['maxlength']);
+
+// rcube_print_time($mboxlist_start, 'render_folder_tree()');
+
+
+  if ($type=='ul')
+    $OUTPUT->add_script(sprintf("%s.gui_object('mailboxlist', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+
+  return $out . "</$type>";
+  }
+
+
+
+
+// create a hierarchical array of the mailbox list
+function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
+  {
+  $pos = strpos($folder, $delm);
+  if ($pos !== false)
+    {
+    $subFolders = substr($folder, $pos+1);
+    $currentFolder = substr($folder, 0, $pos);
+    }
+  else
+    {
+    $subFolders = false;
+    $currentFolder = $folder;
+    }
+
+  $path .= $currentFolder;
+
+  if (!isset($arrFolders[$currentFolder]))
+    {
+    $arrFolders[$currentFolder] = array('id' => $path,
+                                        'name' => rcube_charset_convert($currentFolder, 'UTF-7'),
+                                        'folders' => array());
+    }
+
+  if (!empty($subFolders))
+    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
+  }
+  
+
+// return html for a structured list <ul> for the mailbox tree
+function rcmail_render_folder_tree_html(&$arrFolders, &$special, &$mbox_name, $maxlength, $nestLevel=0)
+  {
+  global $JS_OBJECT_NAME, $COMM_PATH, $IMAP, $CONFIG, $OUTPUT;
+
+  $idx = 0;
+  $out = '';
+  foreach ($arrFolders as $key => $folder)
+    {
+    $zebra_class = ($nestLevel*$idx)%2 ? 'even' : 'odd';
+    $title = '';
+
+    $folder_lc = strtolower($folder['id']);
+    if (in_array($folder_lc, $special))
+      $foldername = rcube_label($folder_lc);
+    else
+      {
+      $foldername = $folder['name'];
+
+      // shorten the folder name to a given length
+      if ($maxlength && $maxlength>1)
+        {
+        $fname = abbrevate_string($foldername, $maxlength);
+        if ($fname != $foldername)
+          $title = ' title="'.rep_specialchars_output($foldername, 'html', 'all').'"';
+        $foldername = $fname;
+        }
+      }
+
+    // add unread message count display
+    if ($unread_count = $IMAP->messagecount($folder['id'], 'RECENT', ($folder['id']==$mbox_name)))
+      $foldername .= sprintf(' (%d)', $unread_count);
+
+    // make folder name safe for ids and class names
+    $folder_css = $class_name = preg_replace('/[^a-z0-9\-_]/', '', $folder_lc);
+
+    // set special class for Sent, Drafts, Trash and Junk
+    if ($folder['id']==$CONFIG['sent_mbox'])
+      $class_name = 'sent';
+    else if ($folder['id']==$CONFIG['drafts_mbox'])
+      $class_name = 'drafts';
+    else if ($folder['id']==$CONFIG['trash_mbox'])
+      $class_name = 'trash';
+    else if ($folder['id']==$CONFIG['junk_mbox'])
+      $class_name = 'junk';
+
+    $out .= sprintf('<li id="rcmbx%s" class="mailbox %s %s%s%s"><a href="%s&amp;_mbox=%s"'.
+                    ' onclick="return %s.command(\'list\',\'%s\')"'.
+                    ' onmouseover="return %s.focus_mailbox(\'%s\')"' .            
+                    ' onmouseout="return %s.unfocus_mailbox(\'%s\')"' .
+                    ' onmouseup="return %s.mbox_mouse_up(\'%s\')"%s>%s</a>',
+                    $folder_css,
+                    $class_name,
+                    $zebra_class,
+                    $unread_count ? ' unread' : '',
+                    addslashes($folder['id'])==addslashes($mbox_name) ? ' selected' : '',
+                    $COMM_PATH,
+                    urlencode($folder['id']),
+                    $JS_OBJECT_NAME,
+                    addslashes($folder['id']),
+                    $JS_OBJECT_NAME,
+                    addslashes($folder['id']),
+                    $JS_OBJECT_NAME,
+                    addslashes($folder['id']),
+                    $JS_OBJECT_NAME,
+                    addslashes($folder['id']),
+                    $title,
+                    rep_specialchars_output($foldername, 'html', 'all'));
+
+    if (!empty($folder['folders']))
+      $out .= "\n<ul>\n" . rcmail_render_folder_tree_html($folder['folders'], $special, $mbox_name, $maxlength, $nestLevel+1) . "</ul>\n";
+
+    $out .= "</li>\n";
+    $idx++;
+    }
+
+  return $out;
+  }
+
+
+// return html for a flat list <select> for the mailbox tree
+function rcmail_render_folder_tree_select(&$arrFolders, &$special, &$mbox_name, $maxlength, $nestLevel=0)
+  {
+  global $IMAP, $OUTPUT;
+
+  $idx = 0;
+  $out = '';
+  foreach ($arrFolders as $key=>$folder)
+    {
+    $folder_lc = strtolower($folder['id']);
+    if (in_array($folder_lc, $special))
+      $foldername = rcube_label($folder_lc);
+    else
+      {
+      $foldername = $folder['name'];
+      
+      // shorten the folder name to a given length
+      if ($maxlength && $maxlength>1)
+        $foldername = abbrevate_string($foldername, $maxlength);
+      }
+
+    $out .= sprintf('<option value="%s">%s%s</option>'."\n",
+                    $folder['id'],
+                    str_repeat('&nbsp;', $nestLevel*4),
+                    rep_specialchars_output($foldername, 'html', 'all'));
+
+    if (!empty($folder['folders']))
+      $out .= rcmail_render_folder_tree_select($folder['folders'], $special, $mbox_name, $maxlength, $nestLevel+1);
+
+    $idx++;
+    }
+
+  return $out;
+  }
+
+
+// return the message list as HTML table
+function rcmail_message_list($attrib)
+  {
+  global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT, $JS_OBJECT_NAME;
+
+  $skin_path = $CONFIG['skin_path'];
+  $image_tag = '<img src="%s%s" alt="%s" border="0" />';
+
+  // check to see if we have some settings for sorting
+  $sort_col   = $_SESSION['sort_col'];
+  $sort_order = $_SESSION['sort_order'];
+  
+  // add some labels to client
+  rcube_add_label('from', 'to');
+
+  // get message headers
+  $a_headers = $IMAP->list_headers('', '', $sort_col, $sort_order);
+
+  // add id to message list table if not specified
+  if (!strlen($attrib['id']))
+    $attrib['id'] = 'rcubemessagelist';
+
+  // allow the following attributes to be added to the <table> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+
+  $out = '<table' . $attrib_str . ">\n";
+
+
+  // define list of cols to be displayed
+  $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
+  $a_sort_cols = array('subject', 'date', 'from', 'to', 'size');
+  
+  // show 'to' instead of from in sent messages
+  if (($IMAP->get_mailbox_name()==$CONFIG['sent_mbox'] || $IMAP->get_mailbox_name()==$CONFIG['drafts_mbox']) && ($f = array_search('from', $a_show_cols))
+      && !array_search('to', $a_show_cols))
+    $a_show_cols[$f] = 'to';
+  
+  // add col definition
+  $out .= '<colgroup>';
+  $out .= '<col class="icon" />';
+
+  foreach ($a_show_cols as $col)
+    $out .= sprintf('<col class="%s" />', $col);
+
+  $out .= '<col class="icon" />';
+  $out .= "</colgroup>\n";
+
+  // add table title
+  $out .= "<thead><tr>\n<td class=\"icon\">&nbsp;</td>\n";
+
+  $javascript = '';
+  foreach ($a_show_cols as $col)
+    {
+    // get column name
+    $col_name = rep_specialchars_output(rcube_label($col));
+
+    // make sort links
+    $sort = '';
+    if ($IMAP->get_capability('sort') && in_array($col, $a_sort_cols))
+      {
+      // have buttons configured
+      if (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton']))
+        {
+        $sort = '&nbsp;&nbsp;';
+
+        // asc link
+        if (!empty($attrib['sortascbutton']))
+          {
+          $sort .= rcube_button(array('command' => 'sort',
+                                      'prop' => $col.'_ASC',
+                                      'image' => $attrib['sortascbutton'],
+                                      'align' => 'absmiddle',
+                                      'title' => 'sortasc'));
+          }       
+        
+        // desc link
+        if (!empty($attrib['sortdescbutton']))
+          {
+          $sort .= rcube_button(array('command' => 'sort',
+                                      'prop' => $col.'_DESC',
+                                      'image' => $attrib['sortdescbutton'],
+                                      'align' => 'absmiddle',
+                                      'title' => 'sortdesc'));        
+          }
+        }
+      // just add a link tag to the header
+      else
+        {
+        $col_name = sprintf('<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>',
+                            $JS_OBJECT_NAME,
+                            $col,
+                            rcube_label('sortby'),
+                            $col_name);
+        }
+      }
+      
+    $sort_class = $col==$sort_col ? " sorted$sort_order" : '';
+
+    // put it all together
+    $out .= '<td class="'.$col.$sort_class.'" id="rcmHead'.$col.'">' . "$col_name$sort</td>\n";    
+    }
+
+  $out .= '<td class="icon">'.($attrib['attachmenticon'] ? sprintf($image_tag, $skin_path, $attrib['attachmenticon'], '') : '')."</td>\n";
+  $out .= "</tr></thead>\n<tbody>\n";
+
+  // no messages in this mailbox
+  if (!sizeof($a_headers))
+    {
+    $out .= rep_specialchars_output(
+                               sprintf('<tr><td colspan="%d">%s</td></tr>',
+                   sizeof($a_show_cols)+2,
+                   rcube_label('nomessagesfound')));
+    }
+
+
+  $a_js_message_arr = array();
+
+  // create row for each message
+  foreach ($a_headers as $i => $header)  //while (list($i, $header) = each($a_headers))
+    {
+    $message_icon = $attach_icon = '';
+    $js_row_arr = array();
+    $zebra_class = $i%2 ? 'even' : 'odd';
+
+    // set messag attributes to javascript array
+    if ($header->deleted)
+      $js_row_arr['deleted'] = true;
+    if (!$header->seen)
+      $js_row_arr['unread'] = true;
+    if ($header->answered)
+      $js_row_arr['replied'] = true;
+    // set message icon  
+    if ($attrib['deletedicon'] && $header->deleted)
+      $message_icon = $attrib['deletedicon'];
+    else if ($attrib['unreadicon'] && !$header->seen)
+      $message_icon = $attrib['unreadicon'];
+    else if ($attrib['repliedicon'] && $header->answered)
+      $message_icon = $attrib['repliedicon'];
+    else if ($attrib['messageicon'])
+      $message_icon = $attrib['messageicon'];
+    
+       // set attachment icon
+    if ($attrib['attachmenticon'] && preg_match("/multipart\/m/i", $header->ctype))
+      $attach_icon = $attrib['attachmenticon'];
+        
+    $out .= sprintf('<tr id="rcmrow%d" class="message%s%s %s">'."\n",
+                    $header->uid,
+                    $header->seen ? '' : ' unread',
+                    $header->deleted ? ' deleted' : '',
+                    $zebra_class);    
+    
+    $out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : '');
+        
+    // format each col
+    foreach ($a_show_cols as $col)
+      {
+      if ($col=='from' || $col=='to')
+        $cont = rep_specialchars_output(rcmail_address_string($header->$col, 3, $attrib['addicon']));
+      else if ($col=='subject')
+        {
+        $cont = rep_specialchars_output($IMAP->decode_header($header->$col), 'html', 'all');
+        // firefox/mozilla temporary workaround to pad subject with content so that whitespace in rows responds to drag+drop
+        $cont .= '<img src="./program/blank.gif" height="5" width="1000" alt="" />';
+        }
+      else if ($col=='size')
+        $cont = show_bytes($header->$col);
+      else if ($col=='date')
+        $cont = format_date($header->date); //date('m.d.Y G:i:s', strtotime($header->date));
+      else
+        $cont = rep_specialchars_output($header->$col, 'html', 'all');
+        
+         $out .= '<td class="'.$col.'">' . $cont . "</td>\n";
+      }
+
+    $out .= sprintf("<td class=\"icon\">%s</td>\n", $attach_icon ? sprintf($image_tag, $skin_path, $attach_icon, '') : '');
+    $out .= "</tr>\n";
+    
+    if (sizeof($js_row_arr))
+      $a_js_message_arr[$header->uid] = $js_row_arr;
+    }
+  
+  // complete message table
+  $out .= "</tbody></table>\n";
+  
+  
+  $message_count = $IMAP->messagecount();
+  
+  // set client env
+  $javascript .= sprintf("%s.gui_object('mailcontframe', '%s');\n", $JS_OBJECT_NAME, 'mailcontframe');
+  $javascript .= sprintf("%s.gui_object('messagelist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
+  $javascript .= sprintf("%s.set_env('messagecount', %d);\n", $JS_OBJECT_NAME, $message_count);
+  $javascript .= sprintf("%s.set_env('current_page', %d);\n", $JS_OBJECT_NAME, $IMAP->list_page);
+  $javascript .= sprintf("%s.set_env('pagecount', %d);\n", $JS_OBJECT_NAME, ceil($message_count/$IMAP->page_size));
+  $javascript .= sprintf("%s.set_env('sort_col', '%s');\n", $JS_OBJECT_NAME, $sort_col);
+  $javascript .= sprintf("%s.set_env('sort_order', '%s');\n", $JS_OBJECT_NAME, $sort_order);
+  
+  if ($attrib['messageicon'])
+    $javascript .= sprintf("%s.set_env('messageicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['messageicon']);
+  if ($attrib['deletedicon'])
+    $javascript .= sprintf("%s.set_env('deletedicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['deletedicon']);
+  if ($attrib['unreadicon'])
+    $javascript .= sprintf("%s.set_env('unreadicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['unreadicon']);
+  if ($attrib['repliedicon'])
+    $javascript .= sprintf("%s.set_env('repliedicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['repliedicon']);
+  if ($attrib['attachmenticon'])
+    $javascript .= sprintf("%s.set_env('attachmenticon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['attachmenticon']);
+    
+  $javascript .= sprintf("%s.set_env('messages', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
+  
+  $OUTPUT->add_script($javascript);  
+  
+  return $out;
+  }
+
+
+
+
+// return javascript commands to add rows to the message list
+function rcmail_js_message_list($a_headers, $insert_top=FALSE)
+  {
+  global $CONFIG, $IMAP;
+
+  $commands = '';
+  $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
+
+  // show 'to' instead of from in sent messages
+  if (strtolower($IMAP->get_mailbox_name())=='sent' && ($f = array_search('from', $a_show_cols))
+      && !array_search('to', $a_show_cols))
+    $a_show_cols[$f] = 'to';
+
+  $commands .= sprintf("this.set_message_coltypes(%s);\n", array2js($a_show_cols)); 
+
+  // loop through message headers
+  for ($n=0; $a_headers[$n]; $n++)
+    {
+    $header = $a_headers[$n];
+    $a_msg_cols = array();
+    $a_msg_flags = array();
+      
+    // format each col; similar as in rcmail_message_list()
+    foreach ($a_show_cols as $col)
+      {
+      if ($col=='from' || $col=='to')
+        $cont = rep_specialchars_output(rcmail_address_string($header->$col, 3));
+      else if ($col=='subject')
+        $cont = rep_specialchars_output($IMAP->decode_header($header->$col), 'html', 'all');
+      else if ($col=='size')
+        $cont = show_bytes($header->$col);
+      else if ($col=='date')
+        $cont = format_date($header->date); //date('m.d.Y G:i:s', strtotime($header->date));
+      else
+        $cont = rep_specialchars_output($header->$col, 'html', 'all');
+          
+      $a_msg_cols[$col] = $cont;
+      }
+
+    $a_msg_flags['deleted'] = $header->deleted ? 1 : 0;
+    $a_msg_flags['unread'] = $header->seen ? 0 : 1;
+    $a_msg_flags['replied'] = $header->answered ? 1 : 0;
+    $commands .= sprintf("this.add_message_row(%s, %s, %s, %b, %b);\n",
+                         $header->uid,
+                         array2js($a_msg_cols),
+                         array2js($a_msg_flags),
+                         preg_match("/multipart\/m/i", $header->ctype),
+                         $insert_top);
+    }
+
+  return $commands;
+  }
+
+
+// return code for search function
+function rcmail_search_form($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+
+  // add some labels to client
+  rcube_add_label('searching');
+
+  $attrib['name'] = '_q';
+  
+  if (empty($attrib['id']))
+    $attrib['id'] = 'rcmqsearchbox';
+  
+  $input_q = new textfield($attrib);
+  $out = $input_q->show();
+
+  $OUTPUT->add_script(sprintf("%s.gui_object('qsearchbox', '%s');",
+                              $JS_OBJECT_NAME,
+                              $attrib['id']));
+
+  // add form tag around text field
+  if (empty($attrib['form']))
+    $out = sprintf('<form name="rcmqsearchform" action="./" '.
+                   'onsubmit="%s.command(\'search\');return false" style="display:inline;">%s</form>',
+                   $JS_OBJECT_NAME,
+                   $out);
+
+  return $out;
+  } 
+
+
+function rcmail_messagecount_display($attrib)
+  {
+  global $IMAP, $OUTPUT, $JS_OBJECT_NAME;
+  
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmcountdisplay';
+
+  $OUTPUT->add_script(sprintf("%s.gui_object('countdisplay', '%s');",
+                              $JS_OBJECT_NAME,
+                              $attrib['id']));
+
+  // allow the following attributes to be added to the <span> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
+
+  
+  $out = '<span' . $attrib_str . '>';
+  $out .= rcmail_get_messagecount_text();
+  $out .= '</span>';
+  return $out;
+  }
+
+
+function rcmail_quota_display($attrib)
+  {
+  global $IMAP, $OUTPUT, $JS_OBJECT_NAME;
+
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmquotadisplay';
+
+  $OUTPUT->add_script(sprintf("%s.gui_object('quotadisplay', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+
+  // allow the following attributes to be added to the <span> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
+  
+  if (!$IMAP->get_capability('QUOTA'))
+    $quota_text = rcube_label('unknown');
+  else if (!($quota_text = $IMAP->get_quota()))
+    $quota_text = rcube_label('unlimited');
+
+  $out = '<span' . $attrib_str . '>';
+  $out .= $quota_text;
+  $out .= '</span>';
+  return $out;
+  }
+
+
+function rcmail_get_messagecount_text($count=NULL, $page=NULL)
+  {
+  global $IMAP, $MESSAGE;
+  
+  if (isset($MESSAGE['index']))
+    {
+    return rcube_label(array('name' => 'messagenrof',
+                             'vars' => array('nr'  => $MESSAGE['index']+1,
+                                             'count' => $count!==NULL ? $count : $IMAP->messagecount())));
+    }
+
+  if ($page===NULL)
+    $page = $IMAP->list_page;
+    
+  $start_msg = ($page-1) * $IMAP->page_size + 1;
+  $max = $count!==NULL ? $count : $IMAP->messagecount();
+
+  if ($max==0)
+    $out = rcube_label('mailboxempty');
+  else
+    $out = rcube_label(array('name' => 'messagesfromto',
+                              'vars' => array('from'  => $start_msg,
+                                              'to'    => min($max, $start_msg + $IMAP->page_size - 1),
+                                              'count' => $max)));
+
+  return rep_specialchars_output($out);
+  }
+
+
+function rcmail_print_body($part, $safe=FALSE, $plain=FALSE) // $body, $ctype_primary='text', $ctype_secondary='plain', $encoding='7bit', $safe=FALSE, $plain=FALSE)
+  {
+  global $IMAP, $REMOTE_OBJECTS, $JS_OBJECT_NAME;
+
+  // extract part properties: body, ctype_primary, ctype_secondary, encoding, parameters
+  extract($part);
+  
+  $block = $plain ? '%s' : '%s'; //'<div style="display:block;">%s</div>';
+  $body = $IMAP->mime_decode($body, $encoding);  
+  $body = $IMAP->charset_decode($body, $parameters);
+
+  // text/html
+  if ($ctype_secondary=='html')
+    {
+    if (!$safe)  // remove remote images and scripts
+      {
+      $remote_patterns = array('/(src|background)=(["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)(\2|\s|>)/Ui',
+                           //  '/(src|background)=(["\']?)([\.\/]+[^"\'\s]+)(\2|\s|>)/Ui',
+                               '/(<base.*href=["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)([^<]*>)/i',
+                               '/(<link.*href=["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)([^<]*>)/i',
+                               '/url\s*\(["\']?([hftps]{3,5}:\/{2}[^"\'\s]+)["\']?\)/i',
+                               '/url\s*\(["\']?([\.\/]+[^"\'\s]+)["\']?\)/i',
+                               '/<script.+<\/script>/Umis');
+
+      $remote_replaces = array('',  // '\\1=\\2#\\4',
+                            // '\\1=\\2#\\4',
+                               '',
+                               '',  // '\\1#\\3',
+                               'none',
+                               'none',
+                               '');
+      
+      // set flag if message containes remote obejcts that where blocked
+      foreach ($remote_patterns as $pattern)
+        {
+        if (preg_match($pattern, $body))
+          {
+          $REMOTE_OBJECTS = TRUE;
+          break;
+          }
+        }
+
+      $body = preg_replace($remote_patterns, $remote_replaces, $body);
+      }
+
+    return sprintf($block, rep_specialchars_output($body, 'html', '', FALSE));
+    }
+
+  // text/enriched
+  if ($ctype_secondary=='enriched')
+    {
+    $body = enriched_to_html($body);
+    return sprintf($block, rep_specialchars_output($body, 'html'));
+    }
+  else
+    {
+    // make links and email-addresses clickable
+    $convert_patterns = $convert_replaces = $replace_strings = array();
+    
+    $url_chars = 'a-z0-9_\-\+\*\$\/&%=@#:';
+    $url_chars_within = '\?\.~,!';
+
+    $convert_patterns[] = "/([\w]+):\/\/([a-z0-9\-\.]+[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/ie";
+    $convert_replaces[] = "rcmail_str_replacement('<a href=\"\\1://\\2\" target=\"_blank\">\\1://\\2</a>', \$replace_strings)";
+
+    $convert_patterns[] = "/([^\/:]|\s)(www\.)([a-z0-9\-]{2,}[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/ie";
+    $convert_replaces[] = "rcmail_str_replacement('\\1<a href=\"http://\\2\\3\" target=\"_blank\">\\2\\3</a>', \$replace_strings)";
+    
+    $convert_patterns[] = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/ie';
+    $convert_replaces[] = "rcmail_str_replacement('<a href=\"mailto:\\1\" onclick=\"return $JS_OBJECT_NAME.command(\'compose\',\'\\1\',this)\">\\1</a>', \$replace_strings)";
+
+    $body = wordwrap(trim($body), 80);
+    $body = preg_replace($convert_patterns, $convert_replaces, $body);
+
+    // split body into single lines
+    $a_lines = preg_split('/\r?\n/', $body);
+
+    // colorize quoted parts
+    for($n=0; $n<sizeof($a_lines); $n++)
+      {
+      $line = $a_lines[$n];
+
+      if ($line{2}=='>')
+        $color = 'red';
+      else if ($line{1}=='>')
+        $color = 'green';
+      else if ($line{0}=='>')
+        $color = 'blue';
+      else
+        $color = FALSE;
+
+      $line = rep_specialchars_output($line, 'html', 'replace', FALSE);
+        
+      if ($color)
+        $a_lines[$n] = sprintf('<font color="%s">%s</font>', $color, $line);
+      else
+        $a_lines[$n] = $line;
+      }
+
+    // insert the links for urls and mailtos
+    $body = preg_replace("/##string_replacement\{([0-9]+)\}##/e", "\$replace_strings[\\1]", join("\n", $a_lines));
+    
+    return sprintf($block, "<pre>\n".$body."\n</pre>");
+    }
+  }
+
+
+
+// add a string to the replacement array and return a replacement string
+function rcmail_str_replacement($str, &$rep)
+  {
+  static $count = 0;
+  $rep[$count] = stripslashes($str);
+  return "##string_replacement{".($count++)."}##";
+  }
+
+
+function rcmail_parse_message($structure, $arg=array(), $recursive=FALSE)
+  {
+  global $IMAP;
+  static $sa_inline_objects = array();
+
+  // arguments are: (bool)$prefer_html, (string)$get_url
+  extract($arg);
+
+  $a_attachments = array();
+  $a_return_parts = array();
+  $out = '';
+
+  $message_ctype_primary = strtolower($structure->ctype_primary);
+  $message_ctype_secondary = strtolower($structure->ctype_secondary);
+
+  // show message headers
+  if ($recursive && is_array($structure->headers) && isset($structure->headers['subject']))
+    $a_return_parts[] = array('type' => 'headers',
+                              'headers' => $structure->headers);
+
+  // print body if message doesn't have multiple parts
+  if ($message_ctype_primary=='text')
+    {
+    $a_return_parts[] = array('type' => 'content',
+                              'body' => $structure->body,
+                              'ctype_primary' => $message_ctype_primary,
+                              'ctype_secondary' => $message_ctype_secondary,
+                              'parameters' => $structure->ctype_parameters,
+                              'encoding' => $structure->headers['content-transfer-encoding']);
+    }
+
+  // message contains alternative parts
+  else if ($message_ctype_primary=='multipart' && $message_ctype_secondary=='alternative' && is_array($structure->parts))
+    {
+    // get html/plaintext parts
+    $plain_part = $html_part = $print_part = $related_part = NULL;
+    
+    foreach ($structure->parts as $p => $sub_part)
+      {
+      $sub_ctype_primary = strtolower($sub_part->ctype_primary);
+      $sub_ctype_secondary = strtolower($sub_part->ctype_secondary);
+
+      // check if sub part is 
+      if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='plain')
+        $plain_part = $p;
+      else if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='html')
+        $html_part = $p;
+      else if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='enriched')
+        $enriched_part = $p;
+      else if ($sub_ctype_primary=='multipart' && $sub_ctype_secondary=='related')
+        $related_part = $p;
+      }
+
+    // parse related part (alternative part could be in here)
+    if ($related_part!==NULL && $prefer_html)
+      {
+      list($parts, $attachmnts) = rcmail_parse_message($structure->parts[$related_part], $arg, TRUE);
+      $a_return_parts = array_merge($a_return_parts, $parts);
+      $a_attachments = array_merge($a_attachments, $attachmnts);
+      }
+
+    // print html/plain part
+    else if ($html_part!==NULL && $prefer_html)
+      $print_part = $structure->parts[$html_part];
+    else if ($enriched_part!==NULL)
+      $print_part = $structure->parts[$enriched_part];
+    else if ($plain_part!==NULL)
+      $print_part = $structure->parts[$plain_part];
+
+    // show message body
+    if (is_object($print_part))
+      $a_return_parts[] = array('type' => 'content',
+                                'body' => $print_part->body,
+                                'ctype_primary' => strtolower($print_part->ctype_primary),
+                                'ctype_secondary' => strtolower($print_part->ctype_secondary),
+                                'parameters' => $print_part->ctype_parameters,
+                                'encoding' => $print_part->headers['content-transfer-encoding']);
+    // show plaintext warning
+    else if ($html_part!==NULL)
+      $a_return_parts[] = array('type' => 'content',
+                                'body' => rcube_label('htmlmessage'),
+                                'ctype_primary' => 'text',
+                                'ctype_secondary' => 'plain');
+                                
+    // add html part as attachment
+    if ($html_part!==NULL && $structure->parts[$html_part]!==$print_part)
+      {
+      $html_part = $structure->parts[$html_part];
+      $a_attachments[] = array('filename' => rcube_label('htmlmessage'),
+                               'encoding' => $html_part->headers['content-transfer-encoding'],
+                               'mimetype' => 'text/html',
+                               'part_id'  => $html_part->mime_id,
+                               'size'     => strlen($IMAP->mime_decode($html_part->body, $html_part->headers['content-transfer-encoding'])));
+      }
+    }
+
+  // message contains multiple parts
+  else if ($message_ctype_primary=='multipart' && is_array($structure->parts))
+    {
+    foreach ($structure->parts as $mail_part)
+      {
+      $primary_type = strtolower($mail_part->ctype_primary);
+      $secondary_type = strtolower($mail_part->ctype_secondary);
+
+      // multipart/alternative
+      if ($primary_type=='multipart') // && ($secondary_type=='alternative' || $secondary_type=='mixed' || $secondary_type=='related'))
+        {
+        list($parts, $attachmnts) = rcmail_parse_message($mail_part, $arg, TRUE);
+
+        $a_return_parts = array_merge($a_return_parts, $parts);
+        $a_attachments = array_merge($a_attachments, $attachmnts);
+        }
+
+      // part text/[plain|html] OR message/delivery-status
+      else if (($primary_type=='text' && ($secondary_type=='plain' || $secondary_type=='html') && $mail_part->disposition!='attachment') ||
+               ($primary_type=='message' && $secondary_type=='delivery-status'))
+        {
+        $a_return_parts[] = array('type' => 'content',
+                                  'body' => $mail_part->body,
+                                  'ctype_primary' => $primary_type,
+                                  'ctype_secondary' => $secondary_type,
+                                  'parameters' => $mail_part->ctype_parameters,
+                                  'encoding' => $mail_part->headers['content-transfer-encoding']);
+        }
+
+      // part message/*
+      else if ($primary_type=='message')
+        {
+        /* don't parse headers here; they're parsed within the recursive call to rcmail_parse_message()
+        if ($mail_part->parts[0]->headers)
+          $a_return_parts[] = array('type' => 'headers',
+                                    'headers' => $mail_part->parts[0]->headers);
+        */
+                                      
+        list($parts, $attachmnts) = rcmail_parse_message($mail_part->parts[0], $arg, TRUE);
+
+        $a_return_parts = array_merge($a_return_parts, $parts);
+        $a_attachments = array_merge($a_attachments, $attachmnts);
+        }
+
+      // part is file/attachment
+      else if ($mail_part->disposition=='attachment' || $mail_part->disposition=='inline' || $mail_part->headers['content-id'] ||
+               (empty($mail_part->disposition) && ($mail_part->d_parameters['filename'] || $mail_part->ctype_parameters['name'])))
+        {
+        if ($message_ctype_secondary=='related' && $mail_part->headers['content-id'])
+          $sa_inline_objects[] = array('filename' => rcube_imap::decode_mime_string($mail_part->d_parameters['filename']),
+                                       'mimetype' => strtolower("$primary_type/$secondary_type"),
+                                       'part_id'  => $mail_part->mime_id,
+                                       'content_id' => preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']));
+
+        else if ($mail_part->d_parameters['filename'])
+          $a_attachments[] = array('filename' => rcube_imap::decode_mime_string($mail_part->d_parameters['filename']),
+                                   'encoding' => strtolower($mail_part->headers['content-transfer-encoding']),
+                                   'mimetype' => strtolower("$primary_type/$secondary_type"),
+                                   'part_id'  => $mail_part->mime_id,
+                                   'size'     => strlen($IMAP->mime_decode($mail_part->body, $mail_part->headers['content-transfer-encoding'])) /*,
+                                   'content'  => $mail_part->body */);
+                                   
+        else if ($mail_part->ctype_parameters['name'])
+          $a_attachments[] = array('filename' => rcube_imap::decode_mime_string($mail_part->ctype_parameters['name']),
+                                   'encoding' => strtolower($mail_part->headers['content-transfer-encoding']),
+                                   'mimetype' => strtolower("$primary_type/$secondary_type"),
+                                   'part_id'  => $mail_part->mime_id,
+                                   'size'     => strlen($IMAP->mime_decode($mail_part->body, $mail_part->headers['content-transfer-encoding'])) /*,
+                                   'content'  => $mail_part->body */);
+                                   
+        else if ($mail_part->headers['content-description'])
+         $a_attachments[] = array('filename' => rcube_imap::decode_mime_string($mail_part->headers['content-description']),
+                                  'encoding' => strtolower($mail_part->headers['content-transfer-encoding']),
+                                   'mimetype' => strtolower("$primary_type/$secondary_type"),
+                                   'part_id'  => $mail_part->mime_id,
+                                   'size'     => strlen($IMAP->mime_decode($mail_part->body, $mail_part->headers['content-transfer-encoding'])) /*,
+                                   'content'  => $mail_part->body */);
+        }
+      }
+
+
+    // if this was a related part try to resolve references
+    if ($message_ctype_secondary=='related' && sizeof($sa_inline_objects))
+      {
+      $a_replace_patters = array();
+      $a_replace_strings = array();
+        
+      foreach ($sa_inline_objects as $inline_object)
+        {
+        $a_replace_patters[] = 'cid:'.$inline_object['content_id'];
+        $a_replace_strings[] = sprintf($get_url, $inline_object['part_id']);
+        }
+      
+      foreach ($a_return_parts as $i => $return_part)
+        {
+        if ($return_part['type']!='content')
+          continue;
+
+        // decode body and replace cid:...
+        $a_return_parts[$i]['body'] = str_replace($a_replace_patters, $a_replace_strings, $IMAP->mime_decode($return_part['body'], $return_part['encoding']));
+        $a_return_parts[$i]['encoding'] = '7bit';
+        }
+      }
+    }
+    
+
+  // join all parts together
+  //$out .= join($part_delimiter, $a_return_parts);
+
+  return array($a_return_parts, $a_attachments);
+  }
+
+
+
+
+// return table with message headers
+function rcmail_message_headers($attrib, $headers=NULL)
+  {
+  global $IMAP, $OUTPUT, $MESSAGE;
+  static $sa_attrib;
+  
+  // keep header table attrib
+  if (is_array($attrib) && !$sa_attrib)
+    $sa_attrib = $attrib;
+  else if (!is_array($attrib) && is_array($sa_attrib))
+    $attrib = $sa_attrib;
+  
+  
+  if (!isset($MESSAGE))
+    return FALSE;
+
+  // get associative array of headers object
+  if (!$headers)
+    $headers = is_object($MESSAGE['headers']) ? get_object_vars($MESSAGE['headers']) : $MESSAGE['headers'];
+    
+  $header_count = 0;
+  
+  // allow the following attributes to be added to the <table> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+  $out = '<table' . $attrib_str . ">\n";
+
+  // show these headers
+  $standard_headers = array('subject', 'from', 'organization', 'to', 'cc', 'bcc', 'reply-to', 'date');
+  
+  foreach ($standard_headers as $hkey)
+    {
+    if (!$headers[$hkey])
+      continue;
+
+    if ($hkey=='date' && !empty($headers[$hkey]))
+      $header_value = format_date(strtotime($headers[$hkey]));
+    else if (in_array($hkey, array('from', 'to', 'cc', 'bcc', 'reply-to')))
+      $header_value = rep_specialchars_output(rcmail_address_string($headers[$hkey], NULL, $attrib['addicon']));
+    else
+      $header_value = rep_specialchars_output($IMAP->decode_header($headers[$hkey]), '', 'all');
+
+    $out .= "\n<tr>\n";
+    $out .= '<td class="header-title">'.rep_specialchars_output(rcube_label($hkey)).":&nbsp;</td>\n";
+    $out .= '<td class="'.$hkey.'" width="90%">'.$header_value."</td>\n</tr>";
+    $header_count++;
+    }
+
+  $out .= "\n</table>\n\n";
+
+  return $header_count ? $out : '';  
+  }
+
+
+
+function rcmail_message_body($attrib)
+  {
+  global $CONFIG, $OUTPUT, $MESSAGE, $GET_URL, $REMOTE_OBJECTS, $JS_OBJECT_NAME;
+  
+  if (!is_array($MESSAGE['parts']) && !$MESSAGE['body'])
+    return '';
+    
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmailMsgBody';
+
+  $safe_mode = (bool)$_GET['_safe'];
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
+  $out = '<div '. $attrib_str . ">\n";
+  
+  $header_attrib = array();
+  foreach ($attrib as $attr => $value)
+    if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs))
+      $header_attrib[$regs[1]] = $value;
+
+
+  // this is an ecrypted message
+  // -> create a plaintext body with the according message
+  if (!sizeof($MESSAGE['parts']) && $MESSAGE['headers']->ctype=='multipart/encrypted')
+    {
+    $MESSAGE['parts'][0] = array('type' => 'content',
+                                 'ctype_primary' => 'text',
+                                 'ctype_secondary' => 'plain',
+                                 'body' => rcube_label('encryptedmessage'));
+    }
+  
+  if ($MESSAGE['parts'])
+    {
+    foreach ($MESSAGE['parts'] as $i => $part)
+      {
+      if ($part['type']=='headers')
+        $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part['headers']);
+      else if ($part['type']=='content')
+        {
+        if (empty($part['parameters']) || empty($part['parameters']['charset']))
+          $part['parameters']['charset'] = $MESSAGE['headers']->charset;
+        
+        // $body = rcmail_print_body($part['body'], $part['ctype_primary'], $part['ctype_secondary'], $part['encoding'], $safe_mode);
+        $body = rcmail_print_body($part, $safe_mode);
+        $out .= '<div class="message-part">';
+        
+        if ($part['ctype_secondary']!='plain')
+          $out .= rcmail_mod_html_body($body, $attrib['id']);
+        else
+          $out .= $body;
+
+        $out .= "</div>\n";
+        }
+      }
+    }
+  else
+    $out .= $MESSAGE['body'];
+
+
+  $ctype_primary = strtolower($MESSAGE['structure']->ctype_primary);
+  $ctype_secondary = strtolower($MESSAGE['structure']->ctype_secondary);
+  
+  // list images after mail body
+  if (get_boolean($attrib['showimages']) && $ctype_primary=='multipart' && $ctype_secondary=='mixed' &&
+      sizeof($MESSAGE['attachments']) && !strstr($message_body, '<html') && strlen($GET_URL))
+    {
+    foreach ($MESSAGE['attachments'] as $attach_prop)
+      {
+      if (strpos($attach_prop['mimetype'], 'image/')===0)
+        $out .= sprintf("\n<hr />\n<p align=\"center\"><img src=\"%s&_part=%s\" alt=\"%s\" title=\"%s\" /></p>\n",
+                        $GET_URL, $attach_prop['part_id'],
+                        $attach_prop['filename'],
+                        $attach_prop['filename']);
+      }
+    }
+  
+  // tell client that there are blocked remote objects
+  if ($REMOTE_OBJECTS && !$safe_mode)
+    $OUTPUT->add_script(sprintf("%s.set_env('blockedobjects', true);", $JS_OBJECT_NAME));
+
+  $out .= "\n</div>";
+  return $out;
+  }
+
+
+
+// modify a HTML message that it can be displayed inside a HTML page
+function rcmail_mod_html_body($body, $container_id)
+  {
+  // remove any null-byte characters before parsing
+  $body = preg_replace('/\x00/', '', $body);
+  
+  $last_style_pos = 0;
+  $body_lc = strtolower($body);
+  
+  // find STYLE tags
+  while (($pos = strpos($body_lc, '<style', $last_style_pos)) && ($pos2 = strpos($body_lc, '</style>', $pos)))
+    {
+    $pos2 += 8;
+    $body_pre = substr($body, 0, $pos);
+    $styles = substr($body, $pos, $pos2-$pos);
+    $body_post = substr($body, $pos2, strlen($body)-$pos2);
+    
+    // replace all css definitions with #container [def]
+    $styles = rcmail_mod_css_styles($styles, $container_id);
+    
+    $body = $body_pre . $styles . $body_post;
+    $last_style_pos = $pos2;
+    }
+
+
+  // remove SCRIPT tags
+  foreach (array('script', 'applet', 'object', 'embed', 'iframe') as $tag)
+    {
+    while (($pos = strpos($body_lc, '<'.$tag)) && ($pos2 = strpos($body_lc, '</'.$tag.'>', $pos)))
+      {
+      $pos2 += 8;
+      $body = substr($body, 0, $pos) . substr($body, $pos2, strlen($body)-$pos2);
+      $body_lc = strtolower($body);
+      }
+    }
+
+  // replace event handlers on any object
+  while ($body != $prev_body)
+    {
+    $prev_body = $body;
+    $body = preg_replace('/(<[^!][^>]*\s)(on[^=>]+)=([^>]+>)/im', '$1__removed=$3', $body);
+    $body = preg_replace('/(<[^!][^>]*\shref=["\']?)(javascript:)([^>]*?>)/im', '$1null:$3', $body);
+    }
+
+  // resolve <base href>
+  $base_reg = '/(<base.*href=["\']?)([hftps]{3,5}:\/{2}[^"\'\s]+)([^<]*>)/i';
+  if (preg_match($base_reg, $body, $regs))
+    {
+    $base_url = $regs[2];
+    $body = preg_replace('/(src|background|href)=(["\']?)([\.\/]+[^"\'\s]+)(\2|\s|>)/Uie', "'\\1=\"'.make_absolute_url('\\3', '$base_url').'\"'", $body);
+    $body = preg_replace('/(url\s*\()(["\']?)([\.\/]+[^"\'\)\s]+)(\2)\)/Uie', "'\\1\''.make_absolute_url('\\3', '$base_url').'\')'", $body);
+    $body = preg_replace($base_reg, '', $body);
+    }
+    
+  // modify HTML links to open a new window if clicked
+  $body = preg_replace('/<a\s+([^>]+)>/Uie', "rcmail_alter_html_link('\\1');", $body);
+
+  // add comments arround html and other tags
+  $out = preg_replace(array('/(<\/?html[^>]*>)/i',
+                            '/(<\/?head[^>]*>)/i',
+                            '/(<title[^>]*>.*<\/title>)/Ui',
+                            '/(<\/?meta[^>]*>)/i'),
+                      '<!--\\1-->',
+                      $body);
+                      
+  $out = preg_replace(array('/(<body[^>]*>)/i',
+                            '/(<\/body>)/i'),
+                      array('<div class="rcmBody">',
+                            '</div>'),
+                      $out);
+  
+  return $out;
+  }
+
+
+// parse link attributes and set correct target
+function rcmail_alter_html_link($in)
+  {
+  $attrib = parse_attrib_string($in);
+
+  if (stristr((string)$attrib['href'], 'mailto:'))
+    $attrib['onclick'] = sprintf("return %s.command('compose','%s',this)",
+                                 $GLOBALS['JS_OBJECT_NAME'],
+                                 preg_replace("/'+/i","",substr($attrib['href'], 7)));
+  else if (!empty($attrib['href']) && $attrib['href']{0}!='#')
+    $attrib['target'] = '_blank';
+  
+  return '<a' . create_attrib_string($attrib, array('href', 'name', 'target', 'onclick', 'id', 'class', 'style', 'title')) . '>';
+  }
+
+
+// replace all css definitions with #container [def]
+function rcmail_mod_css_styles($source, $container_id)
+  {
+  $a_css_values = array();
+  $last_pos = 0;
+  
+  // cut out all contents between { and }
+  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
+    {
+    $key = sizeof($a_css_values);
+    $a_css_values[$key] = substr($source, $pos+1, $pos2-($pos+1));
+    $source = substr($source, 0, $pos+1) . "<<str_replacement[$key]>>" . substr($source, $pos2, strlen($source)-$pos2);
+    $last_pos = $pos+2;
+    }
+  
+  $styles = preg_replace('/(^\s*|,\s*)([a-z0-9\._][a-z0-9\.\-_]*)/im', "\\1#$container_id \\2", $source);
+  $styles = preg_replace('/<<str_replacement\[([0-9]+)\]>>/e', "\$a_css_values[\\1]", $styles);
+  
+  // replace body definition because we also stripped off the <body> tag
+  $styles = preg_replace("/$container_id\s+body/i", "$container_id div.rcmBody", $styles);
+  
+  return $styles;
+  }
+
+
+
+// return first text part of a message
+function rcmail_first_text_part($message_parts)
+  {
+  if (!is_array($message_parts))
+    return FALSE;
+    
+  $html_part = NULL;
+      
+  // check all message parts
+  foreach ($message_parts as $pid => $part)
+    {
+    $mimetype = strtolower($part->ctype_primary.'/'.$part->ctype_secondary);
+    if ($mimetype=='text/plain')
+      {
+      $body = rcube_imap::mime_decode($part->body, $part->headers['content-transfer-encoding']);
+      $body = rcube_imap::charset_decode($body, $part->ctype_parameters);
+      return $body;
+      }
+    else if ($mimetype=='text/html')
+      {
+      $html_part = rcube_imap::mime_decode($part->body, $part->headers['content-transfer-encoding']);
+      $html_part = rcube_imap::charset_decode($html_part, $part->ctype_parameters);
+      }
+    }
+    
+
+  // convert HTML to plain text
+  if ($html_part)
+    {    
+    // remove special chars encoding
+    $trans = array_flip(get_html_translation_table(HTML_ENTITIES));
+    $html_part = strtr($html_part, $trans);
+
+    // create instance of html2text class
+    $txt = new html2text($html_part);
+    return $txt->get_text();
+    }
+
+  return FALSE;
+  }
+
+
+// get source code of a specific message and cache it
+function rcmail_message_source($uid)
+  {
+  global $IMAP, $DB, $CONFIG;
+
+  // get message ID if uid is given
+  $cache_key = $IMAP->mailbox.'.msg';
+  $cached = $IMAP->get_cached_message($cache_key, $uid, FALSE);
+  
+  // message is cached in database
+  if ($cached && !empty($cached->body))
+    return $cached->body;
+
+  if (!$cached)
+    $headers = $IMAP->get_headers($uid);
+  else
+    $headers = &$cached;
+
+  // create unique identifier based on message_id
+  if (!empty($headers->messageID))
+    $message_id = md5($headers->messageID);
+  else
+    $message_id = md5($headers->uid.'@'.$_SESSION['imap_host']);
+  
+  $temp_dir = $CONFIG['temp_dir'].(!eregi('\/$', $CONFIG['temp_dir']) ? '/' : '');
+  $cache_dir = $temp_dir.$_SESSION['client_id'];
+  $cache_path = $cache_dir.'/'.$message_id;
+
+  // message is cached in temp dir
+  if ($CONFIG['enable_caching'] && is_dir($cache_dir) && is_file($cache_path))
+    {
+    if ($fp = fopen($cache_path, 'r'))
+      {
+      $msg_source = fread($fp, filesize($cache_path));
+      fclose($fp);
+      return $msg_source;
+      }
+    }
+
+
+  // get message from server
+  $msg_source = $IMAP->get_raw_body($uid);
+  
+  // return message source without caching
+  if (!$CONFIG['enable_caching'])
+    return $msg_source;
+
+
+  // let's cache the message body within the database
+  if ($cached && ($CONFIG['db_max_length'] -300) > $headers->size)
+    {
+    $DB->query("UPDATE ".get_table_name('messages')."
+                SET    body=?
+                WHERE  user_id=?
+                AND    cache_key=?
+                AND    uid=?",
+               $msg_source,
+               $_SESSION['user_id'],
+               $cache_key,
+               $uid);
+
+    return $msg_source;
+    }
+
+
+  // create dir for caching
+  if (!is_dir($cache_dir))
+    $dir = mkdir($cache_dir);
+  else
+    $dir = true;
+
+  // attempt to write a file with the message body    
+  if ($dir && ($fp = fopen($cache_path, 'w')))
+    {
+    fwrite($fp, $msg_source);
+    fclose($fp);
+    }
+  else
+    {
+    raise_error(array('code' => 403, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 
+                      'message' => "Failed to write to temp dir"), TRUE, FALSE);
+    }
+
+  return $msg_source;
+  }
+
+
+// decode address string and re-format it as HTML links
+function rcmail_address_string($input, $max=NULL, $addicon=NULL)
+  {
+  global $IMAP, $PRINT_MODE, $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $EMAIL_ADDRESS_PATTERN;
+  
+  $a_parts = $IMAP->decode_address_list($input);
+
+  if (!sizeof($a_parts))
+    return $input;
+
+  $c = count($a_parts);
+  $j = 0;
+  $out = '';
+
+  foreach ($a_parts as $part)
+    {
+    $j++;
+    if ($PRINT_MODE)
+      $out .= sprintf('%s &lt;%s&gt;', rep_specialchars_output($part['name']), $part['mailto']);
+    else if (preg_match($EMAIL_ADDRESS_PATTERN, $part['mailto']))
+      {
+      $out .= sprintf('<a href="mailto:%s" onclick="return %s.command(\'compose\',\'%s\',this)" class="rcmContactAddress" title="%s">%s</a>',
+                      $part['mailto'],
+                      $JS_OBJECT_NAME,
+                      $part['mailto'],
+                      $part['mailto'],
+                      rep_specialchars_output($part['name']));
+                      
+      if ($addicon)
+        $out .= sprintf('&nbsp;<a href="#add" onclick="return %s.command(\'add-contact\',\'%s\',this)" title="%s"><img src="%s%s" alt="add" border="0" /></a>',
+                        $JS_OBJECT_NAME,
+                        urlencode($part['string']),
+                        rcube_label('addtoaddressbook'),
+                        $CONFIG['skin_path'],
+                        $addicon);
+      }
+    else
+      {
+      if ($part['name'])
+        $out .= rep_specialchars_output($part['name']);
+      if ($part['mailto'])
+        $out .= (strlen($out) ? ' ' : '') . sprintf('&lt;%s&gt;', $part['mailto']);
+      }
+      
+    if ($c>$j)
+      $out .= ','.($max ? '&nbsp;' : ' ');
+        
+    if ($max && $j==$max && $c>$j)
+      {
+      $out .= '...';
+      break;
+      }        
+    }
+    
+  return $out;
+  }
+
+
+function rcmail_message_part_controls()
+  {
+  global $CONFIG, $IMAP, $MESSAGE;
+  
+  if (!is_array($MESSAGE) || !is_array($MESSAGE['parts']) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE['parts'][$_GET['_part']])
+    return '';
+    
+  $part = $MESSAGE['parts'][$_GET['_part']];
+  
+  $attrib_str = create_attrib_string($attrib, array('id', 'class', 'style', 'cellspacing', 'cellpadding', 'border', 'summary'));
+  $out = '<table '. $attrib_str . ">\n";
+  
+  $filename = $part->d_parameters['filename'] ? $part->d_parameters['filename'] : $part->ctype_parameters['name'];
+  $filesize = strlen($IMAP->mime_decode($part->body, $part->headers['content-transfer-encoding']));
+  
+  if ($filename)
+    {
+    $out .= sprintf('<tr><td class="title">%s</td><td>%s</td><td>[<a href="./?%s">%s</a>]</tr>'."\n",
+                    rcube_label('filename'),
+                    rep_specialchars_output($filename),
+                    str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']),
+                    rcube_label('download'));
+    }
+    
+  if ($filesize)
+    $out .= sprintf('<tr><td class="title">%s</td><td>%s</td></tr>'."\n",
+                    rcube_label('filesize'),
+                    show_bytes($filesize));
+  
+  $out .= "\n</table>";
+  
+  return $out;
+  }
+
+
+
+function rcmail_message_part_frame($attrib)
+  {
+  global $MESSAGE;
+  
+  $part = $MESSAGE['parts'][$_GET['_part']];
+  $ctype_primary = strtolower($part->ctype_primary);
+
+  $attrib['src'] = './?'.str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
+
+  $attrib_str = create_attrib_string($attrib, array('id', 'class', 'style', 'src', 'width', 'height'));
+  $out = '<iframe '. $attrib_str . "></ifame>";
+    
+  return $out;
+  }
+
+
+// create temp dir for attachments
+function rcmail_create_compose_tempdir()
+  {
+  global $CONFIG;
+  
+  if ($_SESSION['compose']['temp_dir'])
+    return $_SESSION['compose']['temp_dir'];
+  
+  if (!empty($CONFIG['temp_dir']))
+    $temp_dir = $CONFIG['temp_dir'].(!eregi('\/$', $CONFIG['temp_dir']) ? '/' : '').$_SESSION['compose']['id'];
+
+  // create temp-dir for uploaded attachments
+  if (!empty($CONFIG['temp_dir']) && is_writeable($CONFIG['temp_dir']))
+    {
+    mkdir($temp_dir);
+    $_SESSION['compose']['temp_dir'] = $temp_dir;
+    }
+
+  return $_SESSION['compose']['temp_dir'];
+  }
+
+
+// clear message composing settings
+function rcmail_compose_cleanup()
+  {
+  if (!isset($_SESSION['compose']))
+    return;
+  
+  // remove attachment files from temp dir
+  if (is_array($_SESSION['compose']['attachments']))
+    foreach ($_SESSION['compose']['attachments'] as $attachment)
+      @unlink($attachment['path']);
+
+  // kill temp dir
+  if ($_SESSION['compose']['temp_dir'])
+    @rmdir($_SESSION['compose']['temp_dir']);
+  
+  unset($_SESSION['compose']);
+  }
+  
+  
+?>
diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
new file mode 100644 (file)
index 0000000..6322f70
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/get.inc                                            |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Delivering a specific part of a mail message                        |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: get.inc 147 2006-02-20 23:29:14Z roundcube $
+
+*/
+
+require_once('Mail/mimeDecode.php');
+
+
+// show loading page
+if ($_GET['_preload'])
+  {
+  $url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']);
+  $message = rcube_label('loadingdata');
+
+  print "<html>\n<head>\n" .
+        '<meta http-equiv="refresh" content="0; url='.$url.'">' .
+        "\n</head>\n<body>" .
+        $message .
+        "\n</body>\n</html>";
+  exit;
+  }
+
+
+
+// similar code as in program/steps/mail/show.inc
+if ($_GET['_uid'])
+  {
+  $MESSAGE = array();
+  $MESSAGE['source'] = rcmail_message_source($_GET['_uid']);
+
+  $mmd = new Mail_mimeDecode($MESSAGE['source']);
+  $MESSAGE['structure'] = $mmd->decode(array('include_bodies' => TRUE,
+                                             'decode_headers' => FALSE,
+                                             'decode_bodies' => FALSE));
+
+  $MESSAGE['parts'] = $mmd->getMimeNumbers($MESSAGE['structure']);
+  }
+
+
+
+// show part page
+if ($_GET['_frame'])
+  {
+  parse_template('messagepart');
+  exit;
+  }
+
+else if ($_GET['_part'])
+  {
+  if ($part = $MESSAGE['parts'][$_GET['_part']]);
+    {
+    $ctype_primary = strtolower($part->ctype_primary);
+    $ctype_secondary = strtolower($part->ctype_secondary);
+
+    $mimetype = sprintf('%s/%s', $ctype_primary, $ctype_secondary);
+    $filename = $part->d_parameters['filename'] ? $part->d_parameters['filename'] : $part->ctype_parameters['name'];
+
+    if ($ctype_primary=='text' && $ctype_secondary=='html')
+      {
+      list($MESSAGE['parts']) = rcmail_parse_message($part,
+                                                     array('safe' => (bool)$_GET['_safe'],
+                                                           'prefer_html' => TRUE,
+                                                           'get_url' => $GET_URL.'&_part=%s'));
+
+      $cont = rcmail_print_body($MESSAGE['parts'][0], (bool)$_GET['_safe']);
+      }
+    else
+      $cont = $IMAP->mime_decode($part->body, $part->headers['content-transfer-encoding']);
+
+    // send correct headers for content type and length
+    if ($_GET['_download'])
+      {
+      // send download headers
+      header("Content-Type: application/octet-stream");
+      header(sprintf('Content-Disposition: attachment; filename="%s"',
+                     $filename ? $filename : "roundcube.$ctype_secondary"));
+      }
+    else
+      {
+      header("Content-Type: $mimetype");
+      header(sprintf('Content-Disposition: inline; filename="%s"', $filename));
+      }
+
+    header(sprintf('Content-Length: %d', strlen($cont)));
+
+    // We need to set the following headers to make downloads work using IE in HTTPS mode.
+    if (isset($_SERVER['HTTPS']))
+      {
+      header('Pragma: ');
+      header('Cache-Control: ');
+      }
+
+    // deliver part content
+    echo $cont;
+    exit;
+    }
+  }
+
+// print message
+else
+  {
+  $ctype_primary = strtolower($MESSAGE['structure']->ctype_primary);
+  $ctype_secondary = strtolower($MESSAGE['structure']->ctype_secondary);
+  $mimetype = sprintf('%s/%s', $ctype_primary, $ctype_secondary);
+
+  // send correct headers for content type
+  header("Content-Type: text/html");
+
+  $cont = ''; 
+  list($MESSAGE['parts']) = rcmail_parse_message($MESSAGE['structure'],
+                                                 array('safe' => (bool)$_GET['_safe'],
+                                                 'get_url' => $GET_URL.'&_part=%s'));
+
+  if ($MESSAGE['parts'] && $ctype_primary=='multipart')
+    {
+    // reset output page
+    $OUTPUT = new rcube_html_page();
+    parse_template('messagepart');
+    exit;
+    }
+  else if ($MESSAGE['parts'][0])
+    {
+    $part = $MESSAGE['parts'][0];
+    $cont = rcmail_print_body($part, (bool)$_GET['_safe']);
+    }
+  else
+    $cont = $IMAP->get_body($_GET['_uid']);
+
+  $OUTPUT = new rcube_html_page();
+  $OUTPUT->write($cont);
+
+/*
+    if ($mimetype=='text/html')
+      print $cont;
+    else
+      {
+      print "<html>\n<body>\n";
+      print $cont;
+      print "\n</body>\n</html>";
+      }
+*/
+  exit;
+  }
+
+
+// if we arrive here, the requested part was not found
+header('HTTP/1.1 404 Not Found');
+exit;
+
+?>
\ No newline at end of file
diff --git a/program/steps/mail/getunread.inc b/program/steps/mail/getunread.inc
new file mode 100644 (file)
index 0000000..2e417e9
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/getunread.inc                                      |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Check all mailboxes for unread messages and update GUI              |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: getunread.inc 269 2006-06-29 23:41:40Z richs $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+$a_folders = $IMAP->list_mailboxes();
+
+if (!empty($a_folders))
+  {
+  foreach ($a_folders as $mbox_row)
+    {
+    $commands = sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_row), $IMAP->messagecount($mbox_row, 'UNSEEN'));
+    rcube_remote_response($commands, TRUE);
+    }
+  }
+
+exit;
+?>
diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc
new file mode 100644 (file)
index 0000000..02c15cf
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/list.inc                                           |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Send message list to client (as remote response)                    |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: list.inc 232 2006-05-18 15:46:50Z cmcnulty $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+$OUTPUT_TYPE = 'js';
+
+$sort = isset($_GET['_sort']) ? $_GET['_sort'] : false;
+
+// is there a sort type for this request?
+if ($sort)
+  {
+  // yes, so set the sort vars
+  list($sort_col, $sort_order) = explode('_', $sort);
+
+  // set session vars for sort (so next page and task switch know how to sort)
+  $_SESSION['sort_col'] = $sort_col;
+  $_SESSION['sort_order'] = $sort_order;
+  }
+else
+  {
+  // use session settings if set, defaults if not
+  $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col'];
+  $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];
+  }
+  
+
+// we have a saved search request
+if (!empty($_GET['_search']) && isset($_SESSION['search'][$_GET['_search']]))
+  {
+  $a_msgs = split(',', $_SESSION['search'][$_GET['_search']]);
+  $a_headers = $IMAP->list_header_set($mbox_name, $a_msgs, NULL, $sort_col, $sort_order);
+  $count = count($a_msgs);
+  }
+else
+  {
+  if ($count = $IMAP->messagecount())
+    $a_headers = $IMAP->list_headers($mbox_name, NULL, $sort_col, $sort_order);
+  }
+
+$unseen = $IMAP->messagecount($mbox_name, 'UNSEEN', !empty($_GET['_refresh']) ? TRUE : FALSE);
+
+// update message count display
+$pages = ceil($count/$IMAP->page_size);
+$commands = sprintf("this.set_env('messagecount', %d);\n", $count);
+$commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
+$commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text($count));
+
+// update mailboxlist
+$mbox_name = $IMAP->get_mailbox_name();
+$commands .= sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_name), $unseen);
+
+
+// add message rows
+if (isset($a_headers) && count($a_headers))
+  $commands .= rcmail_js_message_list($a_headers);
+
+  
+// send response
+rcube_remote_response($commands);
+
+exit;
+?>
\ No newline at end of file
diff --git a/program/steps/mail/mark.inc b/program/steps/mail/mark.inc
new file mode 100644 (file)
index 0000000..fb5cb7e
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/mark.inc                                           |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Mark the submitted messages with the specified flag                 |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: mark.inc 232 2006-05-18 15:46:50Z cmcnulty $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+$a_flags_map = array('undelete' => 'UNDELETED',
+                     'delete' => 'DELETED',
+                     'read' => 'SEEN',
+                     'unread' => 'UNSEEN');
+
+if ($_GET['_uid'] && $_GET['_flag'])
+  {
+  $flag = $a_flags_map[$_GET['_flag']] ? $a_flags_map[$_GET['_flag']] : strtoupper($_GET['_flag']);
+  $marked = $IMAP->set_flag($_GET['_uid'], $flag);
+  if ($marked != -1)
+    {
+    $mbox_name = $IMAP->get_mailbox_name();
+    $commands = sprintf("this.set_unread_count('%s', %d);\n", $mbox_name, $IMAP->messagecount($mbox_name, 'UNSEEN'));
+    rcube_remote_response($commands);
+    }
+  }
+  
+exit;
+?>
\ No newline at end of file
diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc
new file mode 100644 (file)
index 0000000..1d20e15
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/move_del.inc                                       |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Move the submitted messages to a specific mailbox or delete them    |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: move_del.inc 269 2006-06-29 23:41:40Z richs $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+// move messages
+if ($_action=='moveto' && $_GET['_uid'] && $_GET['_target_mbox'])
+  {
+  $count = sizeof(explode(',', $_GET['_uid']));  
+  $moved = $IMAP->move_message($_GET['_uid'], $_GET['_target_mbox'], $_GET['_mbox']);
+  
+  if (!$moved)
+    {
+    // send error message
+    $commands = "this.list_mailbox();\n";
+    $commands .= show_message('errormoving', 'error');
+    rcube_remote_response($commands);
+    exit;
+    }
+  }
+
+// delete messages 
+else if ($_action=='delete' && $_GET['_uid'])
+  {
+  $count = sizeof(explode(',', $_GET['_uid']));
+  $del = $IMAP->delete_message($_GET['_uid'], $_GET['_mbox']);
+  
+  if (!$del)
+    {
+    // send error message
+    $commands = "this.list_mailbox();\n";
+    $commands .= show_message('errordeleting', 'error');
+    rcube_remote_response($commands);
+    exit;
+    }
+  }
+  
+// unknown action or missing query param
+else
+  {
+  exit;
+  }
+
+
+// update message count display
+$pages = ceil($IMAP->messagecount()/$IMAP->page_size);
+$commands = sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text());
+$commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
+
+
+// update mailboxlist
+$mbox = $IMAP->get_mailbox_name();
+$commands .= sprintf("this.set_unread_count('%s', %d);\n", $mbox, $IMAP->messagecount($mbox, 'UNSEEN'));
+
+if ($_action=='moveto')
+  $commands .= sprintf("this.set_unread_count('%s', %d);\n", $_GET['_target_mbox'], $IMAP->messagecount($_GET['_target_mbox'], 'UNSEEN'));
+
+$commands .= sprintf("this.set_quota('%s');\n", $IMAP->get_quota()); 
+
+// add new rows from next page (if any)
+if ($_GET['_from']!='show' && $pages>1 && $IMAP->list_page < $pages)
+  {
+  $a_headers = $IMAP->list_headers($mbox, null, $_SESSION['sort_col'], $_SESSION['sort_order']);
+  $a_headers = array_slice($a_headers, -$count, $count);
+  $commands .= rcmail_js_message_list($a_headers);
+  }
+
+  
+// send response
+rcube_remote_response($commands);
+
+exit;
+?>
diff --git a/program/steps/mail/rss.inc b/program/steps/mail/rss.inc
new file mode 100644 (file)
index 0000000..a0ccf41
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/rss.inc                                            |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Send mailboxcontents as RSS feed                                    |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Sjon Hortensius <sjon@hortensius.net>                         |
+ +-----------------------------------------------------------------------+
+
+ $Id: rss.inc 134 2006-02-05 16:35:40Z roundcube $
+
+*/
+
+require_once('Mail/mimeDecode.php');
+
+
+function rss_encode($string){
+       $string = rep_specialchars_output($string, 'xml');
+       return $string;
+}
+
+
+
+$REMOTE_REQUEST = TRUE;
+$OUTPUT_TYPE = 'rss';
+
+$webmail_url = 'http';
+if (strstr('HTTPS', $_SERVER['SERVER_PROTOCOL'] )!== FALSE)
+  $webmail_url .= 's';
+$webmail_url .= '://'.$_SERVER['SERVER_NAME'];
+if ($_SERVER['SERVER_PORT'] != '80')
+       $webmail_url .= ':'.$_SERVER['SERVER_PORT'];
+$webmail_url .= '/';
+if (dirname($_SERVER['SCRIPT_NAME']) != '/')
+       $webmail_url .= dirname($_SERVER['SCRIPT_NAME']).'/';
+
+$auth_webmail_url = $webmail_url.'?_auth='.$GLOBALS['sess_auth'];
+
+$messagecount_unread = $IMAP->messagecount('INBOX', 'UNSEEN', TRUE);
+$messagecount = $IMAP->messagecount();
+
+$sort_col = 'date';
+$sort_order = 'DESC';
+
+// Send global XML output
+header('Content-type: text/xml');
+echo '<?xml version="1.0" encoding="UTF-8"?>
+       <rss version="2.0"
+        xmlns:dc="http://purl.org/dc/elements/1.1/"
+        xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
+        xmlns:admin="http://webns.net/mvcb/"
+        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+        xmlns:content="http://purl.org/rss/1.0/modules/content/">';
+
+// Send channel-specific output
+echo '
+       <channel>
+               <pubDate>'.date('r').'</pubDate>
+               <lastBuildDate>'.date('r').'</lastBuildDate>
+               <ttl>5</ttl>
+               <docs>http://blogs.law.harvard.edu/tech/rss</docs>
+               <description>INBOX contains '.$messagecount.' messages, of which '.$messagecount_unread.' unread</description>
+               <link>'.rss_encode($auth_webmail_url, 'xml') .'</link>
+               <title>webmail for '.rss_encode($_SESSION['username'].' @ '.$_SESSION['imap_host']).'</title>
+               <generator>'.rss_encode($CONFIG['useragent'], 'xml').' (RSS extension by Sjon Hortensius)</generator>
+               <image>
+                       <link>http://www.roundcube.net/</link>
+                       <title>'.rss_encode($CONFIG['product_name']).' logo</title>
+                       <url>'.rss_encode($webmail_url.'skins/default/images/roundcube_logo.png').'</url>
+               </image>';
+
+// Check if the user wants to override the default sortingmethode
+if (isset($_GET['_sort']))
+  list($sort_col, $sort_order) = explode('_', $_GET['_sort']);
+
+// Add message to output
+if ($messagecount > 0)
+  {
+  $items = $IMAP->list_headers('INBOX', null, $sort_col, $sort_order);
+  foreach ($items as $item)
+    {
+
+    // Convert '"name" <email>' to 'email (name)'
+    if (strstr($item->from, '<'))
+      $item->from = preg_replace('~"?([^"]*)"? <([^>]*)>~', '\2 (\1)', $item->from);
+
+    $item->link = $auth_webmail_url.'&_task=mail&_action=show&_uid='.$item->uid.'&_mbox=INBOX';
+
+    $item->body = $IMAP->get_body($item->uid);
+
+    // Print the actual messages
+    echo '
+                       <item>
+                               <title>'.rss_encode($item->subject).'</title>
+                               <link>'.rss_encode($item->link).'</link>
+                               <description><![CDATA['."\n".nl2br(rss_encode($item->body))."\n".']]></description>
+                               <author>'.rss_encode($item->from).'</author>
+                               <category></category>
+                               <guid>'.rss_encode($item->link).'</guid>
+                               <pubDate>'.date('r', $item->timestamp).'</pubDate>
+                       </item>';
+    }
+  }
+
+echo '</channel>
+</rss>';
+
+exit;
+?>
\ No newline at end of file
diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc
new file mode 100644 (file)
index 0000000..517ef30
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/*
+ +-----------------------------------------------------------------------+
+ | steps/mail/search.inc                                                 |
+ |                                                                       |
+ | Search functions for rc webmail                                       |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Benjamin Smith <defitro@gmail.com>                            |
+ |         Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+// reset list_page
+$IMAP->set_page(1);
+$_SESSION['page'] = 1;
+
+// search query comes in with ISO encoding because javascript escape()
+// uses ISO-8859-1. Better handling for that will follow.
+$imap_charset = 'ISO-8859-1';
+
+// get search string
+$str = get_input_value('_search', RCUBE_INPUT_GET);
+$mbox = get_input_value('_mbox', RCUBE_INPUT_GET);
+$search_request = md5($str);
+
+
+// Check the search string for type of search
+if (preg_match("/^from:/i", $str)) {
+  list(,$srch) = explode(":", $str);
+  $search = $IMAP->search($mbox, "HEADER FROM" ,trim($srch), $imap_charset);
+  finish_search($mbox, $search);
+}
+else if (preg_match("/^to:/i", $str)) {
+  list(,$srch) = explode(":", $str);
+  $search = $IMAP->search($mbox, "HEADER TO", trim($srch), $imap_charset);
+  finish_search($mbox, $search);
+}
+else if (preg_match("/^cc:/i", $str)) {
+  list(,$srch) = explode(":", $str);
+  $search = $IMAP->search($mbox, "HEADER CC", trim($srch), $imap_charset);
+  finish_search($mbox, $search);
+}
+else if (preg_match("/^subject:/i", $str)) {
+  list(,$srch) = explode(":", $str);
+  $search = $IMAP->search($mbox, "HEADER SUBJECT", trim($srch), $imap_charset);
+  finish_search($mbox, $search);
+}
+else if (preg_match("/^body:/i", $str)) {
+  list(,$srch) = explode(":", $str);
+  $search = $IMAP->search($mbox, "TEXT", trim($srch), $imap_charset);
+  finish_search($mbox, $search);
+}
+// search in subject and sender by default
+else {
+  $search = $IMAP->search($mbox, "HEADER SUBJECT", trim($str), $imap_charset);
+  $search2 = $IMAP->search($mbox, "HEADER FROM", trim($str), $imap_charset);
+  finish_search($mbox, array_unique(array_merge($search, $search2)));
+}
+
+
+// Complete the search display results or report error
+function finish_search($mbox, $search)
+  {
+  global $IMAP, $JS_OBJECT_NAME, $OUTPUT, $search_request;
+  $commands = '';
+  $count = 0;
+    
+  // Make sure our $search is legit..
+  if (is_array($search) && $search[0] != '')
+    {
+    // Get the headers
+    $result_h = $IMAP->list_header_set($mbox, $search, 1, $_SESSION['sort_col'], $_SESSION['sort_order']);
+    $count = count($search);
+
+    // save search results in session
+    if (!is_array($_SESSION['search']))
+      $_SESSION['search'] = array();
+
+    // Make sure we got the headers
+    if ($result_h != NULL)
+      {
+      $_SESSION['search'][$search_request] = join(',', $search);
+      $commands = rcmail_js_message_list($result_h);
+      $commands .= show_message('searchsuccessful', 'confirmation', array('nr' => $count));
+      }
+    }
+  else
+    {
+    $commands = show_message('searchnomatch', 'warning');
+    $search_request = -1;
+    }
+  
+  // update message count display
+  $pages = ceil($count/$IMAP->page_size);
+  $commands .= sprintf("\nthis.set_env('search_request', '%s')\n", $search_request);
+  $commands .= sprintf("this.set_env('messagecount', %d);\n", $count);
+  $commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
+  $commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text($count, 1));
+  rcube_remote_response($commands);
+  }
+
+?>
\ No newline at end of file
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
new file mode 100644 (file)
index 0000000..607badb
--- /dev/null
@@ -0,0 +1,388 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/sendmail.inc                                       |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Compose a new mail message with all headers and attachments         |
+ |   and send it using IlohaMail's SMTP methods or with PHP mail()       |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: sendmail.inc 288 2006-07-31 22:51:23Z thomasb $
+
+*/
+
+
+//require_once('lib/smtp.inc');
+require_once('include/rcube_smtp.inc');
+require_once('Mail/mime.php');
+
+
+if (!isset($_SESSION['compose']['id']))
+  {
+  rcmail_overwrite_action('list');
+  return;
+  }
+
+
+/****** message sending functions ********/
+
+
+function rcmail_get_identity($id)
+  {
+  global $DB, $CHARSET, $OUTPUT;
+  
+  // get identity record
+  $sql_result = $DB->query("SELECT *, email AS mailto
+                            FROM ".get_table_name('identities')."
+                            WHERE  identity_id=?
+                            AND    user_id=?
+                            AND    del<>1",
+                            $id,$_SESSION['user_id']);
+                                   
+  if ($DB->num_rows($sql_result))
+    {
+    $sql_arr = $DB->fetch_assoc($sql_result);
+    $out = $sql_arr;
+    $name = strpos($sql_arr['name'], ",") ? '"'.$sql_arr['name'].'"' : $sql_arr['name'];
+    $out['string'] = sprintf('%s <%s>',
+                             rcube_charset_convert($name, $CHARSET, $OUTPUT->get_charset()),
+                             $sql_arr['mailto']);
+    return $out;
+    }
+
+  return FALSE;  
+  }
+
+
+if (strlen($_POST['_draft_saveid']) > 3)
+  $olddraftmessageid = get_input_value('_draft_saveid', RCUBE_INPUT_POST);
+
+$message_id = sprintf('<%s@%s>', md5(uniqid('rcmail'.rand(),true)), $_SESSION['imap_host']);
+$savedraft = !empty($_POST['_draft']) ? TRUE : FALSE;
+
+// remove all scripts and act as called in frame
+$OUTPUT->reset();
+$_framed = TRUE;
+
+
+/****** check submission and compose message ********/
+
+
+if (empty($_POST['_to']) && empty($_POST['_subject']) && $_POST['_message'])
+  {
+  show_message("sendingfailed", 'error'); 
+  //rcmail_overwrite_action('compose');
+  rcube_iframe_response();
+  return;
+  }
+
+
+// set default charset
+$input_charset = $OUTPUT->get_charset();
+$message_charset = isset($_POST['_charset']) ? $_POST['_charset'] : $input_charset;
+
+$mailto_regexp = array('/[,;]\s*[\r\n]+/', '/[\r\n]+/', '/[,;]\s*$/m');
+$mailto_replace = array(', ', ', ', '');
+
+// replace new lines and strip ending ', '
+$mailto = preg_replace($mailto_regexp, $mailto_replace, get_input_value('_to', RCUBE_INPUT_POST, TRUE, $message_charset));
+
+// decode address strings
+$to_address_arr = $IMAP->decode_address_list($mailto);
+$identity_arr = rcmail_get_identity(get_input_value('_from', RCUBE_INPUT_POST));
+
+$from = $identity_arr['mailto'];
+$first_to = is_array($to_address_arr[0]) ? $to_address_arr[0]['mailto'] : $mailto;
+
+if (empty($identity_arr['string']))
+  $identity_arr['string'] = $from;
+
+// compose headers array
+$headers = array('Date' => date('D, j M Y G:i:s O'),
+                 'From' => $identity_arr['string'],
+                 'To'   => rcube_charset_convert($mailto, $input_charset, $message_charset));
+
+// additional recipients
+if (!empty($_POST['_cc']))
+  $headers['Cc'] = preg_replace($mailto_regexp, $mailto_replace, get_input_value('_cc', RCUBE_INPUT_POST, TRUE, $message_charset));
+
+if (!empty($_POST['_bcc']))
+  $headers['Bcc'] = preg_replace($mailto_regexp, $mailto_replace, get_input_value('_bcc', RCUBE_INPUT_POST, TRUE, $message_charset));
+  
+if (!empty($identity_arr['bcc']))
+  $headers['Bcc'] = ($headers['Bcc'] ? $headers['Bcc'].', ' : '') . $identity_arr['bcc'];
+
+// add subject
+$headers['Subject'] = trim(get_input_value('_subject', RCUBE_INPUT_POST, FALSE, $message_charset));
+
+if (!empty($identity_arr['organization']))
+  $headers['Organization'] = $identity_arr['organization'];
+
+if (!empty($identity_arr['reply-to']))
+  $headers['Reply-To'] = $identity_arr['reply-to'];
+
+if (!empty($_SESSION['compose']['reply_msgid']))
+  $headers['In-Reply-To'] = $_SESSION['compose']['reply_msgid'];
+
+if (!empty($_SESSION['compose']['references']))
+  $headers['References'] = $_SESSION['compose']['references'];
+
+if (!empty($_POST['_priority']))
+  {
+  $priority = (int)$_POST['_priority'];
+  $a_priorities = array(1=>'lowest', 2=>'low', 4=>'high', 5=>'highest');
+  if ($str_priority = $a_priorities[$priority])
+    $headers['X-Priority'] = sprintf("%d (%s)", $priority, ucfirst($str_priority));
+  }
+
+if (!empty($_POST['_receipt']))
+  {
+  $headers['Return-Receipt-To'] = $identity_arr['string'];
+  $headers['Disposition-Notification-To'] = $identity_arr['string'];
+  }
+
+// additional headers
+$headers['Message-ID'] = $message_id;
+$headers['X-Sender'] = $from;
+
+if (!empty($CONFIG['useragent']))
+  $headers['User-Agent'] = $CONFIG['useragent'];
+
+// fetch message body
+$message_body = get_input_value('_message', RCUBE_INPUT_POST, TRUE, $message_charset);
+
+// append generic footer to all messages
+if (!empty($CONFIG['generic_message_footer']))
+  {
+  $file = realpath($CONFIG['generic_message_footer']);
+  if($fp = fopen($file, 'r'))
+    {
+    $content = fread($fp, filesize($file));
+    fclose($fp);
+    $message_body .= "\r\n" . rcube_charset_convert($content, 'UTF-8', $message_charset);
+    }
+  }
+
+// try to autodetect operating system and use the correct line endings
+// use the configured delimiter for headers
+if (!empty($CONFIG['mail_header_delimiter']))
+  $header_delm = $CONFIG['mail_header_delimiter'];
+else if (strtolower(substr(PHP_OS, 0, 3)=='win')) 
+  $header_delm = "\r\n";
+else if (strtolower(substr(PHP_OS, 0, 3)=='mac'))
+  $header_delm = "\r\n";
+else    
+  $header_delm = "\n";
+
+// create PEAR::Mail_mime instance
+$MAIL_MIME = new Mail_mime($header_delm);
+$MAIL_MIME->setTXTBody($message_body, FALSE, TRUE);
+//$MAIL_MIME->setTXTBody(wordwrap($message_body), FALSE, TRUE);
+
+
+// add stored attachments, if any
+if (is_array($_SESSION['compose']['attachments']))
+  foreach ($_SESSION['compose']['attachments'] as $attachment)
+    $MAIL_MIME->addAttachment($attachment['path'], $attachment['mimetype'], $attachment['name'], TRUE);
+
+  
+// add submitted attachments
+if (is_array($_FILES['_attachments']['tmp_name']))
+  foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath)
+    $MAIL_MIME->addAttachment($filepath, $files['type'][$i], $files['name'][$i], TRUE);
+
+
+// chose transfer encoding
+$charset_7bit = array('ASCII', 'ISO-2022-JP', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-15');
+$transfer_encoding = in_array(strtoupper($message_charset), $charset_7bit) ? '7bit' : '8bit';
+
+// encoding settings for mail composing
+$message_param = array('text_encoding' => $transfer_encoding,
+                       'html_encoding' => 'quoted-printable',
+                       'head_encoding' => 'quoted-printable',
+                       'head_charset'  => $message_charset,
+                       'html_charset'  => $message_charset,
+                       'text_charset'  => $message_charset);
+
+// compose message body and get headers
+$msg_body = &$MAIL_MIME->get($message_param);
+
+$msg_subject = $headers['Subject'];
+
+if ($MBSTRING && function_exists("mb_encode_mimeheader"))
+  $headers['Subject'] = mb_encode_mimeheader($headers['Subject'], $message_charset);
+
+// Begin SMTP Delivery Block 
+if (!$savedraft) {
+
+  // send thru SMTP server using custom SMTP library
+  if ($CONFIG['smtp_server'])
+    {
+    // generate list of recipients
+    $a_recipients = array($mailto);
+  
+    if (strlen($headers['Cc']))
+      $a_recipients[] = $headers['Cc'];
+    if (strlen($headers['Bcc']))
+      $a_recipients[] = $headers['Bcc'];
+  
+    // clean Bcc from header for recipients
+    $send_headers = $headers;
+    unset($send_headers['Bcc']);
+  
+    // generate message headers
+    $header_str = $MAIL_MIME->txtHeaders($send_headers);
+  
+    // send message
+    $sent = smtp_mail($from, $a_recipients, $header_str, $msg_body);
+  
+    // log error
+    if (!$sent)
+      {
+      raise_error(array('code' => 800,
+                        'type' => 'smtp',
+                        'line' => __LINE__,
+                        'file' => __FILE__,
+                        'message' => "SMTP error: $SMTP_ERROR"), TRUE, FALSE);
+      }
+    }
+  
+  // send mail using PHP's mail() function
+  else
+    {
+    // unset some headers because they will be added by the mail() function
+    $headers_enc = $MAIL_MIME->headers($headers);
+    $headers_php = $MAIL_MIME->_headers;
+    unset($headers_php['To'], $headers_php['Subject']);
+    
+    // reset stored headers and overwrite
+    $MAIL_MIME->_headers = array();
+    $header_str = $MAIL_MIME->txtHeaders($headers_php);
+  
+    if (ini_get('safe_mode'))
+      $sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str);
+    else
+      $sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str, "-f$from");
+    }
+  
+  
+  // return to compose page if sending failed
+  if (!$sent)
+    {
+    show_message("sendingfailed", 'error'); 
+    rcube_iframe_response();
+    return;
+    }
+  
+  
+  // set repliead flag
+  if ($_SESSION['compose']['reply_uid'])
+    $IMAP->set_flag($_SESSION['compose']['reply_uid'], 'ANSWERED');
+
+  } // End of SMTP Delivery Block
+
+
+
+// Determine which folder to save message
+if ($savedraft)
+  $store_target = 'drafts_mbox';
+else
+  $store_target = 'sent_mbox';
+
+if ($CONFIG[$store_target])
+  {
+  // create string of complete message headers
+  $header_str = $MAIL_MIME->txtHeaders($headers);
+
+  // check if mailbox exists
+  if (!in_array_nocase($CONFIG[$store_target], $IMAP->list_mailboxes()))
+    $store_folder = $IMAP->create_mailbox($CONFIG[$store_target], TRUE);
+  else
+    $store_folder = TRUE;
+  
+  // add headers to message body
+  $msg_body = $header_str."\r\n".$msg_body;
+
+  // append message to sent box
+  if ($store_folder)
+    $saved = $IMAP->save_message($CONFIG[$store_target], $msg_body);
+
+  // raise error if saving failed
+  if (!$saved)
+    {
+    raise_error(array('code' => 800,
+                      'type' => 'imap',
+                      'file' => __FILE__,
+                      'message' => "Could not save message in $CONFIG[$store_target]"), TRUE, FALSE);
+    
+    show_message('errorsaving', 'error');
+    rcube_iframe_response($errorout);
+    }
+
+  if ($olddraftmessageid)
+    {
+    // delete previous saved draft
+    $a_deleteid = $IMAP->search($CONFIG['drafts_mbox'],'HEADER Message-ID',$olddraftmessageid);
+    $deleted = $IMAP->delete_message($IMAP->get_uid($a_deleteid[0],$CONFIG['drafts_mbox']),$CONFIG['drafts_mbox']);
+
+    // raise error if deletion of old draft failed
+    if (!$deleted)
+      raise_error(array('code' => 800,
+                        'type' => 'imap',
+                        'file' => __FILE__,
+                        'message' => "Could not delete message from ".$CONFIG['drafts_mbox']), TRUE, FALSE);
+    }
+  }
+
+if ($savedraft)
+  {
+  // clear the "saving message" busy status, and display success
+  show_message('messagesaved', 'confirmation');
+
+  // update "_draft_saveid" on the page, which is used to delete a previous draft
+  $frameout = "var foundid = parent.rcube_find_object('_draft_saveid', parent.document);\n";
+  $frameout .= sprintf("foundid.value = '%s';\n", str_replace(array('<','>'), "", $message_id));
+
+  // update the "cmp_hash" to prevent "Unsaved changes" warning
+  $frameout .= sprintf("parent.%s.cmp_hash = parent.%s.compose_field_hash();\n", $JS_OBJECT_NAME, $JS_OBJECT_NAME);
+
+  // start the auto-save timer again
+  $frameout .= sprintf("parent.%s.auto_save_start();", $JS_OBJECT_NAME);
+
+  // send html page with JS calls as response
+  rcube_iframe_response($frameout);
+  }
+else
+  {
+  if ($CONFIG['smtp_log'])
+    {
+    $log_entry = sprintf("[%s] User: %d on %s; Message for %s; Subject: %s\n",
+                 date("d-M-Y H:i:s O", mktime()),
+                 $_SESSION['user_id'],
+                 $_SERVER['REMOTE_ADDR'],
+                 $mailto,
+                 $msg_subject);
+
+    if ($fp = @fopen($CONFIG['log_dir'].'/sendmail', 'a'))
+      {
+      fwrite($fp, $log_entry);
+      fclose($fp);
+      }
+    }
+
+  rcmail_compose_cleanup();
+  rcube_iframe_response(sprintf("parent.$JS_OBJECT_NAME.sent_successfully('%s');",
+                                rep_specialchars_output(rcube_label('messagesent'), 'js')));
+  }
+
+
+?>
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
new file mode 100644 (file)
index 0000000..bbf43b8
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/show.inc                                           |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Display a mail message similar as a usual mail application does     |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: show.inc 277 2006-07-19 19:36:35Z thomasb $
+
+*/
+
+require_once('Mail/mimeDecode.php');
+
+$PRINT_MODE = $_action=='print' ? TRUE : FALSE;
+
+
+// similar code as in program/steps/mail/get.inc
+if ($_GET['_uid'])
+  {
+  $MESSAGE = array();
+  $MESSAGE['headers'] = $IMAP->get_headers($_GET['_uid']);
+  $MESSAGE['source'] = rcmail_message_source($_GET['_uid']);
+  
+  // go back to list if message not found (wrong UID)
+  if (!$MESSAGE['headers'] || !$MESSAGE['source'])
+    {
+    $_action = 'list';
+    return;
+    }
+
+  $mmd = new Mail_mimeDecode($MESSAGE['source']);
+  $MESSAGE['structure'] = $mmd->decode(array('include_bodies' => TRUE,
+                                             'decode_headers' => FALSE,
+                                             'decode_bodies' => FALSE));
+                                             
+  $mmd->getMimeNumbers($MESSAGE['structure']);
+
+  $MESSAGE['subject'] = $IMAP->decode_header($MESSAGE['structure']->headers['subject']);
+
+  if ($MESSAGE['structure'])
+    list($MESSAGE['parts'], $MESSAGE['attachments']) = rcmail_parse_message($MESSAGE['structure'],
+                                                                           array('safe' => (bool)$_GET['_safe'],
+                                                                                 'prefer_html' => $CONFIG['prefer_html'],
+                                                                                 'get_url' => $GET_URL.'&_part=%s'));
+  else
+    $MESSAGE['body'] = $IMAP->get_body($_GET['_uid']);
+
+
+  // mark message as read
+  if (!$MESSAGE['headers']->seen)
+    $IMAP->set_flag($_GET['_uid'], 'SEEN');
+
+  // give message uid to the client
+  $javascript = sprintf("%s.set_env('uid', '%s');\n", $JS_OBJECT_NAME, $_GET['_uid']);
+  $javascript .= sprintf("%s.set_env('safemode', '%b');", $JS_OBJECT_NAME, $_GET['_safe']);
+
+  $next = $prev = -1;
+  // get previous and next message UID
+  if (!($_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] == 'DESC') && 
+      $IMAP->get_capability('sort')) {
+      // Only if we use custom sorting
+      $a_msg_index = $IMAP->message_index(NULL, $_SESSION['sort_col'], $_SESSION['sort_order']);
+      $MESSAGE['index'] = array_search((string)$_GET['_uid'], $a_msg_index, TRUE);
+      $prev = isset($a_msg_index[$MESSAGE['index']-1]) ? $a_msg_index[$MESSAGE['index']-1] : -1 ;
+      $next = isset($a_msg_index[$MESSAGE['index']+1]) ? $a_msg_index[$MESSAGE['index']+1] : -1 ;
+  } else {
+      // this assumes that we are sorted by date_DESC
+      $seq = $IMAP->get_id($_GET['_uid']);
+      $prev = $IMAP->get_uid($seq + 1);
+      $next = $IMAP->get_uid($seq - 1);
+      $MESSAGE['index'] = $IMAP->messagecount() - $seq;
+  }
+  
+  if ($prev > 0)
+    $javascript .= sprintf("\n%s.set_env('prev_uid', '%s');", $JS_OBJECT_NAME, $prev);
+  if ($next > 0)
+    $javascript .= sprintf("\n%s.set_env('next_uid', '%s');", $JS_OBJECT_NAME, $next);
+
+  $OUTPUT->add_script($javascript);
+  }
+
+
+
+function rcmail_message_attachments($attrib)
+  {
+  global $CONFIG, $OUTPUT, $PRINT_MODE, $MESSAGE, $GET_URL, $JS_OBJECT_NAME;
+
+  if (sizeof($MESSAGE['attachments']))
+    {
+    // allow the following attributes to be added to the <ul> tag
+    $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
+    $out = '<ul' . $attrib_str . ">\n";
+
+    foreach ($MESSAGE['attachments'] as $attach_prop)
+      {
+      if ($PRINT_MODE)
+        $out .= sprintf('<li>%s (%s)</li>'."\n",
+                        $attach_prop['filename'],
+                        show_bytes($attach_prop['size']));
+      else
+        $out .= sprintf('<li><a href="%s&amp;_part=%s" onclick="return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)">%s</a></li>'."\n",
+                        htmlentities($GET_URL),
+                        $attach_prop['part_id'],
+                        $JS_OBJECT_NAME,
+                        $attach_prop['part_id'],
+                        $attach_prop['mimetype'],
+                        $attach_prop['filename']);
+      }
+
+    $out .= "</ul>";
+    return $out;
+    }  
+  }
+
+
+
+// return an HTML iframe for loading mail content
+function rcmail_messagecontent_frame($attrib)
+  {
+  global $COMM_PATH, $OUTPUT, $GET_URL, $JS_OBJECT_NAME;
+  
+  // allow the following attributes to be added to the <iframe> tag
+  $attrib_str = create_attrib_string($attrib);
+  $framename = 'rcmailcontentwindow';
+  
+  $out = sprintf('<iframe src="%s" name="%s"%s>%s</iframe>'."\n",
+         $GET_URL,
+         $framename,
+         $attrib_str,
+         rcube_label('loading'));
+
+
+  $OUTPUT->add_script("$JS_OBJECT_NAME.set_env('contentframe', '$framename');");
+
+  return $out;
+  }
+
+
+function rcmail_remote_objects_msg($attrib)
+  {
+  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+  
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmremoteobjmsg';
+
+  // allow the following attributes to be added to the <div> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
+  $out = '<div' . $attrib_str . ">";
+  
+  $out .= rep_specialchars_output(sprintf('%s&nbsp;<a href="#loadimages" onclick="%s.command(\'load-images\')" title="%s">%s</a>',
+                                  rcube_label('blockedimages'),
+                                  $JS_OBJECT_NAME,
+                                  rcube_label('showimages'),
+                                  rcube_label('showimages')));
+  
+  $out .= '</div>';
+  
+  $OUTPUT->add_script(sprintf("%s.gui_object('remoteobjectsmsg', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+  return $out;
+  }
+
+
+if ($_action=='print')
+  parse_template('printmessage');
+else
+  parse_template('message');
+?>
\ No newline at end of file
diff --git a/program/steps/mail/spell.inc b/program/steps/mail/spell.inc
new file mode 100644 (file)
index 0000000..ea22a32
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/spell.inc                                          |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Submit request to Google's spell checking engine                    |
+ |                                                                       |
+ | CREDITS:                                                              |
+ |   Script from GoogieSpell by amix.dk                                  |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: spell.inc 265 2006-06-25 10:04:45Z thomasb $
+
+*/
+
+$REMOTE_REQUEST = TRUE;
+
+$google = "ssl://www.google.com";
+$port = 443;
+$lang = $_GET['lang'];
+$path = "/tbproxy/spell?lang=$lang";
+$data = file_get_contents('php://input');
+$store = "";
+
+if ($fp = fsockopen($google, $port, $errno, $errstr, 30))
+  {
+  $out = "POST $path HTTP/1.0\r\n";
+  $out .= "Host: $google\r\n";
+  $out .= "Content-Length: " . strlen($data) . "\r\n";
+  $out .= "Content-type: application/x-www-form-urlencoded\r\n";
+  $out .= "Connection: Close\r\n\r\n";
+  $out .= $data;
+  fwrite($fp, $out);
+  
+  while (!feof($fp))
+    $store .= fgets($fp, 128);
+  fclose($fp);
+  }
+
+print $store;  
+exit;
+
+?>
\ No newline at end of file
diff --git a/program/steps/mail/upload.inc b/program/steps/mail/upload.inc
new file mode 100644 (file)
index 0000000..22fb7c8
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/upload.inc                                         |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Handle file-upload and make them available as attachments           |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: upload.inc 297 2006-08-06 15:55:11Z thomasb $
+
+*/
+
+
+if (!$_SESSION['compose'])
+  {
+  exit;
+  }
+
+
+// create temp dir for file uploads
+$temp_dir = rcmail_create_compose_tempdir();
+
+
+if (!is_array($_SESSION['compose']['attachments']))
+  $_SESSION['compose']['attachments'] = array();
+
+
+$response = '';
+
+foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath)
+  {
+  $tmpfname = tempnam($temp_dir, 'rcmAttmnt');
+  if (move_uploaded_file($filepath, $tmpfname))
+    {
+    $id = count($_SESSION['compose']['attachments']);
+    $_SESSION['compose']['attachments'][] = array('name' => $_FILES['_attachments']['name'][$i],
+                                                  'mimetype' => $_FILES['_attachments']['type'][$i],
+                                                  'path' => $tmpfname);
+
+    if (is_file($CONFIG['skin_path'] . '/images/icons/remove-attachment.png'))
+      $button = sprintf('<img src="%s/images/icons/remove-attachment.png" alt="%s" border="0" style="padding-right:2px;vertical-align:middle" />',
+                        $CONFIG['skin_path'],
+                        rcube_label('delete'));
+    else
+      $button = rcube_label('delete');
+
+    $content = sprintf('<a href="#delete" onclick="return %s.command(\\\'remove-attachment\\\', \\\'rcmfile%d\\\', this)" title="%s">%s</a>%s',
+                       $JS_OBJECT_NAME,
+                       $id,
+                       rcube_label('delete'),
+                       $button,
+                       rep_specialchars_output($_FILES['_attachments']['name'][$i], 'js'));
+
+    $response .= sprintf('parent.%s.add2attachment_list(\'rcmfile%d\',\'%s\');',
+                         $JS_OBJECT_NAME,
+                         $id,
+                         $content);
+    }
+  }
+
+
+// send html page with JS calls as response
+$frameout = <<<EOF
+$response
+parent.$JS_OBJECT_NAME.show_attachment_form(false);
+parent.$JS_OBJECT_NAME.auto_save_start();
+EOF;
+
+rcube_iframe_response($frameout);
+
+?>
diff --git a/program/steps/mail/viewsource.inc b/program/steps/mail/viewsource.inc
new file mode 100644 (file)
index 0000000..76046db
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/viewsource.inc                                     |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Display a mail message similar as a usual mail application does     |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: viewsource.inc 8 2005-09-28 22:28:05Z roundcube $
+
+*/
+
+
+// similar code as in program/steps/mail/get.inc
+if ($_GET['_uid'])
+  {
+  header('Content-Type: text/plain');
+  print rcmail_message_source($_GET['_uid']);
+  }
+else
+  {
+  raise_error(array('code' => 500,
+                    'type' => 'php',
+                    'message' => 'Message UID '.$_GET['_uid'].' not found'),
+              TRUE,
+              TRUE);
+  }
+
+exit;
+?>
\ No newline at end of file
diff --git a/program/steps/settings/delete_identity.inc b/program/steps/settings/delete_identity.inc
new file mode 100644 (file)
index 0000000..669608e
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/settings/delete_identity.inc                            |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Delete the submitted identities (IIDs) from the database            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: delete_identity.inc 429 2006-12-22 22:26:24Z thomasb $
+
+*/
+
+$REMOTE_REQUEST = $_GET['_remote'] ? TRUE : FALSE;
+
+if ($_GET['_iid'] && preg_match('/^[0-9]+(,[0-9]+)*$/',$_GET['_iid']))
+  {
+  $DB->query("UPDATE ".get_table_name('identities')."
+              SET    del=1
+              WHERE  user_id=?
+              AND    identity_id IN (".$_GET['_iid'].")",
+              $_SESSION['user_id']);
+
+  $count = $DB->affected_rows();
+  if ($count)
+    {
+    $commands = show_message('deletedsuccessfully', 'confirmation');
+    }
+
+  // send response
+  if ($REMOTE_REQUEST)
+    rcube_remote_response($commands);
+  }
+
+
+if ($REMOTE_REQUEST)
+  exit;
+
+
+// go to identities page
+$_action = 'identities';
+
+// overwrite action variable  
+$OUTPUT->add_script(sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $_action));
+?>
diff --git a/program/steps/settings/edit_identity.inc b/program/steps/settings/edit_identity.inc
new file mode 100644 (file)
index 0000000..f29f94d
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/settings/edit_identity.inc                              |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Show edit form for a identity record or to add a new one            |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: edit_identity.inc 88 2005-12-03 16:54:12Z roundcube $
+
+*/
+
+if (($_GET['_iid'] || $_POST['_iid']) && $_action=='edit-identity')
+  {
+  $id = $_POST['_iid'] ? $_POST['_iid'] : $_GET['_iid'];
+  $DB->query("SELECT * FROM ".get_table_name('identities')."
+              WHERE  identity_id=?
+              AND    user_id=?
+              AND    del<>1",
+              $id,
+              $_SESSION['user_id']);
+  
+  $IDENTITY_RECORD = $DB->fetch_assoc();
+  
+  if (is_array($IDENTITY_RECORD))
+    $OUTPUT->add_script(sprintf("%s.set_env('iid', '%s');", $JS_OBJECT_NAME, $IDENTITY_RECORD['identity_id']));
+    
+  $PAGE_TITLE = rcube_label('edititem');
+  }
+else
+  $PAGE_TITLE = rcube_label('newitem');
+
+
+
+function rcube_identity_form($attrib)
+  {
+  global $IDENTITY_RECORD, $JS_OBJECT_NAME;
+
+  if (!$IDENTITY_RECORD && $GLOBALS['_action']!='add-identity')
+    return rcube_label('notfound');
+
+  // add some labels to client
+  rcube_add_label('noemailwarning');
+  rcube_add_label('nonamewarning');
+
+
+  list($form_start, $form_end) = get_form_tags($attrib, 'save-identity', array('name' => '_iid', 'value' => $IDENTITY_RECORD['identity_id']));
+  unset($attrib['form']);
+
+
+  // list of available cols
+  $a_show_cols = array('name'         => array('type' => 'text'),
+                       'email'        => array('type' => 'text'),
+                       'organization' => array('type' => 'text'),
+                       'reply-to'     => array('type' => 'text', 'label' => 'replyto'),
+                       'bcc'          => array('type' => 'text'),
+                       'signature'       => array('type' => 'textarea'),
+                       'standard'     => array('type' => 'checkbox', 'label' => 'setdefault'));
+
+
+  // a specific part is requested
+  if ($attrib['part'])
+    {
+    $colprop = $a_show_cols[$attrib['part']];
+    if (is_array($colprop))
+      {
+      $out = $form_start;
+      $out .= rcmail_get_edit_field($attrib['part'], $IDENTITY_RECORD[$attrib['part']], $attrib, $colprop['type']); 
+      return $out;
+      }
+    else
+      return '';
+    }
+
+
+  // return the complete edit form as table
+  $out = "$form_start<table>\n\n";
+
+  foreach ($a_show_cols as $col => $colprop)
+    {
+    $attrib['id'] = 'rcmfd_'.$col;
+    $label = strlen($colprop['label']) ? $colprop['label'] : $col;
+    $value = rcmail_get_edit_field($col, $IDENTITY_RECORD[$col], $attrib, $colprop['type']);
+
+    $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                    $attrib['id'],
+                    rep_specialchars_output(rcube_label($label)),
+                    $value);
+    }
+
+  $out .= "\n</table>$form_end";
+
+  return $out;  
+  }
+
+
+
+if ($_action=='add-identity' && template_exists('addidentity'))
+  parse_template('addidentity');
+
+parse_template('editidentity');
+?>
\ No newline at end of file
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
new file mode 100644 (file)
index 0000000..429e176
--- /dev/null
@@ -0,0 +1,222 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/settings/func.inc                                       |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide functionality for user's settings & preferences             |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: func.inc 171 2006-03-23 22:32:47Z roundcube $
+
+*/
+
+
+// get user record
+$sql_result = $DB->query("SELECT username, mail_host FROM ".get_table_name('users')."
+                          WHERE  user_id=?",
+                          $_SESSION['user_id']);
+                                 
+if ($USER_DATA = $DB->fetch_assoc($sql_result))
+  $PAGE_TITLE = sprintf('%s %s@%s', rcube_label('settingsfor'), $USER_DATA['username'], $USER_DATA['mail_host']);
+
+
+
+function rcmail_user_prefs_form($attrib)
+  {
+  global $DB, $CONFIG, $sess_user_lang;
+
+  // add some labels to client
+  rcube_add_label('nopagesizewarning');
+  
+  list($form_start, $form_end) = get_form_tags($attrib, 'save-prefs');
+  unset($attrib['form']);
+
+  // allow the following attributes to be added to the <table> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+
+  // return the complete edit form as table
+  $out = "$form_start<table" . $attrib_str . ">\n\n";
+
+  $a_show_cols = array('language'   => array('type' => 'text'),
+                       'pagesize'   => array('type' => 'text'),
+                       'timezone'   => array('type' => 'text'),
+                       'prettydate' => array('type' => 'text'));
+                       
+  // show language selection
+  $a_lang = rcube_list_languages();
+  asort($a_lang);
+  
+  $field_id = 'rcmfd_lang';
+  $select_lang = new select(array('name' => '_language', 'id' => $field_id));
+  $select_lang->add(array_values($a_lang), array_keys($a_lang));
+  
+
+  $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                  $field_id,
+                  rep_specialchars_output(rcube_label('language')),
+                  $select_lang->show($sess_user_lang));
+
+
+  // show page size selection
+  $field_id = 'rcmfd_timezone';
+  $select_timezone = new select(array('name' => '_timezone', 'id' => $field_id));
+  $select_timezone->add('(GMT -11:00) Midway Island, Samoa', '-11');
+  $select_timezone->add('(GMT -10:00) Hawaii', '-10');
+  $select_timezone->add('(GMT -9:00) Alaska', '-9');
+  $select_timezone->add('(GMT -8:00) Pacific Time (US/Canada)', '-8');
+  $select_timezone->add('(GMT -7:00) Mountain Time (US/Canada)', '-7');
+  $select_timezone->add('(GMT -6:00) Central Time (US/Canada), Mexico City', '-6');
+  $select_timezone->add('(GMT -5:00) Eastern Time (US/Canada), Bogota, Lima', '-5');
+  $select_timezone->add('(GMT -4:00) Atlantic Time (Canada), Caracas, La Paz', '-4');
+  $select_timezone->add('(GMT -3:00) Brazil, Buenos Aires, Georgetown', '-3');
+  $select_timezone->add('(GMT -3:30) Nfld Time (Canada), Nfld, S. Labador', '-3.5');
+  $select_timezone->add('(GMT -2:00) Mid-Atlantic', '-2');
+  $select_timezone->add('(GMT -1:00) Azores, Cape Verde Islands', '-1');
+  $select_timezone->add('(GMT) Western Europe, London, Lisbon, Casablanca', '0');
+  $select_timezone->add('(GMT +1:00) Central European Time', '1');
+  $select_timezone->add('(GMT +2:00) EET: Kaliningrad, South Africa', '2');
+  $select_timezone->add('(GMT +3:00) Baghdad, Kuwait, Riyadh, Moscow, Nairobi', '3');
+  $select_timezone->add('(GMT +3:30) Tehran', '3.5');
+  $select_timezone->add('(GMT +4:00) Abu Dhabi, Muscat, Baku, Tbilisi', '4');
+  $select_timezone->add('(GMT +4:30) Kabul', '4.5');
+  $select_timezone->add('(GMT +5:00) Ekaterinburg, Islamabad, Karachi', '5');
+  $select_timezone->add('(GMT +5:30) Chennai, Kolkata, Mumbai, New Delhi', '5.5');
+  $select_timezone->add('(GMT +5:45) Kathmandu', '5.75');
+  $select_timezone->add('(GMT +6:00) Almaty, Dhaka, Colombo', '6');
+  $select_timezone->add('(GMT +7:00) Bangkok, Hanoi, Jakarta', '7');
+  $select_timezone->add('(GMT +8:00) Beijing, Perth, Singapore, Taipei', '8');
+  $select_timezone->add('(GMT +9:00) Tokyo, Seoul, Yakutsk', '9');
+  $select_timezone->add('(GMT +9:30) Adelaide, Darwin', '9.5');
+  $select_timezone->add('(GMT +10:00) EAST/AEST: Guam, Vladivostok', '10');
+  $select_timezone->add('(GMT +11:00) Magadan, Solomon Islands', '11');
+  $select_timezone->add('(GMT +12:00) Auckland, Wellington, Kamchatka', '12');
+  $select_timezone->add('(GMT +13:00) Tonga, Pheonix Islands', '13');
+  $select_timezone->add('(GMT +14:00) Kiribati', '14');
+  
+  
+  $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                  $field_id,
+                  rep_specialchars_output(rcube_label('timezone')),
+                  $select_timezone->show($CONFIG['timezone']));
+
+
+  $field_id = 'rcmfd_dst';
+  $input_dst = new checkbox(array('name' => '_dst_active', 'id' => $field_id, 'value' => 1));
+  $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                  $field_id,
+                  rep_specialchars_output(rcube_label('dstactive')),
+                  $input_dst->show($CONFIG['dst_active']));
+
+
+  // show page size selection
+  $field_id = 'rcmfd_pgsize';
+  $input_pagesize = new textfield(array('name' => '_pagesize', 'id' => $field_id, 'size' => 5));
+
+  $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                  $field_id,
+                  rep_specialchars_output(rcube_label('pagesize')),
+                  $input_pagesize->show($CONFIG['pagesize']));
+
+  // show checkbox for HTML/plaintext messages
+  $field_id = 'rcmfd_htmlmsg';
+  $input_pagesize = new checkbox(array('name' => '_prefer_html', 'id' => $field_id, 'value' => 1));
+
+  $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                  $field_id,
+                  rep_specialchars_output(rcube_label('preferhtml')),
+                  $input_pagesize->show($CONFIG['prefer_html']?1:0));
+
+  // MM: Show checkbox for toggling 'pretty dates' 
+  $field_id = 'rcmfd_prettydate';
+  $input_prettydate = new checkbox(array('name' => '_pretty_date', 'id' => $field_id, 'value' => 1));
+
+  $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                  $field_id,
+                  rep_specialchars_output(rcube_label('prettydate')),
+                  $input_prettydate->show($CONFIG['prettydate']?1:0));
+
+
+  $out .= "\n</table>$form_end";
+
+  return $out;  
+  }
+
+
+
+
+function rcmail_identities_list($attrib)
+  {
+  global $DB, $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+
+
+  // get contacts from DB
+  $sql_result = $DB->query("SELECT * FROM ".get_table_name('identities')."
+                            WHERE  del<>1
+                            AND    user_id=?
+                            ORDER BY standard DESC, name ASC",
+                            $_SESSION['user_id']);
+
+
+  // add id to message list table if not specified
+  if (!strlen($attrib['id']))
+    $attrib['id'] = 'rcmIdentitiesList';
+
+  // define list of cols to be displayed
+  $a_show_cols = array('name', 'email', 'organization', 'reply-to');
+
+  // create XHTML table  
+  $out = rcube_table_output($attrib, $sql_result, $a_show_cols, 'identity_id');
+  
+  // set client env
+  $javascript = sprintf("%s.gui_object('identitieslist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
+  $OUTPUT->add_script($javascript);    
+
+  return $out;
+  }
+
+
+
+// similar function as in /steps/addressbook/edit.inc
+function get_form_tags($attrib, $action, $add_hidden=array())
+  {
+  global $OUTPUT, $JS_OBJECT_NAME, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
+
+  $form_start = '';
+  if (!strlen($EDIT_FORM))
+    {
+    $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
+    $hiddenfields->add(array('name' => '_action', 'value' => $action));
+    
+    if ($add_hidden)
+      $hiddenfields->add($add_hidden);
+    
+    if ($_GET['_framed'] || $_POST['_framed'])
+      $hiddenfields->add(array('name' => '_framed', 'value' => 1));
+    
+    $form_start = !strlen($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
+    $form_start .= "\n$SESS_HIDDEN_FIELD\n";
+    $form_start .= $hiddenfields->show();
+    }
+    
+  $form_end = (!strlen($EDIT_FORM) && !strlen($attrib['form'])) ? '</form>' : '';
+  $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
+
+  if (!strlen($EDIT_FORM))
+    $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('editform', '$form_name');");
+  
+  $EDIT_FORM = $form_name;
+
+  return array($form_start, $form_end);  
+  }
+
+
+?>
\ No newline at end of file
diff --git a/program/steps/settings/identities.inc b/program/steps/settings/identities.inc
new file mode 100644 (file)
index 0000000..f7fb31c
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/settings/identities.inc                                 |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Manage identities of a user account                                 |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: identities.inc 8 2005-09-28 22:28:05Z roundcube $
+
+*/
+
+if ($USER_DATA = $DB->fetch_assoc($sql_result))
+  $PAGE_TITLE = sprintf('%s (%s@%s)', rcube_label('identities'), $USER_DATA['username'], $USER_DATA['mail_host']);
+
+
+
+// similar function as /steps/addressbook/func.inc::rcmail_contact_frame()
+function rcmail_identity_frame($attrib)
+  {
+  global $OUTPUT, $JS_OBJECT_NAME;
+
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmIdentityFrame';
+
+  $attrib['name'] = $attrib['id'];
+
+  $OUTPUT->add_script(sprintf("%s.set_env('contentframe', '%s');", $JS_OBJECT_NAME, $attrib['name']));
+
+  $attrib_str = create_attrib_string($attrib, array('name', 'id', 'class', 'style', 'src', 'width', 'height', 'frameborder'));
+  $out = '<iframe'. $attrib_str . '></iframe>';
+
+  return $out;
+  }
+
+
+
+parse_template('identities');
+?>
\ No newline at end of file
diff --git a/program/steps/settings/manage_folders.inc b/program/steps/settings/manage_folders.inc
new file mode 100644 (file)
index 0000000..8eb03ab
--- /dev/null
@@ -0,0 +1,273 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/settings/manage_folders.inc                             |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide functionality to create/delete/rename folders               |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: manage_folders.inc 285 2006-07-30 19:38:06Z thomasb $
+
+*/
+
+// init IMAP connection
+rcmail_imap_init(TRUE);
+
+
+// subscribe to one or more mailboxes
+if ($_action=='subscribe')
+  {
+  if (strlen($_GET['_mboxes']))
+    $IMAP->subscribe(array($_GET['_mboxes']));
+
+  if ($REMOTE_REQUEST)
+    rcube_remote_response('// subscribed');
+  }
+
+// unsubscribe one or more mailboxes
+else if ($_action=='unsubscribe')
+  {
+  if (strlen($_GET['_mboxes']))
+    $IMAP->unsubscribe(array($_GET['_mboxes']));
+
+  if ($REMOTE_REQUEST)
+    rcube_remote_response('// unsubscribed');
+  }
+
+// create a new mailbox
+else if ($_action=='create-folder')
+  {
+  if (!empty($_GET['_name']))
+    $create = $IMAP->create_mailbox(trim(get_input_value('_name', RCUBE_INPUT_GET)), TRUE);
+
+  if ($create && $REMOTE_REQUEST)
+    {
+    $commands = sprintf("this.add_folder_row('%s')", rep_specialchars_output($create, 'js'));
+    rcube_remote_response($commands);
+    }
+  else if (!$create && $REMOTE_REQUEST)
+    {
+    $commands = show_message('errorsaving', 'error');
+    rcube_remote_response($commands);
+    }
+  else if (!$create)
+    show_message('errorsaving', 'error');
+  }
+
+// rename a mailbox
+else if ($_action=='rename-folder')
+  {
+  if (!empty($_GET['_folder_oldname']) && !empty($_GET['_folder_newname']))
+    $rename = $IMAP->rename_mailbox(get_input_value('_folder_oldname', RCUBE_INPUT_GET), trim(get_input_value('_folder_newname', RCUBE_INPUT_GET)));
+
+  if ($rename && $REMOTE_REQUEST)
+    {
+    $commands = sprintf("this.replace_folder_row('%s','%s');",
+                        addslashes(rep_specialchars_output($rename, 'js')),
+                        rep_specialchars_output($_GET['_folder_oldname'], 'js'));
+    rcube_remote_response($commands);
+    }
+  else if (!$rename && $REMOTE_REQUEST)
+    {
+    $commands = "this.reset_folder_rename();\n";
+    $commands .= show_message('errorsaving', 'error');
+    rcube_remote_response($commands);
+    }
+  else if (!$rename)
+    show_message('errorsaving', 'error');
+  }
+
+// delete an existing IMAP mailbox
+else if ($_action=='delete-folder')
+  {
+  if (!empty($_GET['_mboxes']))
+    $deleted = $IMAP->delete_mailbox(array(get_input_value('_mboxes', RCUBE_INPUT_GET)));
+
+  if ($REMOTE_REQUEST && $deleted)
+    {
+    $commands = sprintf("this.remove_folder_row('%s');\n", rep_specialchars_output(get_input_value('_mboxes', RCUBE_INPUT_GET), 'js'));
+    $commands .= show_message('folderdeleted', 'confirmation');
+    rcube_remote_response($commands);
+    }
+  else if ($REMOTE_REQUEST)
+    {
+    $commands = show_message('errorsaving', 'error');
+    rcube_remote_response($commands);
+    }
+  }
+
+
+
+// build table with all folders listed by server
+function rcube_subscription_form($attrib)
+  {
+  global $IMAP, $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+
+  list($form_start, $form_end) = get_form_tags($attrib, 'folders');
+  unset($attrib['form']);
+  
+  
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmSubscriptionlist';
+
+  // allow the following attributes to be added to the <table> tag
+  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+
+  $out = "$form_start\n<table" . $attrib_str . ">\n";
+
+
+  // add table header
+  $out .= "<thead><tr>\n";
+  $out .= sprintf('<td class="name">%s</td><td class="subscribed">%s</td>'.
+                  '<td class="rename">&nbsp;</td><td class="delete">&nbsp;</td>',
+                  rcube_label('foldername'), rcube_label('subscribed'));
+                  
+  $out .= "\n</tr></thead>\n<tbody>\n";
+
+
+  // get folders from server
+  $a_unsubscribed = $IMAP->list_unsubscribed();
+  $a_subscribed = $IMAP->list_mailboxes();
+  $a_js_folders = array();
+  $checkbox_subscribe = new checkbox(array('name' => '_subscribed[]', 'onclick' => "$JS_OBJECT_NAME.command(this.checked?'subscribe':'unsubscribe',this.value)"));
+  
+  if (!empty($attrib['deleteicon']))
+    $del_button = sprintf('<img src="%s%s" alt="%s" border="0" />', $CONFIG['skin_path'], $attrib['deleteicon'], rcube_label('delete'));
+  else
+    $del_button = rcube_label('delete');
+
+  if (!empty($attrib['renameicon']))
+    $edit_button = sprintf('<img src="%s%s" alt="%s" border="0" />', $CONFIG['skin_path'], $attrib['renameicon'], rcube_label('rename'));
+  else
+    $del_button = rcube_label('rename');
+
+  // create list of available folders
+  foreach ($a_unsubscribed as $i => $folder)
+    {
+    $protected = ($CONFIG['protect_default_folders'] == TRUE && in_array($folder,$CONFIG['default_imap_folders']));
+    $zebra_class = $i%2 ? 'even' : 'odd';
+    $folder_js = rep_specialchars_output($folder, 'js');
+    
+    if (!$protected)
+      $a_js_folders['rcmrow'.($i+1)] = $folder_js;
+
+    $out .= sprintf('<tr id="rcmrow%d" class="%s"><td>%s</td><td>%s</td>',
+                    $i+1,
+                    $zebra_class,
+                    rep_specialchars_output(rcube_charset_convert($folder, 'UTF-7', 'UTF-8'), 'html', 'all'),
+                    $checkbox_subscribe->show(in_array($folder, $a_subscribed)?$folder:'', array('value' => $folder, 'disabled' => $protected)));
+
+    // add rename and delete buttons
+    if (!$protected)
+      $out .= sprintf('<td><a href="#rename" onclick="%s.command(\'rename-folder\',\'%s\')" title="%s">%s</a>'.
+                      '<td><a href="#delete" onclick="%s.command(\'delete-folder\',\'%s\')" title="%s">%s</a></td>',
+                      $JS_OBJECT_NAME,
+                      $folder_js,
+                      rcube_label('renamefolder'),
+                      $edit_button,
+                      $JS_OBJECT_NAME,
+                      $folder_js,
+                      rcube_label('deletefolder'),
+                      $del_button);
+    else
+      $out .= '<td></td><td></td>';
+    
+    $out .= "</tr>\n";
+    }
+
+  $out .= "</tbody>\n</table>";
+  $out .= "\n$form_end";
+
+
+  $javascript = sprintf("%s.gui_object('subscriptionlist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
+  $javascript .= sprintf("%s.set_env('subscriptionrows', %s);", $JS_OBJECT_NAME, array2js($a_js_folders));
+  $OUTPUT->add_script($javascript);
+
+  return $out;  
+  }
+
+
+function rcube_create_folder_form($attrib)
+  {
+  global $JS_OBJECT_NAME;
+
+  list($form_start, $form_end) = get_form_tags($attrib, 'create-folder');
+  unset($attrib['form']);
+
+
+  // return the complete edit form as table
+  $out = "$form_start\n";
+
+  $input = new textfield(array('name' => '_folder_name'));
+  $out .= $input->show();
+  
+  if (get_boolean($attrib['button']))
+    {
+    $button = new input_field(array('type' => 'button',
+                                    'value' => rcube_label('create'),
+                                    'onclick' => "$JS_OBJECT_NAME.command('create-folder',this.form)"));
+    $out .= $button->show();
+    }
+
+  $out .= "\n$form_end";
+
+  return $out;
+  }
+
+function rcube_rename_folder_form($attrib)
+  {
+  global $CONFIG, $IMAP, $JS_OBJECT_NAME;
+
+  list($form_start, $form_end) = get_form_tags($attrib, 'rename-folder');
+  unset($attrib['form']);
+
+  // return the complete edit form as table
+  $out = "$form_start\n";
+
+  $a_unsubscribed = $IMAP->list_unsubscribed();
+  $select_folder = new select(array('name' => '_folder_oldname', 'id' => 'rcmfd_oldfolder'));
+
+  foreach ($a_unsubscribed as $i => $folder)
+    {
+    if ($CONFIG['protect_default_folders'] == TRUE && in_array($folder,$CONFIG['default_imap_folders'])) 
+      continue;
+
+    $select_folder->add($folder);
+    }
+
+  $out .= $select_folder->show();
+
+  $out .= " to ";
+  $inputtwo = new textfield(array('name' => '_folder_newname'));
+  $out .= $inputtwo->show();
+
+  if (get_boolean($attrib['button']))
+    {
+    $button = new input_field(array('type' => 'button',
+                                    'value' => rcube_label('rename'),
+                                    'onclick' => "$JS_OBJECT_NAME.command('rename-folder',this.form)"));
+    $out .= $button->show();
+    }
+
+  $out .= "\n$form_end";
+
+  return $out;
+  }
+
+
+// add some labels to client
+rcube_add_label('deletefolderconfirm');
+
+
+parse_template('managefolders');
+?>
diff --git a/program/steps/settings/save_identity.inc b/program/steps/settings/save_identity.inc
new file mode 100644 (file)
index 0000000..253dcbd
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/settings/save_identity.inc                              |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Save an identity record or to add a new one                         |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: save_identity.inc 430 2006-12-22 22:31:38Z thomasb $
+
+*/
+
+$a_save_cols = array('name', 'email', 'organization', 'reply-to', 'bcc', 'standard', 'signature');
+$a_html_cols = array('signature');
+
+
+// check input
+if (empty($_POST['_name']) || empty($_POST['_email']))
+  {
+  show_message('formincomplete', 'warning');
+  rcmail_overwrite_action('edit-identitiy');
+  return;
+  }
+
+
+// update an existing contact
+if ($_POST['_iid'])
+  {
+  $a_write_sql = array();
+
+  foreach ($a_save_cols as $col)
+    {
+    $fname = '_'.$col;
+    if (!isset($_POST[$fname]))
+      continue;
+
+    $a_write_sql[] = sprintf("%s=%s",
+                             $DB->quoteIdentifier($col),
+                             $DB->quote(get_input_value($fname, RCUBE_INPUT_POST, in_array($col, $a_html_cols))));
+    }
+
+  if (sizeof($a_write_sql))
+    {
+    $DB->query("UPDATE ".get_table_name('identities')."
+                SET ".join(', ', $a_write_sql)."
+                WHERE  identity_id=?
+                AND    user_id=?
+                AND    del<>1",
+                $_POST['_iid'],
+                $_SESSION['user_id']);
+                       
+    $updated = $DB->affected_rows();
+    }
+       
+  if ($updated && !empty($_POST['_standard']))
+    {
+    show_message('successfullysaved', 'confirmation');
+
+    // mark all other identities as 'not-default'
+    $DB->query("UPDATE ".get_table_name('identities')."
+                SET ".$DB->quoteIdentifier('standard')."='0'
+                WHERE  user_id=?
+                AND    identity_id<>?
+                AND    del<>1",
+                $_SESSION['user_id'],
+                $_POST['_iid']);
+    
+    if ($_POST['_framed'])
+      {
+      // update the changed col in list
+      // ...      
+      }
+    }
+  else if ($DB->is_error())
+    {
+    // show error message
+    show_message('errorsaving', 'error');
+    rcmail_overwrite_action('edit-identitiy');
+    }
+  }
+
+// insert a new contact
+else
+  {
+  $a_insert_cols = $a_insert_values = array();
+
+  foreach ($a_save_cols as $col)
+    {
+    $fname = '_'.$col;
+    if (!isset($_POST[$fname]))
+      continue;
+    
+    $a_insert_cols[] = $DB->quoteIdentifier($col);
+    $a_insert_values[] = $DB->quote(get_input_value($fname, RCUBE_INPUT_POST, in_array($col, $a_html_cols)));
+    }
+    
+  if (sizeof($a_insert_cols))
+    {
+    $DB->query("INSERT INTO ".get_table_name('identities')."
+                (user_id, ".join(', ', $a_insert_cols).")
+                VALUES (?, ".join(', ', $a_insert_values).")",
+                $_SESSION['user_id']);
+
+    $insert_id = $DB->insert_id(get_sequence_name('identities'));
+    }
+    
+  if ($insert_id)
+    {
+    $_GET['_iid'] = $insert_id;
+
+    if ($_POST['_framed'])
+      {
+      // add contact row or jump to the page where it should appear
+      // ....
+      }
+    }
+  else
+    {
+    // show error message
+    show_message('errorsaving', 'error');
+    rcmail_overwrite_action('edit-identity');
+    }
+  }
+
+
+// go to next step
+rcmail_overwrite_action($_POST['_framed'] ? 'edit-identity' : 'identities');
+
+?>
\ No newline at end of file
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
new file mode 100644 (file)
index 0000000..f712547
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/settings/save_prefs.inc                                 |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Save user preferences to DB and to the current session              |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: save_prefs.inc 194 2006-04-13 18:23:48Z roundcube $
+
+*/
+
+$a_user_prefs = $_SESSION['user_prefs'];
+if (!is_array($a_user_prefs))
+  $a_user_prefs = array();
+
+
+$a_user_prefs['timezone'] = isset($_POST['_timezone']) ? floatval($_POST['_timezone']) : $CONFIG['timezone'];
+$a_user_prefs['dst_active'] = isset($_POST['_dst_active']) ? TRUE : FALSE;
+$a_user_prefs['pagesize'] = is_numeric($_POST['_pagesize']) ? (int)$_POST['_pagesize'] : $CONFIG['pagesize'];
+$a_user_prefs['prefer_html'] = isset($_POST['_prefer_html']) ? TRUE : FALSE;
+
+// MM: Date format toggle (Pretty / Standard)
+$a_user_prefs['prettydate'] = isset($_POST['_pretty_date']) ? TRUE : FALSE;
+
+if (isset($_POST['_language']))
+  {
+  $sess_user_lang = $_SESSION['user_lang'] = $_POST['_language'];
+  rcmail_set_locale($sess_user_lang);
+  }
+
+if (rcmail_save_user_prefs($a_user_prefs))
+  show_message('successfullysaved', 'confirmation');
+
+
+// go to next step
+$_action = 'preferences';
+
+// overwrite action variable  
+$OUTPUT->add_script(sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $_action));  
+
+?>
\ No newline at end of file
diff --git a/skins/default/addresses.css b/skins/default/addresses.css
new file mode 100644 (file)
index 0000000..5bb6c61
--- /dev/null
@@ -0,0 +1,119 @@
+/***** RoundCube|Mail address book task styles *****/
+
+
+#abooktoolbar
+{
+  position: absolute;
+  top: 45px;
+  left: 200px;
+  height: 35px;
+}
+
+#abooktoolbar a
+{
+  padding-right: 10px;
+}
+
+#abookcountbar
+{
+  position: absolute;
+  top: 60px;
+  left: 490px;
+  width: 240px;
+  height: 20px;
+  text-align: left;
+}
+
+#abookcountbar span
+{
+  font-size: 11px;
+  color: #333333;
+}
+
+
+#addresslist
+{
+  position: absolute;
+  top: 85px;
+  left: 20px;
+  width: 450px;
+  bottom: 40px;
+  border: 1px solid #999999;
+  background-color: #F9F9F9;
+  overflow: auto;
+  /* css hack for IE */
+  height: expression((parseInt(document.documentElement.clientHeight)-135)+'px');
+}
+
+#contacts-table
+{
+  width: 100%;
+  table-layout: fixed;
+  /* css hack for IE */
+  width: expression(document.getElementById('addresslist').clientWidth);
+}
+
+
+#contacts-table tbody td
+{
+  cursor: pointer;
+}
+
+
+#contacts-box
+{
+  position: absolute;
+  top: 85px;
+  left: 490px;
+  right: 40px;
+  bottom: 40px;
+  border: 1px solid #999999;
+  overflow: hidden;
+  /* css hack for IE */
+  width: expression((parseInt(document.documentElement.clientWidth)-530)+'px');
+  height: expression((parseInt(document.documentElement.clientHeight)-135)+'px');
+}
+
+body.iframe,
+#contact-frame
+{
+  background-color: #F9F9F9;
+}
+
+#contact-frame
+{
+  border: none;
+/* visibility: hidden; */
+}
+
+#contact-title
+{
+  height: 12px !important;
+/*  height: 20px; */
+  padding: 4px 20px 3px 20px;
+  border-bottom: 1px solid #999999;
+  color: #333333;
+  font-size: 11px;
+  font-weight: bold;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif); 
+}
+
+#contact-details
+{
+  padding: 15px 20px 10px 20px;
+}
+
+#contact-details table td.title
+{
+  color: #666666;
+  font-weight: bold;
+  text-align: right;
+  padding-right: 10px;
+}
+
+
+
+
+
+
diff --git a/skins/default/common.css b/skins/default/common.css
new file mode 100755 (executable)
index 0000000..6cec3cf
--- /dev/null
@@ -0,0 +1,298 @@
+/***** RoundCube|Mail basic styles *****/
+
+body
+{
+  margin: 8px;
+  background-color: #F6F6F6; /* #EBEBEB; */
+  color: #000000;
+}
+
+body.iframe
+{
+  margin: 0px;
+}
+
+body.extwin
+{
+  margin: 10px;
+}
+
+body, td, th, span, div, p, h3
+{
+  font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  color: #000000;
+}
+
+th
+{
+  font-weight: normal;
+}
+
+h3
+{
+  font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+  font-size: 18px;
+  color: #000000;
+}
+
+a, a:active, a:visited
+{
+  color: #000000;
+  outline: none;
+}
+
+a.button, a.button:visited, a.tab, a.tab:visited, a.axislist
+{
+  color: #000000;
+  text-decoration: none;
+}
+
+a.tab
+{
+  width: 80px;
+  display: block;
+  text-align: center;
+}
+
+hr
+{
+  height: 1px;
+  background-color: #666666;
+  border-style: none;
+}
+
+input, textarea
+{
+  font-size: 9pt;
+  font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+  padding: 1px;
+  padding-left: 3px;
+  padding-right: 3px;
+  background-color: #ffffff;
+  border: 1px solid #666666;
+}
+
+input.button
+{
+  height: 20px;
+  color: #333333;
+  font-size: 12px;
+  padding-left: 8px;
+  padding-right: 8px;
+  background: url(images/buttons/bg.gif) repeat-x #f0f0f0;
+  border: 1px solid #a4a4a4;
+}
+
+input.button:hover
+{
+  color: black;
+}
+
+img
+{
+  behavior: url('skins/default/pngbehavior.htc');
+}
+
+.alttext
+{
+  font-size: 11px;
+}
+
+
+/** common user interface objects */
+
+#header
+{
+  position: absolute;
+  top: 10px;
+  left: 20px;
+  width: 170px;
+  height: 40px;
+  z-index: 100;
+}
+
+#taskbar
+{
+  position: absolute;
+  top: 0px;
+  right: 0px;
+  width: 600px;
+  height: 37px;
+  background: url(images/taskbar.gif) top right no-repeat;
+  padding: 10px 24px 0px 0px;
+  text-align: right;
+  white-space: nowrap;
+  z-index: 2;
+}
+
+#taskbar a,
+#taskbar a:active,
+#taskbar a:visited
+{
+  font-size: 11px;
+  color: #666666;
+  text-decoration: none;
+  padding: 6px 16px 6px 30px;
+  background-repeat: no-repeat;
+}
+
+#taskbar a:hover
+{
+  color: #333333;
+}
+
+a.button-mail
+{
+  background-image: url(images/buttons/mail.gif);
+}
+
+a.button-addressbook
+{
+  background-image: url(images/buttons/addressbook.gif);
+}
+
+a.button-settings
+{
+  background-image: url(images/buttons/settings.gif);
+}
+
+a.button-logout
+{
+  background-image: url(images/buttons/logout.gif);
+}
+
+
+#message
+{
+  position: absolute;
+  display: none;
+  top: -1px;
+  left: 200px;
+  right: 200px;
+  z-index: 5000;
+  opacity: 0.85;
+}
+
+#message div
+{
+  width: 400px;
+  margin: 0px auto;
+  height: 24px;
+  min-height: 24px;
+  padding: 8px 10px 8px 46px;
+}
+
+#message div.notice,
+#remote-objects-message
+{
+  background: url(images/display/info.png) 6px 3px no-repeat;
+  background-color: #F7FDCB;
+  border: 1px solid #C2D071;
+}
+
+#message div.error,
+#message div.warning
+{
+  background: url(images/display/warning.png) 6px 3px no-repeat;
+  background-color: #EF9398;
+  border: 1px solid #DC5757;
+}
+
+#message div.confirmation
+{
+  background: url(images/display/confirm.png) 6px 3px no-repeat;
+  background-color: #A6EF7B;
+  border: 1px solid #76C83F;
+}
+
+#message div.loading
+{
+  background: url(images/display/loading.gif) 6px 3px no-repeat;
+  background-color: #EBEBEB;
+  border: 1px solid #CCCCCC;
+}
+
+
+/***** common table settings ******/
+
+table.records-table thead tr td
+{
+  height: 20px;
+  padding: 0px 4px 0px 4px;
+  vertical-align: middle;
+  border-bottom: 1px solid #999999;
+  color: #333333;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif); 
+  font-size: 11px;
+  font-weight: bold;
+}
+
+table.records-table tbody tr td
+{
+  height: 16px;
+  padding: 2px 4px 2px 4px;
+  font-size: 11px;
+  white-space: nowrap;
+  border-bottom: 1px solid #EBEBEB;
+  overflow: hidden;
+  text-align: left;  
+}
+
+table.records-table tr
+{
+  background-color: #FFFFFF;
+}
+
+table.records-table tr.selected td
+{
+  font-weight: bold;
+  color: #FFFFFF;
+  background-color: #CC3333;
+}
+
+
+
+/***** roundcube webmail pre-defined classes *****/
+
+a.rcmContactAddress
+{
+  text-decoration: none;
+}
+
+a.rcmContactAddress:hover
+{
+  text-decoration: underline;
+}
+
+#rcmKSearchpane
+{
+  background-color: #F9F9F9;
+  border: 1px solid #CCCCCC;
+}
+
+#rcmKSearchpane ul
+{
+  margin: 0px;
+  padding: 2px;
+  list-style-image: none;
+  list-style-type: none;
+}
+
+#rcmKSearchpane ul li
+{
+  height: 16px;
+  font-size: 11px;
+  padding-left: 8px;
+  padding-top: 2px;
+  padding-right: 8px;
+  white-space: nowrap;  
+}
+
+#rcmKSearchpane ul li.selected
+{
+  color: #ffffff;
+  background-color: #CC3333;
+}
+
diff --git a/skins/default/googiespell.css b/skins/default/googiespell.css
new file mode 100755 (executable)
index 0000000..5738338
--- /dev/null
@@ -0,0 +1,101 @@
+/***** modified styles for GoogieSpell *****/
+
+.googie_window {
+  font-size: 11px;
+  width: 185px;
+  text-align: left;
+  border: 1px solid #666666;
+  background-color: #ffffff;
+  margin: 0;
+  padding: 0;
+  position: absolute;
+  visibility: hidden;
+}
+
+.googie_list {
+  width: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+.googie_list td {
+  font-size: 11px;
+  padding-left: 10px;
+  padding-right: 10px;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  cursor: pointer;
+  list-style-type: none;
+}
+
+.googie_list_onhover {
+  color: #FFFFFF;
+  background-color: #CC3333;
+}
+
+.googie_list_onout {
+  background-color: #F6F6F6;
+}
+
+.googie_list_selected {
+  background-color: #cccccc;
+  font-weight: bold;
+}
+
+.googie_list_close {
+  font-size: 11px;
+  color: #b91414;
+}
+
+.googie_list_onhover .googie_list_close {
+  color: #FFFFFF;
+}
+
+.googie_list_revert {
+  font-size: 11px;
+  color: #b91479;
+}
+
+.googie_link {
+  color: #b91414;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.googie_check_spelling_link {  
+  color: #CC0000;
+  font-size: 11px;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.googie_check_spelling_link:hover {
+  text-decoration: underline;
+}
+
+.googie_no_style {
+  text-decoration: none;
+}
+
+.googie_check_spelling_ok {
+  color: green;
+  font-size: 11px;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+.googie_lang_3d_click img {
+  vertical-align: middle;
+  border-top: 1px solid #555;
+  border-left: 1px solid #555;
+  border-right: 1px solid #b1b1b1;
+  border-bottom: 1px solid #b1b1b1;
+}
+
+.googie_lang_3d_on img {
+  vertical-align: middle;
+  border-top: 1px solid #b1b1b1;
+  border-left: 1px solid #b1b1b1;
+  border-right: 1px solid #555;
+  border-bottom: 1px solid #555;
+}
diff --git a/skins/default/images/blank.gif b/skins/default/images/blank.gif
new file mode 100644 (file)
index 0000000..ea83374
Binary files /dev/null and b/skins/default/images/blank.gif differ
diff --git a/skins/default/images/buttons/add_act.png b/skins/default/images/buttons/add_act.png
new file mode 100644 (file)
index 0000000..0454ff8
Binary files /dev/null and b/skins/default/images/buttons/add_act.png differ
diff --git a/skins/default/images/buttons/add_contact_act.png b/skins/default/images/buttons/add_contact_act.png
new file mode 100644 (file)
index 0000000..994242c
Binary files /dev/null and b/skins/default/images/buttons/add_contact_act.png differ
diff --git a/skins/default/images/buttons/add_contact_pas.png b/skins/default/images/buttons/add_contact_pas.png
new file mode 100644 (file)
index 0000000..91ca0d0
Binary files /dev/null and b/skins/default/images/buttons/add_contact_pas.png differ
diff --git a/skins/default/images/buttons/add_contact_sel.png b/skins/default/images/buttons/add_contact_sel.png
new file mode 100644 (file)
index 0000000..effa91a
Binary files /dev/null and b/skins/default/images/buttons/add_contact_sel.png differ
diff --git a/skins/default/images/buttons/add_pas.png b/skins/default/images/buttons/add_pas.png
new file mode 100644 (file)
index 0000000..cf4de2e
Binary files /dev/null and b/skins/default/images/buttons/add_pas.png differ
diff --git a/skins/default/images/buttons/add_sel.png b/skins/default/images/buttons/add_sel.png
new file mode 100644 (file)
index 0000000..a6d8197
Binary files /dev/null and b/skins/default/images/buttons/add_sel.png differ
diff --git a/skins/default/images/buttons/addressbook.gif b/skins/default/images/buttons/addressbook.gif
new file mode 100644 (file)
index 0000000..d8c0c17
Binary files /dev/null and b/skins/default/images/buttons/addressbook.gif differ
diff --git a/skins/default/images/buttons/addressbook.png b/skins/default/images/buttons/addressbook.png
new file mode 100644 (file)
index 0000000..359f33e
Binary files /dev/null and b/skins/default/images/buttons/addressbook.png differ
diff --git a/skins/default/images/buttons/attach_act.png b/skins/default/images/buttons/attach_act.png
new file mode 100644 (file)
index 0000000..612d36d
Binary files /dev/null and b/skins/default/images/buttons/attach_act.png differ
diff --git a/skins/default/images/buttons/attach_pas.png b/skins/default/images/buttons/attach_pas.png
new file mode 100644 (file)
index 0000000..37c67c9
Binary files /dev/null and b/skins/default/images/buttons/attach_pas.png differ
diff --git a/skins/default/images/buttons/attach_sel.png b/skins/default/images/buttons/attach_sel.png
new file mode 100644 (file)
index 0000000..81a4700
Binary files /dev/null and b/skins/default/images/buttons/attach_sel.png differ
diff --git a/skins/default/images/buttons/back_act.png b/skins/default/images/buttons/back_act.png
new file mode 100644 (file)
index 0000000..d5352b5
Binary files /dev/null and b/skins/default/images/buttons/back_act.png differ
diff --git a/skins/default/images/buttons/back_pas.png b/skins/default/images/buttons/back_pas.png
new file mode 100644 (file)
index 0000000..ac15bad
Binary files /dev/null and b/skins/default/images/buttons/back_pas.png differ
diff --git a/skins/default/images/buttons/back_sel.png b/skins/default/images/buttons/back_sel.png
new file mode 100644 (file)
index 0000000..b25acbf
Binary files /dev/null and b/skins/default/images/buttons/back_sel.png differ
diff --git a/skins/default/images/buttons/bg.gif b/skins/default/images/buttons/bg.gif
new file mode 100644 (file)
index 0000000..e2191c9
Binary files /dev/null and b/skins/default/images/buttons/bg.gif differ
diff --git a/skins/default/images/buttons/compose_act.png b/skins/default/images/buttons/compose_act.png
new file mode 100644 (file)
index 0000000..c7e2d61
Binary files /dev/null and b/skins/default/images/buttons/compose_act.png differ
diff --git a/skins/default/images/buttons/compose_pas.png b/skins/default/images/buttons/compose_pas.png
new file mode 100644 (file)
index 0000000..5fd9d7a
Binary files /dev/null and b/skins/default/images/buttons/compose_pas.png differ
diff --git a/skins/default/images/buttons/compose_sel.png b/skins/default/images/buttons/compose_sel.png
new file mode 100644 (file)
index 0000000..2efd8cb
Binary files /dev/null and b/skins/default/images/buttons/compose_sel.png differ
diff --git a/skins/default/images/buttons/contacts_act.png b/skins/default/images/buttons/contacts_act.png
new file mode 100644 (file)
index 0000000..9876128
Binary files /dev/null and b/skins/default/images/buttons/contacts_act.png differ
diff --git a/skins/default/images/buttons/contacts_pas.png b/skins/default/images/buttons/contacts_pas.png
new file mode 100644 (file)
index 0000000..25dfd8c
Binary files /dev/null and b/skins/default/images/buttons/contacts_pas.png differ
diff --git a/skins/default/images/buttons/contacts_sel.png b/skins/default/images/buttons/contacts_sel.png
new file mode 100644 (file)
index 0000000..20df77c
Binary files /dev/null and b/skins/default/images/buttons/contacts_sel.png differ
diff --git a/skins/default/images/buttons/delete_act.png b/skins/default/images/buttons/delete_act.png
new file mode 100644 (file)
index 0000000..b141cd3
Binary files /dev/null and b/skins/default/images/buttons/delete_act.png differ
diff --git a/skins/default/images/buttons/delete_pas.png b/skins/default/images/buttons/delete_pas.png
new file mode 100644 (file)
index 0000000..fc1e892
Binary files /dev/null and b/skins/default/images/buttons/delete_pas.png differ
diff --git a/skins/default/images/buttons/delete_sel.png b/skins/default/images/buttons/delete_sel.png
new file mode 100644 (file)
index 0000000..b4c32c1
Binary files /dev/null and b/skins/default/images/buttons/delete_sel.png differ
diff --git a/skins/default/images/buttons/down_arrow.png b/skins/default/images/buttons/down_arrow.png
new file mode 100755 (executable)
index 0000000..d4f9e2d
Binary files /dev/null and b/skins/default/images/buttons/down_arrow.png differ
diff --git a/skins/default/images/buttons/download_act.png b/skins/default/images/buttons/download_act.png
new file mode 100644 (file)
index 0000000..694527d
Binary files /dev/null and b/skins/default/images/buttons/download_act.png differ
diff --git a/skins/default/images/buttons/download_pas.png b/skins/default/images/buttons/download_pas.png
new file mode 100644 (file)
index 0000000..fb39db5
Binary files /dev/null and b/skins/default/images/buttons/download_pas.png differ
diff --git a/skins/default/images/buttons/download_sel.png b/skins/default/images/buttons/download_sel.png
new file mode 100644 (file)
index 0000000..cfc44b3
Binary files /dev/null and b/skins/default/images/buttons/download_sel.png differ
diff --git a/skins/default/images/buttons/drafts_act.png b/skins/default/images/buttons/drafts_act.png
new file mode 100644 (file)
index 0000000..3fc718a
Binary files /dev/null and b/skins/default/images/buttons/drafts_act.png differ
diff --git a/skins/default/images/buttons/drafts_pas.png b/skins/default/images/buttons/drafts_pas.png
new file mode 100644 (file)
index 0000000..424709e
Binary files /dev/null and b/skins/default/images/buttons/drafts_pas.png differ
diff --git a/skins/default/images/buttons/drafts_sel.png b/skins/default/images/buttons/drafts_sel.png
new file mode 100644 (file)
index 0000000..34419b8
Binary files /dev/null and b/skins/default/images/buttons/drafts_sel.png differ
diff --git a/skins/default/images/buttons/edit_contact_act.png b/skins/default/images/buttons/edit_contact_act.png
new file mode 100644 (file)
index 0000000..57b2782
Binary files /dev/null and b/skins/default/images/buttons/edit_contact_act.png differ
diff --git a/skins/default/images/buttons/edit_contact_pas.png b/skins/default/images/buttons/edit_contact_pas.png
new file mode 100644 (file)
index 0000000..b999294
Binary files /dev/null and b/skins/default/images/buttons/edit_contact_pas.png differ
diff --git a/skins/default/images/buttons/edit_contact_sel.png b/skins/default/images/buttons/edit_contact_sel.png
new file mode 100644 (file)
index 0000000..616d583
Binary files /dev/null and b/skins/default/images/buttons/edit_contact_sel.png differ
diff --git a/skins/default/images/buttons/forward_act.png b/skins/default/images/buttons/forward_act.png
new file mode 100644 (file)
index 0000000..2fdbdba
Binary files /dev/null and b/skins/default/images/buttons/forward_act.png differ
diff --git a/skins/default/images/buttons/forward_pas.png b/skins/default/images/buttons/forward_pas.png
new file mode 100644 (file)
index 0000000..e671398
Binary files /dev/null and b/skins/default/images/buttons/forward_pas.png differ
diff --git a/skins/default/images/buttons/forward_sel.png b/skins/default/images/buttons/forward_sel.png
new file mode 100644 (file)
index 0000000..90f67bb
Binary files /dev/null and b/skins/default/images/buttons/forward_sel.png differ
diff --git a/skins/default/images/buttons/inbox_act.png b/skins/default/images/buttons/inbox_act.png
new file mode 100644 (file)
index 0000000..30c1e76
Binary files /dev/null and b/skins/default/images/buttons/inbox_act.png differ
diff --git a/skins/default/images/buttons/inbox_pas.png b/skins/default/images/buttons/inbox_pas.png
new file mode 100644 (file)
index 0000000..67f4da0
Binary files /dev/null and b/skins/default/images/buttons/inbox_pas.png differ
diff --git a/skins/default/images/buttons/inbox_sel.png b/skins/default/images/buttons/inbox_sel.png
new file mode 100644 (file)
index 0000000..89d661e
Binary files /dev/null and b/skins/default/images/buttons/inbox_sel.png differ
diff --git a/skins/default/images/buttons/ldap_act.png b/skins/default/images/buttons/ldap_act.png
new file mode 100644 (file)
index 0000000..b09f267
Binary files /dev/null and b/skins/default/images/buttons/ldap_act.png differ
diff --git a/skins/default/images/buttons/ldap_pas.png b/skins/default/images/buttons/ldap_pas.png
new file mode 100644 (file)
index 0000000..b09f267
Binary files /dev/null and b/skins/default/images/buttons/ldap_pas.png differ
diff --git a/skins/default/images/buttons/logout.gif b/skins/default/images/buttons/logout.gif
new file mode 100644 (file)
index 0000000..93eb1aa
Binary files /dev/null and b/skins/default/images/buttons/logout.gif differ
diff --git a/skins/default/images/buttons/logout.png b/skins/default/images/buttons/logout.png
new file mode 100644 (file)
index 0000000..2fe632a
Binary files /dev/null and b/skins/default/images/buttons/logout.png differ
diff --git a/skins/default/images/buttons/mail.gif b/skins/default/images/buttons/mail.gif
new file mode 100644 (file)
index 0000000..8bb93f7
Binary files /dev/null and b/skins/default/images/buttons/mail.gif differ
diff --git a/skins/default/images/buttons/mail.png b/skins/default/images/buttons/mail.png
new file mode 100644 (file)
index 0000000..30c1e76
Binary files /dev/null and b/skins/default/images/buttons/mail.png differ
diff --git a/skins/default/images/buttons/next_act.png b/skins/default/images/buttons/next_act.png
new file mode 100644 (file)
index 0000000..fed8294
Binary files /dev/null and b/skins/default/images/buttons/next_act.png differ
diff --git a/skins/default/images/buttons/next_pas.png b/skins/default/images/buttons/next_pas.png
new file mode 100644 (file)
index 0000000..df80ad3
Binary files /dev/null and b/skins/default/images/buttons/next_pas.png differ
diff --git a/skins/default/images/buttons/next_sel.png b/skins/default/images/buttons/next_sel.png
new file mode 100644 (file)
index 0000000..77cb1b1
Binary files /dev/null and b/skins/default/images/buttons/next_sel.png differ
diff --git a/skins/default/images/buttons/previous_act.png b/skins/default/images/buttons/previous_act.png
new file mode 100644 (file)
index 0000000..457d873
Binary files /dev/null and b/skins/default/images/buttons/previous_act.png differ
diff --git a/skins/default/images/buttons/previous_pas.png b/skins/default/images/buttons/previous_pas.png
new file mode 100644 (file)
index 0000000..db7186d
Binary files /dev/null and b/skins/default/images/buttons/previous_pas.png differ
diff --git a/skins/default/images/buttons/previous_sel.png b/skins/default/images/buttons/previous_sel.png
new file mode 100644 (file)
index 0000000..d102a53
Binary files /dev/null and b/skins/default/images/buttons/previous_sel.png differ
diff --git a/skins/default/images/buttons/print_act.png b/skins/default/images/buttons/print_act.png
new file mode 100644 (file)
index 0000000..19e1f33
Binary files /dev/null and b/skins/default/images/buttons/print_act.png differ
diff --git a/skins/default/images/buttons/print_pas.png b/skins/default/images/buttons/print_pas.png
new file mode 100644 (file)
index 0000000..b6c0e78
Binary files /dev/null and b/skins/default/images/buttons/print_pas.png differ
diff --git a/skins/default/images/buttons/print_sel.png b/skins/default/images/buttons/print_sel.png
new file mode 100644 (file)
index 0000000..0ddaa31
Binary files /dev/null and b/skins/default/images/buttons/print_sel.png differ
diff --git a/skins/default/images/buttons/reply_act.png b/skins/default/images/buttons/reply_act.png
new file mode 100644 (file)
index 0000000..89ad7cc
Binary files /dev/null and b/skins/default/images/buttons/reply_act.png differ
diff --git a/skins/default/images/buttons/reply_pas.png b/skins/default/images/buttons/reply_pas.png
new file mode 100644 (file)
index 0000000..0b38933
Binary files /dev/null and b/skins/default/images/buttons/reply_pas.png differ
diff --git a/skins/default/images/buttons/reply_sel.png b/skins/default/images/buttons/reply_sel.png
new file mode 100644 (file)
index 0000000..76f5eac
Binary files /dev/null and b/skins/default/images/buttons/reply_sel.png differ
diff --git a/skins/default/images/buttons/replyall_act.png b/skins/default/images/buttons/replyall_act.png
new file mode 100644 (file)
index 0000000..b6ad3fb
Binary files /dev/null and b/skins/default/images/buttons/replyall_act.png differ
diff --git a/skins/default/images/buttons/replyall_pas.png b/skins/default/images/buttons/replyall_pas.png
new file mode 100644 (file)
index 0000000..948072d
Binary files /dev/null and b/skins/default/images/buttons/replyall_pas.png differ
diff --git a/skins/default/images/buttons/replyall_sel.png b/skins/default/images/buttons/replyall_sel.png
new file mode 100644 (file)
index 0000000..483436c
Binary files /dev/null and b/skins/default/images/buttons/replyall_sel.png differ
diff --git a/skins/default/images/buttons/send_act.png b/skins/default/images/buttons/send_act.png
new file mode 100644 (file)
index 0000000..999d21d
Binary files /dev/null and b/skins/default/images/buttons/send_act.png differ
diff --git a/skins/default/images/buttons/send_pas.png b/skins/default/images/buttons/send_pas.png
new file mode 100644 (file)
index 0000000..db227c9
Binary files /dev/null and b/skins/default/images/buttons/send_pas.png differ
diff --git a/skins/default/images/buttons/send_sel.png b/skins/default/images/buttons/send_sel.png
new file mode 100644 (file)
index 0000000..fc3d133
Binary files /dev/null and b/skins/default/images/buttons/send_sel.png differ
diff --git a/skins/default/images/buttons/settings.gif b/skins/default/images/buttons/settings.gif
new file mode 100644 (file)
index 0000000..a390cd9
Binary files /dev/null and b/skins/default/images/buttons/settings.gif differ
diff --git a/skins/default/images/buttons/settings.png b/skins/default/images/buttons/settings.png
new file mode 100644 (file)
index 0000000..41395bf
Binary files /dev/null and b/skins/default/images/buttons/settings.png differ
diff --git a/skins/default/images/buttons/source_act.png b/skins/default/images/buttons/source_act.png
new file mode 100644 (file)
index 0000000..3971b5c
Binary files /dev/null and b/skins/default/images/buttons/source_act.png differ
diff --git a/skins/default/images/buttons/source_pas.png b/skins/default/images/buttons/source_pas.png
new file mode 100644 (file)
index 0000000..aec440a
Binary files /dev/null and b/skins/default/images/buttons/source_pas.png differ
diff --git a/skins/default/images/buttons/source_sel.png b/skins/default/images/buttons/source_sel.png
new file mode 100644 (file)
index 0000000..4749317
Binary files /dev/null and b/skins/default/images/buttons/source_sel.png differ
diff --git a/skins/default/images/buttons/spacer.gif b/skins/default/images/buttons/spacer.gif
new file mode 100644 (file)
index 0000000..5bfd67a
Binary files /dev/null and b/skins/default/images/buttons/spacer.gif differ
diff --git a/skins/default/images/buttons/spellcheck_act.png b/skins/default/images/buttons/spellcheck_act.png
new file mode 100644 (file)
index 0000000..dd3056e
Binary files /dev/null and b/skins/default/images/buttons/spellcheck_act.png differ
diff --git a/skins/default/images/buttons/spellcheck_pas.png b/skins/default/images/buttons/spellcheck_pas.png
new file mode 100644 (file)
index 0000000..3d41201
Binary files /dev/null and b/skins/default/images/buttons/spellcheck_pas.png differ
diff --git a/skins/default/images/buttons/spellcheck_sel.png b/skins/default/images/buttons/spellcheck_sel.png
new file mode 100644 (file)
index 0000000..8c24d6f
Binary files /dev/null and b/skins/default/images/buttons/spellcheck_sel.png differ
diff --git a/skins/default/images/buttons/up_arrow.png b/skins/default/images/buttons/up_arrow.png
new file mode 100755 (executable)
index 0000000..aca1b54
Binary files /dev/null and b/skins/default/images/buttons/up_arrow.png differ
diff --git a/skins/default/images/cleardot.png b/skins/default/images/cleardot.png
new file mode 100644 (file)
index 0000000..98c3bfc
Binary files /dev/null and b/skins/default/images/cleardot.png differ
diff --git a/skins/default/images/display/confirm.png b/skins/default/images/display/confirm.png
new file mode 100644 (file)
index 0000000..27265f8
Binary files /dev/null and b/skins/default/images/display/confirm.png differ
diff --git a/skins/default/images/display/info.png b/skins/default/images/display/info.png
new file mode 100644 (file)
index 0000000..85462e4
Binary files /dev/null and b/skins/default/images/display/info.png differ
diff --git a/skins/default/images/display/loading.gif b/skins/default/images/display/loading.gif
new file mode 100755 (executable)
index 0000000..747c656
Binary files /dev/null and b/skins/default/images/display/loading.gif differ
diff --git a/skins/default/images/display/warning.png b/skins/default/images/display/warning.png
new file mode 100644 (file)
index 0000000..9909617
Binary files /dev/null and b/skins/default/images/display/warning.png differ
diff --git a/skins/default/images/favicon.ico b/skins/default/images/favicon.ico
new file mode 100644 (file)
index 0000000..5a011f2
Binary files /dev/null and b/skins/default/images/favicon.ico differ
diff --git a/skins/default/images/googiespell/change_lang.gif b/skins/default/images/googiespell/change_lang.gif
new file mode 100644 (file)
index 0000000..8145183
Binary files /dev/null and b/skins/default/images/googiespell/change_lang.gif differ
diff --git a/skins/default/images/googiespell/indicator.gif b/skins/default/images/googiespell/indicator.gif
new file mode 100644 (file)
index 0000000..b556bb0
Binary files /dev/null and b/skins/default/images/googiespell/indicator.gif differ
diff --git a/skins/default/images/googiespell/ok.gif b/skins/default/images/googiespell/ok.gif
new file mode 100644 (file)
index 0000000..04727e2
Binary files /dev/null and b/skins/default/images/googiespell/ok.gif differ
diff --git a/skins/default/images/googiespell/spellc.gif b/skins/default/images/googiespell/spellc.gif
new file mode 100644 (file)
index 0000000..6ed9360
Binary files /dev/null and b/skins/default/images/googiespell/spellc.gif differ
diff --git a/skins/default/images/icons/abcard.png b/skins/default/images/icons/abcard.png
new file mode 100644 (file)
index 0000000..d0d8500
Binary files /dev/null and b/skins/default/images/icons/abcard.png differ
diff --git a/skins/default/images/icons/attachment.png b/skins/default/images/icons/attachment.png
new file mode 100644 (file)
index 0000000..0fcf464
Binary files /dev/null and b/skins/default/images/icons/attachment.png differ
diff --git a/skins/default/images/icons/deleted.png b/skins/default/images/icons/deleted.png
new file mode 100644 (file)
index 0000000..cffb7f5
Binary files /dev/null and b/skins/default/images/icons/deleted.png differ
diff --git a/skins/default/images/icons/dot.png b/skins/default/images/icons/dot.png
new file mode 100644 (file)
index 0000000..99f7365
Binary files /dev/null and b/skins/default/images/icons/dot.png differ
diff --git a/skins/default/images/icons/edit.png b/skins/default/images/icons/edit.png
new file mode 100644 (file)
index 0000000..9b40b5c
Binary files /dev/null and b/skins/default/images/icons/edit.png differ
diff --git a/skins/default/images/icons/flagged.png b/skins/default/images/icons/flagged.png
new file mode 100644 (file)
index 0000000..58e3e1c
Binary files /dev/null and b/skins/default/images/icons/flagged.png differ
diff --git a/skins/default/images/icons/folder-closed.png b/skins/default/images/icons/folder-closed.png
new file mode 100644 (file)
index 0000000..5cbf72a
Binary files /dev/null and b/skins/default/images/icons/folder-closed.png differ
diff --git a/skins/default/images/icons/folder-drafts.png b/skins/default/images/icons/folder-drafts.png
new file mode 100644 (file)
index 0000000..d828b56
Binary files /dev/null and b/skins/default/images/icons/folder-drafts.png differ
diff --git a/skins/default/images/icons/folder-inbox.png b/skins/default/images/icons/folder-inbox.png
new file mode 100644 (file)
index 0000000..995ca81
Binary files /dev/null and b/skins/default/images/icons/folder-inbox.png differ
diff --git a/skins/default/images/icons/folder-junk.png b/skins/default/images/icons/folder-junk.png
new file mode 100644 (file)
index 0000000..06fbd49
Binary files /dev/null and b/skins/default/images/icons/folder-junk.png differ
diff --git a/skins/default/images/icons/folder-open.png b/skins/default/images/icons/folder-open.png
new file mode 100644 (file)
index 0000000..09ba4b3
Binary files /dev/null and b/skins/default/images/icons/folder-open.png differ
diff --git a/skins/default/images/icons/folder-sent.png b/skins/default/images/icons/folder-sent.png
new file mode 100644 (file)
index 0000000..2968ab5
Binary files /dev/null and b/skins/default/images/icons/folder-sent.png differ
diff --git a/skins/default/images/icons/folder-trash.png b/skins/default/images/icons/folder-trash.png
new file mode 100644 (file)
index 0000000..0712aaa
Binary files /dev/null and b/skins/default/images/icons/folder-trash.png differ
diff --git a/skins/default/images/icons/forwarded.png b/skins/default/images/icons/forwarded.png
new file mode 100644 (file)
index 0000000..1ea246f
Binary files /dev/null and b/skins/default/images/icons/forwarded.png differ
diff --git a/skins/default/images/icons/plus.gif b/skins/default/images/icons/plus.gif
new file mode 100755 (executable)
index 0000000..854b5eb
Binary files /dev/null and b/skins/default/images/icons/plus.gif differ
diff --git a/skins/default/images/icons/remove-attachment.png b/skins/default/images/icons/remove-attachment.png
new file mode 100644 (file)
index 0000000..8d496fe
Binary files /dev/null and b/skins/default/images/icons/remove-attachment.png differ
diff --git a/skins/default/images/icons/replied.png b/skins/default/images/icons/replied.png
new file mode 100644 (file)
index 0000000..4a52132
Binary files /dev/null and b/skins/default/images/icons/replied.png differ
diff --git a/skins/default/images/icons/reset.gif b/skins/default/images/icons/reset.gif
new file mode 100644 (file)
index 0000000..a9a53d5
Binary files /dev/null and b/skins/default/images/icons/reset.gif differ
diff --git a/skins/default/images/icons/silhouette.png b/skins/default/images/icons/silhouette.png
new file mode 100644 (file)
index 0000000..c7d9748
Binary files /dev/null and b/skins/default/images/icons/silhouette.png differ
diff --git a/skins/default/images/icons/unread.png b/skins/default/images/icons/unread.png
new file mode 100644 (file)
index 0000000..31f6406
Binary files /dev/null and b/skins/default/images/icons/unread.png differ
diff --git a/skins/default/images/listheader_aqua.gif b/skins/default/images/listheader_aqua.gif
new file mode 100644 (file)
index 0000000..59f44ea
Binary files /dev/null and b/skins/default/images/listheader_aqua.gif differ
diff --git a/skins/default/images/listheader_dark.gif b/skins/default/images/listheader_dark.gif
new file mode 100644 (file)
index 0000000..cd35555
Binary files /dev/null and b/skins/default/images/listheader_dark.gif differ
diff --git a/skins/default/images/listheader_light.gif b/skins/default/images/listheader_light.gif
new file mode 100644 (file)
index 0000000..8d9e6ca
Binary files /dev/null and b/skins/default/images/listheader_light.gif differ
diff --git a/skins/default/images/mailbox_list.gif b/skins/default/images/mailbox_list.gif
new file mode 100644 (file)
index 0000000..d53de17
Binary files /dev/null and b/skins/default/images/mailbox_list.gif differ
diff --git a/skins/default/images/mailbox_selected.gif b/skins/default/images/mailbox_selected.gif
new file mode 100644 (file)
index 0000000..bbc2265
Binary files /dev/null and b/skins/default/images/mailbox_selected.gif differ
diff --git a/skins/default/images/rcube_watermark.png b/skins/default/images/rcube_watermark.png
new file mode 100644 (file)
index 0000000..a9e83e1
Binary files /dev/null and b/skins/default/images/rcube_watermark.png differ
diff --git a/skins/default/images/roundcube_logo.gif b/skins/default/images/roundcube_logo.gif
new file mode 100644 (file)
index 0000000..b77fd3d
Binary files /dev/null and b/skins/default/images/roundcube_logo.gif differ
diff --git a/skins/default/images/roundcube_logo.png b/skins/default/images/roundcube_logo.png
new file mode 100644 (file)
index 0000000..847d011
Binary files /dev/null and b/skins/default/images/roundcube_logo.png differ
diff --git a/skins/default/images/roundcube_logo_print.gif b/skins/default/images/roundcube_logo_print.gif
new file mode 100644 (file)
index 0000000..8fbf6a8
Binary files /dev/null and b/skins/default/images/roundcube_logo_print.gif differ
diff --git a/skins/default/images/searchfield.gif b/skins/default/images/searchfield.gif
new file mode 100644 (file)
index 0000000..b1dc938
Binary files /dev/null and b/skins/default/images/searchfield.gif differ
diff --git a/skins/default/images/sort_asc.gif b/skins/default/images/sort_asc.gif
new file mode 100644 (file)
index 0000000..244db10
Binary files /dev/null and b/skins/default/images/sort_asc.gif differ
diff --git a/skins/default/images/sort_desc.gif b/skins/default/images/sort_desc.gif
new file mode 100644 (file)
index 0000000..2427311
Binary files /dev/null and b/skins/default/images/sort_desc.gif differ
diff --git a/skins/default/images/tab_act.gif b/skins/default/images/tab_act.gif
new file mode 100644 (file)
index 0000000..9329db1
Binary files /dev/null and b/skins/default/images/tab_act.gif differ
diff --git a/skins/default/images/tab_pas.gif b/skins/default/images/tab_pas.gif
new file mode 100644 (file)
index 0000000..26adabf
Binary files /dev/null and b/skins/default/images/tab_pas.gif differ
diff --git a/skins/default/images/taskbar.gif b/skins/default/images/taskbar.gif
new file mode 100644 (file)
index 0000000..b6fc91c
Binary files /dev/null and b/skins/default/images/taskbar.gif differ
diff --git a/skins/default/includes/header.html b/skins/default/includes/header.html
new file mode 100644 (file)
index 0000000..a7e034a
--- /dev/null
@@ -0,0 +1,3 @@
+<div id="header"><roundcube:button command="mail" image="/images/roundcube_logo.png" alt="RoundCube Webmail" width="165" height="55" /></div>
+
+<roundcube:object name="message" id="message" />
diff --git a/skins/default/includes/ldapscripts.html b/skins/default/includes/ldapscripts.html
new file mode 100644 (file)
index 0000000..e58fd4d
--- /dev/null
@@ -0,0 +1,74 @@
+<script type="text/javascript">
+var ldap_server_select = document.getElementById('rcfmd_ldap_public_servers');
+
+if (ldap_server_select) {
+  // attach event to ldap server drop down
+  ldap_server_select.onchange = function() {
+    updateLdapSearchFields(this);
+    return false;
+  }
+  
+  // update the fields on page load
+  updateLdapSearchFields(ldap_server_select);
+}
+
+/**
+ * function to change the attributes of the ldap server search fields select box
+ * this function is triggered by an onchange event in the server select box 
+ */
+function updateLdapSearchFields(element) {
+
+  // get the search fields select element
+  var search_fields = document.getElementById('rcfmd_ldap_public_search_field');
+
+  // get rid of the current options for the select
+  for (i = search_fields.length - 1; i>=0; i--)
+    search_fields.remove(i);
+
+  // get the array containing this servers search fields
+  var server_fields = rcmail.env[element.value + '_search_fields'];
+
+  // add a new option for each of the possible search fields for the selected server
+  for (i = 0; i < server_fields.length; i++) {
+
+    // the last array value is for fuzzy search, so skip that one
+    if (i < (server_fields.length - 1)) {
+      var new_option = document.createElement('option');
+      new_option.text  = server_fields[i][0];
+      new_option.value = server_fields[i][1];
+
+      // standards compliant browsers
+      try {
+        search_fields.add(new_option, null);
+      }
+      // for the standards challenged one...
+      catch(e) {
+        search_fields.add(new_option);      
+      }
+    } else {
+      // ok, last member of array, so check the value of fuzzy_search
+      var fuzzy_search = server_fields[i];
+      var search_check_box = document.getElementById('rcmfd_ldap_public_search_type');
+
+      if (fuzzy_search == 'fuzzy') {
+        // we should enable the check box
+        if (search_check_box.disabled)
+          search_check_box.disabled = false;
+
+        // make sure the checkbox is unchecked
+        if (search_check_box.checked)
+          search_check_box.checked = false;
+
+      } else {
+        // we should disable the check box
+        if (!search_check_box.disabled)
+          search_check_box.disabled = true;
+
+        // check the checkbox (just a visual clue for the user)
+        if (!search_check_box.checked)
+          search_check_box.checked = true;
+      }
+    }
+  }
+}
+</script>
diff --git a/skins/default/includes/links.html b/skins/default/includes/links.html
new file mode 100644 (file)
index 0000000..30aeb4c
--- /dev/null
@@ -0,0 +1,3 @@
+<link rel="index" href="$__comm_path" />
+<link rel="shortcut icon" href="/images/favicon.ico"/>
+<link rel="stylesheet" type="text/css" href="/common.css" />
\ No newline at end of file
diff --git a/skins/default/includes/settingscripts.html b/skins/default/includes/settingscripts.html
new file mode 100644 (file)
index 0000000..9ee93cd
--- /dev/null
@@ -0,0 +1,14 @@
+<script type="text/javascript">
+
+if (window.rcmail && rcmail.env.action)
+  {
+  var action = rcmail.env.action=='preferences' ? 'default' : (rcmail.env.action.indexOf('identity')>0 ? 'identities' : rcmail.env.action);
+  var tab = document.getElementById('settingstab'+action);
+  }
+else 
+  var tab = document.getElementById('settingstabdefault');
+  
+if (tab)
+  tab.className = 'tablink-selected';
+
+</script>
\ No newline at end of file
diff --git a/skins/default/includes/settingstabs.html b/skins/default/includes/settingstabs.html
new file mode 100644 (file)
index 0000000..ef561d9
--- /dev/null
@@ -0,0 +1,3 @@
+<div id="tabsbar">
+<span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span><span id="settingstabfolders" class="tablink"><roundcube:button command="folders" type="link" label="folders" title="managefolders" class="tablink" /></span><span id="settingstabidentities" class="tablink"><roundcube:button command="identities" type="link" label="identities" title="manageidentities" class="tablink" /></span>
+</div>
diff --git a/skins/default/includes/taskbar.html b/skins/default/includes/taskbar.html
new file mode 100644 (file)
index 0000000..ef1aa82
--- /dev/null
@@ -0,0 +1,6 @@
+<div id="taskbar">
+<roundcube:button command="mail" label="mail" class="button-mail" />
+<roundcube:button command="addressbook" label="addressbook" class="button-addressbook" />
+<roundcube:button command="settings" label="settings" class="button-settings" />
+<roundcube:button command="logout" label="logout" class="button-logout" />
+</div>
\ No newline at end of file
diff --git a/skins/default/ldapsearchform.css b/skins/default/ldapsearchform.css
new file mode 100644 (file)
index 0000000..9661442
--- /dev/null
@@ -0,0 +1,54 @@
+/***** RoundCube|Mail address book task styles *****/
+
+
+body.iframe,
+{
+  background-color: #F9F9F9;
+}
+
+#ldapsearch-title
+{
+  height: 12px !important;
+/*  height: 20px; */
+  padding: 4px 20px 3px 20px;
+  border-bottom: 1px solid #999999;
+  color: #333333;
+  font-size: 11px;
+  font-weight: bold;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif); 
+}
+
+#ldapsearch-details
+{
+  padding: 15px 20px 10px 20px;
+}
+
+#ldapsearch-details table td.title
+{
+  color: #666666;
+  font-weight: bold;
+  text-align: right;
+  padding-right: 10px;
+}
+
+#ldapAddressList 
+{
+  width: 100%;
+  table-layout: fixed;
+  /* css hack for IE */
+  width: expression(document.getElementById('addresslist').clientWidth);
+}
+
+#ldapAddressList table 
+{
+  border-top: 1px solid #999999;
+}
+
+#ldap-search-results div
+{
+  width: 100%;
+  color: red;
+  background-color: green;
+}
+
diff --git a/skins/default/mail.css b/skins/default/mail.css
new file mode 100644 (file)
index 0000000..72a782b
--- /dev/null
@@ -0,0 +1,863 @@
+/***** RoundCube|Mail mail task styles *****/
+
+
+#messagetoolbar
+{
+  position: absolute;
+  top: 45px;
+  left: 200px;
+  right: 250px;
+  height: 35px;
+  white-space: nowrap;
+/*  border: 1px solid #cccccc; */
+}
+
+#messagetoolbar a
+{
+  padding-right: 10px;
+}
+
+#messagetoolbar select
+{
+  font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  color: #333333;
+}
+
+#messagetoolbar select.mboxlist
+{
+  position: absolute;
+  left: 375px;
+  top: 10px;
+}
+
+#messagetoolbar select.mboxlist option
+{
+  padding-left: 15px;
+}
+
+#messagetoolbar select.mboxlist option[value="0"]
+{
+  padding-left: 2px;
+}
+
+#listcontrols
+{
+  position: absolute;
+  left: 200px;
+  bottom: 20px;
+  height: 16px;
+  width: 500px;
+  font-size: 11px;
+}
+
+#listcontrols a,
+#listcontrols a:active,
+#listcontrols a:visited,
+#mailboxcontrols a,
+#mailboxcontrols a:active,
+#mailboxcontrols a:visited
+{
+  color: #999999;
+  font-size: 11px;
+  text-decoration: none;
+}
+
+#listcontrols a.active,
+#listcontrols a.active:active,
+#listcontrols a.active:visited,
+#mailboxcontrols a.active,
+#mailboxcontrols a.active:active,
+#mailboxcontrols a.active:visited
+{
+  color: #CC0000;
+}
+
+#listcontrols a.active:hover,
+#mailboxcontrols a.active:hover
+{
+  text-decoration: underline;
+}
+
+#messagecountbar
+{
+  position: absolute;
+  bottom: 16px;
+  right: 40px;
+  width: 300px;
+  height: 20px;
+  text-align: right;
+  white-space: nowrap;
+}
+
+#messagecountbar span
+{
+  font-size: 11px;
+  color: #333333;
+}
+
+#messagepartcontainer
+{
+  position: absolute;
+  top: 80px;
+  left: 20px;
+  right: 20px;
+  bottom: 20px;
+}
+
+#mailcontframe
+{
+  position: absolute;
+  top: 85px;
+  left: 200px;
+  right: 40px;
+  bottom: 40px;
+  border: 1px solid #999999;
+  background-color: #F9F9F9;
+  overflow: auto;
+  /* css hack for IE */
+  width: expression((parseInt(document.documentElement.clientWidth)-240)+'px');
+  height: expression((parseInt(document.documentElement.clientHeight)-125)+'px');
+}
+
+
+#messagepartframe
+{
+  border: 1px solid #999999;
+  background-color: #F9F9F9;  
+}
+
+
+#partheader
+{
+  position: absolute;
+  top: 10px;
+  left: 220px;
+  height: 40px;
+}
+
+#partheader table td
+{
+  padding-left: 2px;
+  padding-right: 4px;
+  vertical-align: middle;
+  font-size: 11px;
+}
+
+#partheader table td.title
+{
+  color: #666666;
+  font-weight: bold;
+}
+
+#rcmdraglayer
+{
+  width: 300px;
+  border: 1px solid #999999;
+  background-color: #F9F9F9;
+  padding-left: 8px;
+  padding-right: 8px;
+  padding-top: 3px;
+  padding-bottom: 3px;
+  font-size: 11px;
+  opacity: 0.6;
+  -moz-opacity: 0.6;
+}
+
+
+/** mailbox list styles */
+
+#mailboxlist-header
+{
+  position: absolute;
+  top: 85px;
+  left: 20px;
+  width: 140px !important;
+/*  width: 162px; */
+  height: 13px;
+  padding: 3px 10px 2px 10px;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif); 
+  border: 1px solid #CCCCCC;
+  color: #333333;
+  font-size: 11px;
+  font-weight: bold;  
+}
+
+#mailboxlist-container
+{
+  position: absolute;
+  top: 105px;
+  left: 20px;
+  width: 160px;
+  bottom: 40px;
+  border: 1px solid #CCCCCC;
+  background-color: #F9F9F9;
+  overflow: auto;
+  /* css hack for IE */
+  height: expression((parseInt(document.documentElement.clientHeight)-145)+'px');
+}
+
+#mailboxlist
+{
+  width: 100%;
+  height: auto;
+  margin: 0px;
+  padding: 0px;
+  list-style-image: none;
+  list-style-type: none;
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+#mailboxlist li
+{
+ /* height: 18px; */
+  font-size: 11px;
+  background: url(images/icons/folder-closed.png) no-repeat;
+  background-position: 10px 1px;
+  border-bottom: 1px solid #EBEBEB;
+/* IE 5.5  margin-left: -16px; */
+}
+
+#mailboxlist li.inbox
+{
+  background-image: url(images/icons/folder-inbox.png);
+}
+
+#mailboxlist li.drafts
+{
+  background-image: url(images/icons/folder-drafts.png);
+}
+
+#mailboxlist li.sent
+{
+  background-image: url(images/icons/folder-sent.png);
+}
+
+#mailboxlist li.junk
+{
+  background-image: url(images/icons/folder-junk.png);
+}
+
+#mailboxlist li.trash
+{
+  background-image: url(images/icons/folder-trash.png);
+}
+
+#mailboxlist li a
+{
+  display: block;
+  padding-left: 32px;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  text-decoration: none;
+}
+
+#mailboxlist li, #mailboxlist li.unread
+{  
+ /* background-image: url(images/mailbox_list.gif); */
+}
+
+#mailboxlist li.unread
+{
+  font-weight: bold;
+}
+
+#mailboxlist li.selected
+{
+  background-color: #929292;
+  border-bottom: 1px solid #898989;
+}
+
+#mailboxlist li.selected a
+{
+  color: #FFF;
+  font-weight: bold;
+}
+
+#mailboxlist li.droptarget
+{
+  background-color: #FFFFA6;
+}
+
+/* styles for nested folders */
+#mailboxlist ul {
+  list-style: none;
+  padding: 0;
+  margin:0;
+  border-top: 1px solid #EBEBEB;  
+}
+
+#mailboxlist ul li {
+  padding-left: 15px;
+  background-position: 25px 1px;
+}
+
+#mailboxlist li.selected li {
+  background-color: #F9F9F9;
+}
+
+#mailboxlist li.unread li {
+  font-weight: normal;
+}
+
+#mailboxlist li.unread li.unread {
+  font-weight: bold;
+}
+
+#mailboxlist li.selected li a{
+  color: black;
+  font-weight: normal;
+}
+
+
+#mailboxcontrols
+{
+  position: absolute;
+  left: 20px;
+  width: 160px;
+  bottom: 20px;
+  height: 16px;
+  overflow: hidden;
+  font-size: 11px;
+}
+
+
+/** message list styles */
+
+body.messagelist
+{
+  margin: 0px;
+  background-color: #F9F9F9;
+}
+
+#messagelist
+{
+  width: 100%;
+  display: table;
+  table-layout: fixed;
+  /* css hack for IE */
+  width: expression(document.getElementById('mailcontframe').clientWidth);
+}
+
+#messagelist thead tr td
+{
+  height: 20px;
+  padding-top: 0px;
+  padding-bottom: 0px;
+  padding-left: 2px;
+  padding-right: 4px;
+  vertical-align: middle;
+  border-bottom: 1px solid #999999;
+  color: #333333;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif); 
+  font-size: 11px;
+  font-weight: bold;
+}
+
+#messagelist thead tr td.sortedASC,
+#messagelist thead tr td.sortedDESC
+{
+  background-image: url(images/listheader_dark.gif); 
+}
+
+#messagelist thead tr td.sortedASC a
+{
+  background: url(images/sort_asc.gif) top right no-repeat;
+}
+
+#messagelist thead tr td.sortedDESC a
+{
+  background: url(images/sort_desc.gif) top right no-repeat;
+}
+
+#messagelist thead tr td a,
+#messagelist thead tr td a:hover
+{
+  display: block;
+  width: auto !important;
+  width: 100%;
+  color: #333333;
+  text-decoration: none;
+}
+
+#messagelist tbody tr td
+{
+  height: 16px !important;
+  height: 20px;
+  padding: 2px;
+  padding-right: 4px;
+  font-size: 11px;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  border-bottom: 1px solid #EBEBEB;
+  cursor: pointer;
+}
+
+#messagelist col
+{
+  display: table-column;
+  text-align: left;
+  vertical-align: middle;
+}
+
+#messagelist tr td.icon
+{
+  width: 16px;
+  vertical-align: middle;
+}
+
+#messagelist tr td.subject
+{
+  overflow: hidden;
+  vertical-align: middle;
+}
+
+#messagelist tr td.size
+{
+  width: 60px;
+  text-align: right;
+  vertical-align: middle;
+}
+
+#messagelist tr td.from,
+#messagelist tr td.to
+{
+  width: 180px;
+  vertical-align: middle;
+}
+
+#messagelist tr td.date
+{
+  width: 110px;
+  vertical-align: middle;
+}
+
+#messagelist tr.message
+{
+  background-color: #FFFFFF;
+}
+
+/*
+#messagelist tr.odd
+{
+  background-color: #F9F9F9;
+}
+*/
+
+#messagelist tr.unread
+{
+  font-weight: bold;
+  background-color: #FFFFFF;
+}
+
+#messagelist tr.selected td
+{
+  font-weight: bold;
+  color: #FFFFFF;
+  background-color: #CC3333;
+}
+
+#messagelist tr.focused td
+{
+  border-bottom: thin dotted;
+  border-top: thin dotted;
+}
+
+#messagelist tr.unfocused td
+{
+  font-weight: bold;
+  color: #FFFFFF;
+  background-color: #929292;
+}
+
+#messagelist tr.selected td a.rcmContactAddress
+{
+  color: #FFFFFF;
+}
+
+#messagelist tr.unfocused td a.rcmContactAddress
+{
+  color: #FFFFFF;
+}
+
+#messagelist tr.deleted td a.rcmContactAddress
+{
+  color: #CCCCCC;
+}
+
+#messagelist tr.deleted td,
+#messagelist tr.deleted td a
+{
+  color: #CCCCCC;
+}
+
+#quicksearchbar
+{
+  position: absolute;
+  top: 60px;
+  right: 40px;
+  width: 182px;
+  height: 20px;
+  text-align: right;
+  background: url('images/searchfield.gif') top left no-repeat;
+}
+
+#quicksearchbar a
+{
+  position: absolute;
+  top: 3px;
+  right: 4px;
+  text-decoration: none;
+}
+
+#quicksearchbar img
+{
+  vertical-align: middle;
+}
+
+#quicksearchbox
+{
+  position: absolute;
+  top: 2px;
+  left: 20px;
+  width: 140px;
+  font-size: 11px;
+  padding: 0px;
+  border: none;
+}
+
+
+/*\*/
+html>body*#quicksearchbar { background-image: none; }
+html>body*#quicksearchbar a { top: 5px; }
+html>body*#quicksearchbar #quicksearchbox { width: 180px; top:0px; right: 1px; left: auto; }
+/**/
+
+
+#rcversion
+{
+  position: absolute;
+  top: 67px;
+  left: 20px;
+  width: 160px;
+  text-align: center;
+
+  font-weight: normal;
+  font-size: x-small;
+  font-variant: small-caps;
+  
+  color: #999999;
+  /*border: 1px solid #308014;
+  background-color: #b4eeb4;*/
+}
+
+/** message view styles */
+
+
+#messageframe
+{
+  position: absolute;
+  top: 85px;
+  left: 200px;
+  right: 40px;
+  bottom: 40px;
+  border: 1px solid #cccccc;
+  background-color: #FFFFFF;
+  overflow: auto;
+  /* css hack for IE */
+  /* margin-bottom: 10px; */
+  width: expression((parseInt(document.documentElement.clientWidth)-240)+'px');
+  height: expression((parseInt(document.documentElement.clientHeight)-125)+'px');
+}
+
+table.headers-table
+{
+  width: 100%;
+  background-color: #EBEBEB;
+  table-layout: fixed;
+
+}
+
+table.headers-table tr td
+{
+  font-size: 11px;
+  border-bottom:1px solid #FFFFFF;
+}
+
+table.headers-table td.header-title
+{
+  width: 80px;
+  color: #666666;
+  font-weight: bold;
+  text-align: right;
+  white-space: nowrap;
+  padding-right: 4px;
+}
+
+table.headers-table tr td.subject
+{
+  width: 95%;
+  font-weight: bold;
+}
+
+#attachment-list
+{
+  margin: 0px;
+  padding: 0px 0px 0px 68px;
+  height: 18px;
+  list-style-image: none;
+  list-style-type: none;
+  background: url(images/icons/attachment.png) 52px 1px no-repeat #DFDFDF; 
+  border-bottom: 1px solid #FFFFFF;
+}
+
+#attachment-list li
+{
+/*  display: block; */
+  float: left;
+  height: 18px;
+  font-size: 11px;
+  padding: 2px 10px 0px 10px;
+}
+
+#attachment-list li a
+{
+  text-decoration: none;
+}
+
+#attachment-list li a:hover
+{
+  text-decoration: underline;
+}
+
+#messagebody
+{
+  min-height: 300px;
+  padding-top: 10px;
+  padding-bottom: 10px;
+  background-color: #FFFFFF;
+}
+
+div.message-part
+{
+  padding: 8px;
+  padding-top: 10px;
+  overflow: hidden;
+}
+
+div.message-part a
+{
+  color: #0000CC;
+}
+
+div.message-part pre
+{
+  margin: 0px;
+  padding: 0px;
+}
+
+
+#remote-objects-message
+{
+  display: none;
+  height: 20px;
+  min-height: 20px;
+  margin: 8px 8px 0px 8px;
+  padding: 10px 10px 6px 46px;  
+}
+
+#remote-objects-message a
+{
+  color: #666666;
+  padding-left: 10px;
+}
+
+#remote-objects-message a:hover
+{
+  color: #333333;
+}
+
+
+/** message compose styles */
+
+#priority-selector
+{
+  position: absolute;
+  left: 280px;
+  top: 10px;
+}
+
+#receipt-selector
+{
+  position: absolute;
+  left: 450px;
+  top: 10px;
+}
+
+#compose-container
+{
+  position: absolute;
+  top: 90px;
+  left: 200px;
+  right: 40px;
+  bottom: 40px;
+  padding: 0px;
+  margin: 0px;
+  /* css hack for IE */
+  width: expression(document.documentElement.clientWidth-240);
+  height: expression((parseInt(document.documentElement.clientHeight)-130)+'px');
+}
+
+/*
+#compose-headers
+{
+  position: absolute;
+  top: 70px;
+  left: 200px;
+  height: 84px;
+  border-top: 1px solid #cccccc;
+  overflow: auto;
+}
+
+#compose-headers td
+{
+  padding-top: 1px;
+  padding-bottom: 1px;
+  border-right: 1px solid #cccccc;
+  border-bottom: 1px solid #cccccc;
+}
+*/
+
+#compose-headers
+{
+  width: 100%;
+}
+
+/*
+#compose-headers td
+{
+  width: 100%;
+}
+*/
+
+#compose-headers td.top
+{
+  vertical-align: top;
+}
+
+#compose-headers td.title,
+#compose-subject td.title
+{
+  width: 80px !important;
+  color: #666666;
+  font-size: 11px;
+  font-weight: bold;
+  padding-right: 10px;
+  white-space: nowrap;
+}
+
+#compose-headers td.add-button
+{
+  width: 40px !important;
+  text-align: right;
+  vertical-align: bottom;
+}
+
+#compose-headers td.add-button a
+{
+  color: #666666;
+  font-size: 11px;
+  text-decoration: none;
+}
+
+#compose-headers td textarea
+{
+  width: 100%;
+  height: 40px;
+}
+
+#compose-headers td input
+{
+  width: 100%;
+}
+
+#compose-cc,
+#compose-bcc,
+#compose-replyto
+{
+  display: none;
+}
+
+#compose-body
+{
+  margin-top: 5px;
+  margin-bottom: 10px;
+  width: 99%;
+  height: 90%;
+  min-height: 280px;
+  font-size: 9pt;
+  font-family: "Courier New", Courier, monospace;
+}
+
+#compose-attachments
+{
+  position: absolute;
+  top: 100px;
+  left: 20px;
+  width: 160px;
+}
+
+#compose-attachments ul
+{
+  margin: 0px;
+  padding: 0px;
+  border: 1px solid #CCCCCC;
+  background-color: #F9F9F9;
+  list-style-image: none;
+  list-style-type: none;
+}
+
+#compose-attachments ul li
+{
+  height: 18px;
+  font-size: 11px;
+  padding-left: 2px;
+  padding-top: 2px;
+  padding-right: 4px;
+  border-bottom: 1px solid #EBEBEB;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+#attachment-title
+{
+  background: url(images/icons/attachment.png) top left no-repeat;
+  padding: 0px 0px 3px 22px;
+}
+
+#attachment-form
+{
+  position: absolute;
+  top: 150px;
+  left: 20px;
+  z-index: 200;
+  padding: 8px;
+  visibility: hidden;
+  border: 1px solid #CCCCCC;
+  background-color: #F9F9F9;
+}
+
+#attachment-form input.button
+{
+  margin-top: 8px;
+}
+
+#rcmquotadisplay
+{
+  color: #999999;
+  font-size: 11px;
+}
diff --git a/skins/default/pngbehavior.htc b/skins/default/pngbehavior.htc
new file mode 100644 (file)
index 0000000..24bfd4c
--- /dev/null
@@ -0,0 +1,52 @@
+<public:component>
+<public:attach event="onpropertychange" onevent="propertyChanged()" />
+<script>
+
+var supported = /MSIE (5\.5)|[6789]/.test(navigator.userAgent) && navigator.platform == "Win32";
+var realSrc;
+var blankSrc = "skins/default/images/blank.gif";
+
+if (supported) fixImage();
+
+function propertyChanged() {
+   if (!supported) return;
+
+   var pName = event.propertyName;
+   if (pName != "src") return;
+   // if not set to blank
+   if ( ! new RegExp(blankSrc).test(src))
+      fixImage();
+};
+
+function fixImage() {
+   // get src
+   var src = element.src;
+
+   // check for real change
+   if (src == realSrc) {
+      element.src = blankSrc;
+      return;
+   }
+
+   if ( ! new RegExp(blankSrc).test(src)) {
+      // backup old src
+      realSrc = src;
+      element._original_src = realSrc;
+   }
+
+   // test for png
+   if ( /\.png$/.test( realSrc.toLowerCase() ) ) {
+      // set blank image
+      element.src = blankSrc;
+      // set filter
+      element.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
+                                     src + "',sizingMethod='crop')";
+   }
+   else {
+      // remove filter
+      element.runtimeStyle.filter = "";
+   }
+}
+
+</script>
+</public:component>
\ No newline at end of file
diff --git a/skins/default/print.css b/skins/default/print.css
new file mode 100644 (file)
index 0000000..ac184db
--- /dev/null
@@ -0,0 +1,111 @@
+/***** RoundCube|Mail message print styles *****/
+
+body
+{
+  background-color: #ffffff;
+  color: #000000;
+  margin: 2mm;
+}
+
+body, td, th, span, div, p, h3
+{
+  font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+  font-size: 9pt;
+  color: #000000;
+}
+
+h3
+{
+  font-size: 18px;
+  color: #000000;
+}
+
+a, a:active, a:visited
+{
+  color: #000000;
+}
+
+#header
+{
+  margin-left: 5mm;
+  margin-bottom: 3mm;
+}
+
+#messageframe
+{
+  position: relative;
+}
+
+table.headers-table
+{
+  table-layout: fixed;
+}
+
+table.headers-table tr td
+{
+  font-size: 9pt;
+}
+
+table.headers-table td.header-title
+{
+  color: #666666;
+  font-weight: bold;
+  text-align: right;
+  vertical-align: top;
+  padding-right: 4mm;
+  white-space: nowrap;
+}
+
+table.headers-table tr td.subject
+{
+  width: 90%;
+  font-weight: bold;
+}
+  
+#attachment-list
+{
+  margin-top: 3mm;
+  padding-top: 3mm;
+  border-top: 1pt solid #cccccc;
+}
+
+#attachment-list li
+{
+  font-size: 9pt;
+}
+
+#attachment-list li a
+{
+  text-decoration: none;
+}
+
+#attachment-list li a:hover
+{
+  text-decoration: underline;
+}
+
+#messagebody
+{
+  margin-top: 5mm;
+  border-top: none;
+}
+
+div.message-part
+{
+  padding: 2mm;
+  margin-top: 5mm;
+  margin-bottom: 5mm;
+  border-top: 1pt solid #cccccc;
+}
+
+div.message-part a
+{
+  color: #0000CC;
+}
+
+div.message-part pre
+{
+  margin: 0;
+  padding: 0;
+  font-size: 9pt;
+}
diff --git a/skins/default/settings.css b/skins/default/settings.css
new file mode 100644 (file)
index 0000000..c172576
--- /dev/null
@@ -0,0 +1,174 @@
+/***** RoundCube|Mail settings task styles *****/
+
+
+#tabsbar
+{
+  position: absolute;
+  top: 50px;
+  left: 220px;
+  right: 60px;
+  height: 22px;
+  border-bottom: 1px solid #999999;
+  white-space: nowrap;
+  /* css hack for IE */
+  width: expression((parseInt(document.documentElement.clientWidth)-280)+'px');
+}
+
+span.tablink,
+span.tablink-selected
+{
+  float: left;
+  width: 100px;
+  height: 24px !important;
+  height: 22px;
+  background: url('images/tab_pas.gif') top left no-repeat;
+}
+
+span.tablink-selected
+{
+  background: url('images/tab_act.gif') top left no-repeat;
+}
+
+span.tablink a,
+span.tablink-selected a
+{
+  display: block;
+  padding-left: 10px;
+  padding-top: 5px;
+  color: #555555;
+  text-decoration: none;
+}
+
+span.tablink-selected a
+{
+  color: #000000;
+}
+
+#userprefs-box
+{
+  position: absolute;
+  top: 95px;
+  left: 20px;
+  width: 550px;
+  border: 1px solid #999999;  
+}
+
+#userprefs-box table td.title
+{
+  color: #666666;
+  padding-right: 10px;
+}
+
+#identities-list,
+#folder-manager
+{
+  position: absolute;
+  top: 95px;
+  left: 20px;
+}
+
+#folder-manager
+{
+  width: 500px;
+  bottom: 120px;
+  overflow: auto;
+  border: 1px solid #999999;
+  height: expression((parseInt(document.documentElement.clientHeight)-215)+'px');
+}
+
+#identities-table
+{
+  width: 500px;
+  border: 1px solid #999999;
+  background-color: #F9F9F9;
+}
+
+#identities-table tbody td
+{
+  cursor: pointer;
+}
+
+#identity-frame
+{
+  position: relative;
+  margin-top: 20px;
+  border: 1px solid #999999;
+}
+
+#identity-details
+{
+  margin-top: 30px;
+  width: 500px;
+  border: 1px solid #999999;
+}
+
+#identity-details table td.title
+{
+  color: #666666;
+  font-weight: bold;
+  text-align: right;
+  padding-right: 10px;
+}
+
+#bottomboxes
+{
+  position: absolute;
+  width: 500px;
+  height: 100px;
+  left: 20px;
+  bottom: 20px;
+}
+
+#userprefs-title,
+#identity-title,
+div.boxtitle,
+#subscription-table thead td
+{
+  height: 12px !important;
+  padding: 4px 20px 3px 6px;
+  border-bottom: 1px solid #999999;
+  color: #333333;
+  font-size: 11px;
+  font-weight: bold;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif); 
+}
+
+div.settingsbox
+{
+  width: 500px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  border: 1px solid #999999;
+}
+
+div.settingspart
+{
+  display: block;
+  padding: 10px;
+}
+
+#subscription-table
+{
+  width: 100%;
+  table-layout: fixed;
+}
+
+#subscription-table tbody td
+{
+  padding-left: 6px;
+  padding-right: 20px;
+  white-space: nowrap;
+  border-bottom: 1px solid #EBEBEB;
+  background-color: #F9F9F9;
+}
+
+#subscription-table td.name
+{
+  width: 280px;
+}
+
+#subscription-table td.subscribed
+{
+  width: 80px;
+}
diff --git a/skins/default/templates/addcontact.html b/skins/default/templates/addcontact.html
new file mode 100644 (file)
index 0000000..a7f4379
--- /dev/null
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/addresses.css" />
+</head>
+<body class="iframe">
+
+<div id="contact-title"><roundcube:label name="addcontact" /></div>
+
+<div id="contact-details">
+<roundcube:object name="contacteditform" size="40" />
+
+<p><br />
+<input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp;
+<roundcube:button command="save" type="input" class="button" label="save" />
+</p>
+
+</div>
+
+
+</body>
+</html>
diff --git a/skins/default/templates/addidentity.html b/skins/default/templates/addidentity.html
new file mode 100644 (file)
index 0000000..d38d806
--- /dev/null
@@ -0,0 +1,36 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/settings.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:include file="/includes/settingstabs.html" />
+
+
+<div id="identities-list">
+<roundcube:object name="itentitiesList" id="identities-table" class="records-table" cellspacing="0" summary="Identities list" editIcon="" />
+
+<p><roundcube:button command="add" type="input" label="newidentity" class="button" /></p>
+
+<div id="identity-details">
+<div id="identity-title"><roundcube:label name="newidentity" /></div>
+
+<div style="padding:15px;">
+<roundcube:object name="identityform" size="40" />
+
+<p><br />
+<roundcube:button command="save" type="input" class="button" label="save" />
+</p>
+</div>
+</div>
+</div>
+
+<roundcube:include file="/includes/settingscripts.html" />
+
+</body>
+</html>
diff --git a/skins/default/templates/addressbook.html b/skins/default/templates/addressbook.html
new file mode 100644 (file)
index 0000000..dcf9656
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/addresses.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+
+<div id="abooktoolbar">
+<roundcube:button command="add" imageSel="/images/buttons/add_contact_sel.png" imageAct="/images/buttons/add_contact_act.png" imagePas="/images/buttons/add_contact_pas.png" width="32" height="32" title="newcontact" />
+<roundcube:button command="delete" imageSel="/images/buttons/delete_sel.png" imageAct="/images/buttons/delete_act.png" imagePas="/images/buttons/delete_pas.png" width="32" height="32" title="deletecontact" />
+<roundcube:button command="compose" imageSel="/images/buttons/compose_sel.png" imageAct="/images/buttons/compose_act.png" imagePas="/images/buttons/compose_pas.png" width="32" height="32" title="composeto" />
+<roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="print" />
+<roundcube:button command="export" imageSel="/images/buttons/download_sel.png" imageAct="/images/buttons/download_act.png" imagePas="/images/buttons/download_pas.png" width="32" height="32" title="export" />
+<roundcube:button command="ldappublicsearch" imageSel="/images/buttons/contacts_sel.png" imageAct="/images/buttons/contacts_act.png" imagePas="/images/buttons/contacts_pas.png" width="32" height="32" title="ldapsearch" />
+</div>
+
+<div id="abookcountbar">
+<roundcube:button command="previouspage" imageSel="/images/buttons/previous_sel.png" imageAct="/images/buttons/previous_act.png" imagePas="/images/buttons/previous_pas.png" width="11" height="11" title="previouspage" />
+&nbsp;<roundcube:object name="recordsCountDisplay" />&nbsp;
+<roundcube:button command="nextpage" imageSel="/images/buttons/next_sel.png" imageAct="/images/buttons/next_act.png" imagePas="/images/buttons/next_pas.png" width="11" height="11" title="nextpage" />
+</div>
+
+<div id="addresslist">
+<roundcube:object name="addresslist" id="contacts-table" class="records-table" cellspacing="0" summary="Contacts list" />
+</div>
+
+<div id="contacts-box">
+<roundcube:object name="addressframe" id="contact-frame" width="100%" height="100%" frameborder="0" src="/watermark.html" />
+</div>
+
+</body>
+</html>
diff --git a/skins/default/templates/compose.html b/skins/default/templates/compose.html
new file mode 100644 (file)
index 0000000..ed44bb5
--- /dev/null
@@ -0,0 +1,142 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="productname" /> :: <roundcube:label name="compose" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/mail.css" />
+<link rel="stylesheet" type="text/css" href="/googiespell.css" />
+<script type="text/javascript">
+<!--
+
+function rcmail_toggle_display(id)
+  {
+  var row, disp;
+  if (row = document.getElementById(id))
+    {
+    disp = (!row.style.display || row.style.display=='none') ? ((document.all && !window.opera) ? 'block' : 'table-row') : 'none';
+    row.style.display = disp;
+    }
+    
+  return false;
+  }
+
+//-->
+</script>
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+
+<form name="form" action="./" method="post">
+
+<div id="messagetoolbar">
+<!--<roundcube:button command="list" image="/images/buttons/back_act.png" imageSel="/images/buttons/back_sel.png" imageAct="/images/buttons/back_act.png" width="32" height="32" title="backtolist" />-->
+<roundcube:button command="send" imageSel="/images/buttons/send_sel.png" imageAct="/images/buttons/send_act.png" imagePas="/images/buttons/send_pas.png" width="32" height="32" title="sendmessage" />
+<roundcube:button command="spellcheck" imageSel="/images/buttons/spellcheck_sel.png" imageAct="/images/buttons/spellcheck_act.png" imagePas="/images/buttons/spellcheck_pas.png" width="32" height="32" title="checkspelling" />
+<roundcube:button command="add-attachment" imageSel="/images/buttons/attach_sel.png" imageAct="/images/buttons/attach_act.png" imagePas="/images/buttons/attach_pas.png" width="32" height="32" title="addattachment" />
+<roundcube:button command="savedraft" imageSel="/images/buttons/drafts_sel.png" imageAct="/images/buttons/drafts_act.png" imagePas="/images/buttons/drafts_pas.png" width="32" height="32" title="savemessage" />
+
+<div id="priority-selector">
+<label for="rcmcomposepriority"><roundcube:label name="priority" />:</label>&nbsp;<roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" />
+</div>
+
+<div id="receipt-selector">
+<roundcube:object name="receiptCheckBox" form="form" id="rcmcomposereceipt" />&nbsp;<label for="rcmcomposereceipt"><roundcube:label name="returnreceipt" /></label>
+</div>
+
+</div>
+
+<div id="compose-container">
+<table border="0" cellspacing="0" cellpadding="1" style="width:100%; height:99%;" summary="">
+<tbody>
+<tr>
+<td>
+
+<table border="0" cellspacing="0" cellpadding="1" id="compose-headers" summary="">
+<tbody><tr>
+
+<td class="title"><label for="rcmcomposefrom"><roundcube:label name="from" /></label></td>
+<td><roundcube:object name="composeHeaders" part="from" form="form" id="rcmcomposefrom" tabindex="1" /></td>
+
+</tr><tr>
+
+<td class="title top"><label for="rcmcomposeto"><roundcube:label name="to" /></label></td>
+<td><roundcube:object name="composeHeaders" part="to" form="form" id="rcmcomposeto" cols="80" rows="2" tabindex="2" /></td>
+<td class="add-button"><a href="#" onclick="return rcmail_toggle_display('compose-cc')">[Cc]</a><br />
+<a href="#" onclick="return rcmail_toggle_display('compose-bcc')">[Bcc]</a><br /></td>
+
+</tr><tr id="compose-cc">
+
+<td class="title top"><label for="rcmcomposecc"><roundcube:label name="cc" /></label></td>
+<td><roundcube:object name="composeHeaders" part="cc" form="form" id="rcmcomposecc" cols="80" rows="2" tabindex="3" /></td>
+
+</tr><tr id="compose-bcc">
+
+<td class="title top"><label for="rcmcomposebcc"><roundcube:label name="bcc" /></label></td>
+<td><roundcube:object name="composeHeaders" part="bcc" form="form" id="rcmcomposebcc" cols="80" rows="2" tabindex="4" /></td>
+
+</tr><tr id="compose-replyto">
+
+<td class="title top"><label for="rcmcomposereplyto"><roundcube:label name="replyto" /></label></td>
+<td><roundcube:object name="composeHeaders" part="replyto" form="form" id="rcmcomposereplyto" size="80" tabindex="5" /></td>
+
+</tr><tr>
+
+<td class="title"><label for="compose-subject"><roundcube:label name="subject" /></label></td>
+<td><roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="6" /></td>
+
+</tr>
+</tbody>
+</table>
+
+</td>
+
+</tr><tr>
+
+<td style="width:100%; height:98%; vertical-align:top;">
+<roundcube:object name="composeBody" id="compose-body" form="form" cols="80" rows="20" wrap="virtual" tabindex="7" />
+
+<table border="0" cellspacing="0" width="100%" summary=""><tbody><tr>
+
+<td>
+<roundcube:button type="input" command="send" class="button" label="sendmessage" />
+<roundcube:button type="input" command="list" class="button" label="cancel" />
+</td>
+<td align="right">
+<roundcube:label name="charset" />:&nbsp;<roundcube:object name="charsetSelector" tabindex="8" />
+</td>
+
+</tr></tbody></table>
+
+</td>
+
+</tr>
+</tbody>
+</table>
+
+</div>
+
+<div id="compose-attachments">
+<div id="attachment-title"><roundcube:label name="attachments" /></div>
+<roundcube:object name="composeAttachmentList" deleteIcon="/images/icons/remove-attachment.png"/ >
+<p><roundcube:button command="add-attachment" imagePas="/images/buttons/add_pas.png" imageSel="/images/buttons/add_sel.png" imageAct="/images/buttons/add_act.png" width="23" height="18" title="addattachment" />
+</div>
+
+</form>
+
+<roundcube:object name="composeAttachmentForm" id="attachment-form" />
+
+<script type="text/javascript">
+<!--
+
+var cc_field = document.form._cc;
+if (cc_field && cc_field.value!='')
+  rcmail_toggle_display('compose-cc');
+
+//-->
+</script>
+
+
+</body>
+</html>
diff --git a/skins/default/templates/editcontact.html b/skins/default/templates/editcontact.html
new file mode 100644 (file)
index 0000000..34f14e1
--- /dev/null
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/addresses.css" />
+</head>
+<body class="iframe">
+
+<div id="contact-title"><roundcube:label name="editcontact" /></div>
+
+<div id="contact-details">
+<roundcube:object name="contacteditform" size="40" />
+
+<p><br />
+<roundcube:button command="show" type="input" class="button" label="cancel" />&nbsp;
+<roundcube:button command="save" type="input" class="button" label="save" />
+</p>
+
+</form>
+</div>
+
+
+</body>
+</html>
diff --git a/skins/default/templates/editidentity.html b/skins/default/templates/editidentity.html
new file mode 100644 (file)
index 0000000..4fb087d
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/settings.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:include file="/includes/settingstabs.html" />
+
+
+<div id="identities-list">
+<roundcube:object name="itentitiesList" id="identities-table" class="records-table" cellspacing="0" summary="Identities list" editIcon="" />
+
+<p><roundcube:button command="add" type="input" label="newidentity" class="button" /></p>
+
+<div id="identity-details">
+<div id="identity-title"><roundcube:label name="edititem" /></div>
+
+<div style="padding:15px;">
+<roundcube:object name="identityform" size="40" />
+
+<p><br />
+<roundcube:button command="delete" type="input" class="button" label="delete" />&nbsp;
+<roundcube:button command="save" type="input" class="button" label="save" />
+</p>
+</div>
+</div>
+</div>
+
+<roundcube:include file="/includes/settingscripts.html" />
+
+</body>
+</html>
diff --git a/skins/default/templates/error.html b/skins/default/templates/error.html
new file mode 100644 (file)
index 0000000..273013b
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>RoundCube|Mail :: ERROR</title>
+<roundcube:include file="/includes/links.html" />
+</head>
+<body>
+
+<div id="header"><img src="/images/roundcube_logo.png" width="165" height="55" alt="RoundCube Webmail" /></div>
+
+<div style="width:400px; margin:60px auto;">
+$__page_content
+</div>
+
+</body>
+</html>
diff --git a/skins/default/templates/identities.html b/skins/default/templates/identities.html
new file mode 100644 (file)
index 0000000..7e311cc
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/settings.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:include file="/includes/settingstabs.html" />
+
+<div id="identities-list">
+<roundcube:object name="itentitiesList" id="identities-table" class="records-table" cellspacing="0" summary="Identities list" editIcon="" />
+
+<p><roundcube:button command="add" type="input" label="newidentity" class="button" /></p>
+</div>
+
+<roundcube:include file="/includes/settingscripts.html" />
+
+</body>
+</html>
diff --git a/skins/default/templates/ldappublicsearch.html b/skins/default/templates/ldappublicsearch.html
new file mode 100644 (file)
index 0000000..70570c0
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<link rel="stylesheet" type="text/css" href="/common.css" />
+<link rel="stylesheet" type="text/css" href="/ldapsearchform.css" />
+</head>
+<body class="iframe">
+
+<div id="ldapsearch-title"><roundcube:label name="ldappublicsearchform" /></div>
+
+<div id="ldapsearch-details">
+<roundcube:object name="ldappublicsearch" size="40" />
+<p>
+<roundcube:button command="ldappublicsearch" type="input" class="button" label="ldappublicsearch" />
+<input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp;
+<br /></p>
+</div>
+
+
+<div id="ldapsearch-results">
+<roundcube:object name="ldappublicaddresslist"
+  id="ldappublicaddresslist"
+  cellspacing="0"
+  summary="Ldap email address list" />
+</div>
+
+<roundcube:include file="/includes/ldapscripts.html" />
+
+</body>
+</html>
diff --git a/skins/default/templates/login.html b/skins/default/templates/login.html
new file mode 100644 (file)
index 0000000..66ec6f2
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<style type="text/css">
+
+#login-form {
+  margin-left: auto;
+  margin-right: auto;
+  margin-top: 50px;
+  width: 350px;
+}
+
+</style>
+</head>
+<body>
+
+<img src="skins/default/images/roundcube_logo.png" id="rcmbtn104" width="165" height="55" border="0" alt="RoundCube Webmail" hspace="10" />
+
+<roundcube:object name="message" id="message" />
+
+<div id="login-form">
+<form name="form" action="./" method="post">
+<roundcube:object name="loginform" form="form" />
+
+<p style="text-align: center;"><input type="submit" class="button" value="<roundcube:label name="login" />" />
+
+</form>
+</div>
+
+</body>
+</html>
diff --git a/skins/default/templates/mail.html b/skins/default/templates/mail.html
new file mode 100644 (file)
index 0000000..c2e5316
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/mail.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+
+<div id="messagetoolbar">
+<roundcube:button command="checkmail" imageSel="/images/buttons/inbox_sel.png" imageAct="/images/buttons/inbox_act.png" imagePas="/images/buttons/inbox_pas.png" width="32" height="32" title="checkmail" />
+<roundcube:button command="compose" imageSel="/images/buttons/compose_sel.png" imageAct="/images/buttons/compose_act.png" imagePas="/images/buttons/compose_pas.png" width="32" height="32" title="writenewmessage" />
+<roundcube:button command="reply" imageSel="/images/buttons/reply_sel.png" imageAct="/images/buttons/reply_act.png" imagePas="/images/buttons/reply_pas.png" width="32" height="32" title="replytomessage" />
+<roundcube:button command="reply-all" imageSel="/images/buttons/replyall_sel.png" imageAct="/images/buttons/replyall_act.png" imagePas="/images/buttons/replyall_pas.png" width="32" height="32" title="replytoallmessage" />
+<roundcube:button command="forward" imageSel="/images/buttons/forward_sel.png" imageAct="/images/buttons/forward_act.png" imagePas="/images/buttons/forward_pas.png" width="32" height="32" title="forwardmessage" />
+<roundcube:button command="delete" imageSel="/images/buttons/delete_sel.png" imageAct="/images/buttons/delete_act.png" imagePas="/images/buttons/delete_pas.png" width="32" height="32" title="deletemessage" />
+<roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="printmessage" />
+</div>
+
+<div id="quicksearchbar">
+<roundcube:object name="searchform" type="search" results="5" id="quicksearchbox" /><roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
+</div>
+
+<div id="messagecountbar">
+<roundcube:button command="previouspage" imageSel="/images/buttons/previous_sel.png" imageAct="/images/buttons/previous_act.png" imagePas="/images/buttons/previous_pas.png" width="11" height="11" title="previousmessages" />
+&nbsp;<roundcube:object name="messageCountDisplay" />&nbsp;
+<roundcube:button command="nextpage" imageSel="/images/buttons/next_sel.png" imageAct="/images/buttons/next_act.png" imagePas="/images/buttons/next_pas.png" width="11" height="11" title="nextmessages" />
+</div>
+
+<div id="mailboxlist-header"><roundcube:label name="mailboxlist" /></div>
+<div id="mailboxlist-container"><roundcube:object name="mailboxlist" id="mailboxlist" maxlength="16" /></div>
+
+<div id="mailboxcontrols">
+<roundcube:label name="folder" />:&nbsp;
+<roundcube:button command="expunge" label="compact" classAct="active" />&nbsp;
+<roundcube:button command="purge" label="empty" classAct="active" />&nbsp;
+</div>
+
+
+<div id="mailcontframe">
+<roundcube:object name="messages"
+  id="messagelist"
+  cellspacing="0"
+  summary="Message list"
+  messageIcon="/images/icons/dot.png"
+  unreadIcon="/images/icons/unread.png"
+  deletedIcon="/images/icons/deleted.png"
+  repliedIcon="/images/icons/replied.png"
+  attachmentIcon="/images/icons/attachment.png" />
+</div>
+
+<div id="listcontrols">
+<roundcube:label name="select" />:&nbsp;
+<roundcube:button command="select-all" label="all" classAct="active" />&nbsp;
+<roundcube:button command="select-all" prop="unread" label="unread" classAct="active" />&nbsp;
+<roundcube:button command="select-none" label="none" classAct="active" /> &nbsp;&nbsp;&nbsp;
+<roundcube:label name="quota" />: <roundcube:object name="quotaDisplay" />
+</div>
+
+</body>
+</html>
diff --git a/skins/default/templates/managefolders.html b/skins/default/templates/managefolders.html
new file mode 100644 (file)
index 0000000..d378619
--- /dev/null
@@ -0,0 +1,41 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/settings.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:include file="/includes/settingstabs.html" />
+
+<form name="subscriptionform" action="./" onsubmit="rcmail.command('create-folder');return false;">
+
+<div id="folder-manager">
+<roundcube:object name="foldersubscription" form="subscriptionform" id="subscription-table"
+  cellpadding="1" cellspacing="0" summary="Folder subscription table"
+  deleteIcon="/images/icons/folder-trash.png"
+  renameIcon="/images/icons/edit.png" />
+</div>
+
+<div id="bottomboxes">
+<div class="settingsbox">
+<div class="boxtitle"><roundcube:label name="createfolder" /></div>
+
+<div class="settingspart">
+<roundcube:label name="foldername" />:&nbsp;
+<roundcube:object name="createfolder" form="subscriptionform" />
+<roundcube:button command="create-folder" type="input" class="button" label="create" />
+</div>
+</div>
+
+</div>
+
+</form>
+
+<roundcube:include file="/includes/settingscripts.html" />
+
+</body>
+</html>
diff --git a/skins/default/templates/message.html b/skins/default/templates/message.html
new file mode 100644 (file)
index 0000000..f802b5a
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/mail.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+
+<div id="messagecountbar">
+<roundcube:button command="previousmessage" imageSel="/images/buttons/previous_sel.png" imageAct="/images/buttons/previous_act.png" imagePas="/images/buttons/previous_pas.png" width="11" height="11" title="previousmessages" />
+&nbsp;<roundcube:object name="messageCountDisplay" />&nbsp;
+<roundcube:button command="nextmessage" imageSel="/images/buttons/next_sel.png" imageAct="/images/buttons/next_act.png" imagePas="/images/buttons/next_pas.png" width="11" height="11" title="nextmessages" />
+</div>
+
+<div id="messagetoolbar">
+<roundcube:button command="list" image="/images/buttons/back_act.png" imageSel="/images/buttons/back_sel.png" imageAct="/images/buttons/back_act.png" width="32" height="32" title="backtolist" />
+<roundcube:button command="compose" imageSel="/images/buttons/compose_sel.png" imageAct="/images/buttons/compose_act.png" imagePas="/images/buttons/compose_pas.png" width="32" height="32" title="writenewmessage" />
+<roundcube:button command="reply" imageSel="/images/buttons/reply_sel.png" imageAct="/images/buttons/reply_act.png" imagePas="/images/buttons/reply_pas.png" width="32" height="32" title="replytomessage" />
+<roundcube:button command="reply-all" imageSel="/images/buttons/replyall_sel.png" imageAct="/images/buttons/replyall_act.png" imagePas="/images/buttons/replyall_pas.png" width="32" height="32" title="replytoallmessage" />
+<roundcube:button command="forward" imageSel="/images/buttons/forward_sel.png" imageAct="/images/buttons/forward_act.png" imagePas="/images/buttons/forward_pas.png" width="32" height="32" title="forwardmessage" />
+<roundcube:button command="delete" imageSel="/images/buttons/delete_sel.png" imageAct="/images/buttons/delete_act.png" imagePas="/images/buttons/delete_pas.png" width="32" height="32" title="deletemessage" />
+<roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="printmessage" />
+<roundcube:button command="viewsource" imageSel="/images/buttons/source_sel.png" imageAct="/images/buttons/source_act.png" imagePas="/images/buttons/source_pas.png" width="32" height="32" title="viewsource" />
+<roundcube:object name="mailboxlist" type="select" noSelection="moveto" maxlength="25" onchange="rcmail.command('moveto', this.options[this.selectedIndex].value)" class="mboxlist" />
+</div>
+
+<div id="mailboxlist-header"><roundcube:label name="mailboxlist" /></div>
+<div id="mailboxlist-container"><roundcube:object name="mailboxlist" id="mailboxlist" maxlength="16" /></div>
+
+<div id="messageframe">
+<roundcube:object name="messageHeaders" class="headers-table" cellspacing="0" cellpadding="2" addicon="/images/icons/plus.gif" summary="Message headers" />
+<roundcube:object name="messageAttachments" id="attachment-list" />
+<roundcube:object name="blockedObjects" id="remote-objects-message" />
+<roundcube:object name="messageBody" id="messagebody" showImages="true" />
+</div>
+
+</body>
+</html>
diff --git a/skins/default/templates/messagepart.html b/skins/default/templates/messagepart.html
new file mode 100644 (file)
index 0000000..cf5803d
--- /dev/null
@@ -0,0 +1,22 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/mail.css" />
+</head>
+<body class="extwin">
+
+<roundcube:include file="/includes/header.html" />
+
+<div id="partheader">
+<roundcube:object name="messagePartControls" cellpadding="2" cellspacing="0" />
+</div>
+
+
+<div id="messagepartcontainer">
+<roundcube:object name="messagePartFrame" id="messagepartframe" width="100%" height="85%" />
+</div>
+
+</body>
+</html>
diff --git a/skins/default/templates/printmessage.html b/skins/default/templates/printmessage.html
new file mode 100644 (file)
index 0000000..223c98b
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<link rel="stylesheet" type="text/css" href="/print.css" />
+</head>
+<body>
+
+<div id="header"><img src="/images/roundcube_logo_print.gif" width="182" height="50" alt="RoundCube Webmail" /></div>
+
+<div id="messageframe">
+<roundcube:object name="messageHeaders" class="headers-table" cellspacing="0" cellpadding="2" />
+<roundcube:object name="messageAttachments" id="attachment-list" />
+<roundcube:object name="messageBody" id="messagebody" showImages="false" />
+</div>
+
+</body>
+</html>
diff --git a/skins/default/templates/settings.html b/skins/default/templates/settings.html
new file mode 100644 (file)
index 0000000..bb01399
--- /dev/null
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/settings.css" />
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:include file="/includes/settingstabs.html" />
+
+<div id="userprefs-box">
+<div id="userprefs-title"><roundcube:label name="userpreferences" /></div>
+
+<div style="padding:15px">
+<roundcube:object name="userprefs">
+
+<p><br /><roundcube:button command="save" type="input" class="button" label="save" /></p>
+</div>
+</div>
+
+<roundcube:include file="/includes/settingscripts.html" />
+
+</body>
+</html>
diff --git a/skins/default/templates/showcontact.html b/skins/default/templates/showcontact.html
new file mode 100644 (file)
index 0000000..8a84343
--- /dev/null
@@ -0,0 +1,19 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/addresses.css" />
+</head>
+<body class="iframe">
+
+<div id="contact-title"><roundcube:object name="contactdetails" part="name" /></div>
+
+<div id="contact-details">
+<roundcube:object name="contactdetails" />
+
+<p><br /><roundcube:button command="edit" type="input" class="button" label="editcontact" /></p>
+</div>
+
+</body>
+</html>
diff --git a/skins/default/watermark.html b/skins/default/watermark.html
new file mode 100644 (file)
index 0000000..85e5365
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body style="background-color:#F2F2F2;">
+
+<div style="margin:10px auto; text-align:center">
+<img src="images/rcube_watermark.png" width="245" height="245" alt="" />
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/temp/.htaccess b/temp/.htaccess
new file mode 100644 (file)
index 0000000..8e6a345
--- /dev/null
@@ -0,0 +1,2 @@
+Order allow,deny
+Deny from all 
\ No newline at end of file