diff --git a/headers/os/add-ons/mail_daemon/ChainRunner.h b/headers/os/add-ons/mail_daemon/ChainRunner.h new file mode 100644 index 0000000000..41c1f83bad --- /dev/null +++ b/headers/os/add-ons/mail_daemon/ChainRunner.h @@ -0,0 +1,98 @@ +#ifndef ZOIDBERG_MAIL_CHAIN_RUNNER_H +#define ZOIDBERG_MAIL_CHAIN_RUNNER_H +/* ChainRunner - runs the mail inbound and outbound chains +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include + +class BStringList; +class BMailStatusWindow; +class BMailChain; + +class BMailChainCallback { + public: + virtual void Callback(status_t result) = 0; + // Called by the callback routines in ChainRunner + // result is the message that ended the chain + // (MD_HANDLED, MD_DISCARD, MD_NO_MORE_MESSAGES) + // Obviously, for process callbacks, result is + // gauranteed to be MD_NO_MORE_MESSAGES. +}; + +class BMailChainRunner : public BLooper { + public: + BMailChainRunner(BMailChain *chain, BMailStatusWindow *status, + bool self_destruct_when_done = true, bool save_chain_when_done = false, + bool destruct_chain_when_done = false); + ~BMailChainRunner(); + + //----Callback functions. Callback objects will be deleted for you. + + void RegisterMessageCallback(BMailChainCallback *callback); + // Your callback->Callback() function will be called when + // the current message is done being processed. + + void RegisterProcessCallback(BMailChainCallback *callback); + // Your callback->Callback() function will be called when + // a filter returns MD_PASS_COMPLETE and before we go back + // to waiting for new messages, or when the fetch list + // runs out. + + void RegisterChainCallback(BMailChainCallback *callback); + // Your callback->Callback() function will be called when + // a filter returns MD_ALL_PASSES_DONE and before everything + // is unloaded and sent home. + + void Stop(); + void ReportProgress(int bytes, int messages, const char *message = NULL); + void ResetProgress(const char *message = NULL); + + void GetMessages(BStringList *list, int32 bytes); + void GetSingleMessage(const char *uid, int32 length, BPath *into); + // The bytes or length field is the total size, used for updating the + // progress bar. Use -1 for unknown maximum size. + + bool QuitRequested(); + + void ShowError(const char *error); + void ShowMessage(const char *message); + + BMailChain *Chain(); + + //----The big, bad asynchronous RunChain() function. Pretty harmless looking, huh? + status_t RunChain(bool asynchronous = true); + + void MessageReceived (BMessage *msg); + + private: + void CallCallbacksFor(BList &list, status_t code); + void get_messages(BStringList *list); + status_t Init(); + #if USE_NASTY_SYNC_THREAD_HACK + static int32 thread_sync_func(void *arg); + status_t init_addons(); + #endif + + BMailChain *_chain; + BList message_cb, process_cb, chain_cb; + + bool destroy_self, destroy_chain, save_chain; + + BMailStatusWindow *_status; + BMailStatusView *_statview; + BList addons; + + bool suicide; + uint8 _other_reserved[3]; + uint32 _reserved[4]; +}; + +BMailChainRunner *GetMailChainRunner(int32 chain_id, BMailStatusWindow *status, bool selfDestruct = true); + +#endif /* ZOIDBERG_MAIL_CHAIN_RUNNER_H */ diff --git a/headers/os/add-ons/mail_daemon/MailAddon.h b/headers/os/add-ons/mail_daemon/MailAddon.h new file mode 100644 index 0000000000..abe3f80cbb --- /dev/null +++ b/headers/os/add-ons/mail_daemon/MailAddon.h @@ -0,0 +1,151 @@ +#ifndef ZOIDBERG_MAIL_ADDON_H +#define ZOIDBERG_MAIL_ADDON_H +/* Filter - the base class for all mail filters +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +class BMessage; +class BView; +class BPositionIO; +class BEntry; +class BPath; +class BView; +class BString; +class BPositionIO; +class BEntry; + +enum { + B_MAIL_DISCARD = B_MAIL_ERROR_BASE + 8, + //--- This terminates the chain and removes the message being processed + // both from disk and from the server. + B_MAIL_END_FETCH, + //--- Terminates the current operation + B_MAIL_END_CHAIN + //--- This is for yikes errors, like an unreachable server. +}; + +class BMailStatusView; +class BMailChainRunner; + +class BMailFilter +{ + public: + BMailFilter(BMessage* settings); + // How to support queueing messages until a time of the + // day/week/month/year? The settings will contain a + // persistent ChainID field, the same for all Filters + // on the same "chain". + + virtual ~BMailFilter(); + // This will be called when the settings for this Filter + // are changed, or there are no new messages to consume + // after settings->FindInt32("timeout") seconds. + + virtual status_t InitCheck(BString* out_message = NULL) = 0; + // Returns B_OK if the Filter was constructed success- + // fully. Otherwise it returns an error code. If it is + // passed a valid BString*, it may add an error message + // to the end of that BString iff it returns an error. + // If it returns an error code then the MailFilter will + // probably be deleted and the error shown to the user. + + virtual status_t ProcessMailMessage + ( + BPositionIO** io_message, BEntry* io_entry, + BMessage* io_headers, BPath* io_folder, const char *io_uid + ) = 0; + // Filters a message. On input and output, the arguments + // are expected to be as below; however it is allowed for + // the MailFilter to alter any of these values as nece- + // ssary, so long as the constraints are as described when + // the function returns: + // + // * io_message - a PositionIO that contains the message + // data, pointing to the first byte of the message's + // header. This can be swapped if, eg, the message + // is copied across volume boundries. When the chain + // begins this is a file in /tmp. + // * io_entry - The entry for the PositionIO above. + // * io_headers - a list of attributes that will be added + // to the message file. + // * io_folder - The message's "folder"---may be com- + // pletely unrelated to its on-disk Entry. + // * io_uid - The unique ID provided by the message's + // Protocol + // + // At most one Filter::ProcessMailMessage() for a given + // chain (and thus ChainID) will be called at a time. + + private: + virtual void _ReservedFilter1(); + virtual void _ReservedFilter2(); + virtual void _ReservedFilter3(); + virtual void _ReservedFilter4(); +}; + +// +// The addon interface: export instantiate_mailfilter() +// and instantiate_mailconfig() to create a Filter addon +// + +extern "C" _EXPORT BView* instantiate_config_panel(BMessage *settings,BMessage *metadata); +// return a view that configures the MailProtocol or MailFilter +// returned by the functions below. BView::Archive(foo,true) +// produces this addon's settings, which are passed to the in- +// stantiate_* functions and stored persistently. This function +// should gracefully handle empty and NULL settings. +// A note on the metadata argument: The metadata pointer is +// guaranteed to remain valid as long as this view exists. As it is +// a pointer to the chain's metadata, your view can save any +// chain-global settings to it in its Archive() function. Note that +// you must cache this pointer yourself! You will never get it again. +// Also note that it is possible for it to be NULL. + +extern "C" _EXPORT BMailFilter* instantiate_mailfilter(BMessage *settings, + BMailChainRunner *runner); +// Return a MailProtocol or MailFilter ready to do its thing, +// based on settings produced by archiving your config panel. +// Note that a Mail::Protocol is a Mail::Filter, so use +// instantiate_mailfilter to start things up. + +extern "C" _EXPORT status_t descriptive_name(BMessage *msg, char *buffer); +// the config panel will show this name in the chains filter +// list if this function returns B_OK. +// The buffer is as big as B_FILE_NAME_LENGTH. + +// standard Filters: +// +// * Parser - does ParseRFC2822(io_message,io_headers) +// * Folder - stores the message in the specified folder, +// optionally under io_folder, returns MD_HANDLED +// * HeaderFilter(regex,Yes_fiters,No_filters) - +// Applies Nes_filters to messages that have a header +// matching regex; applies No_filters otherwise. +// * CompatabilityFilter - Invokes the standard mail_dae- +// mon filter ~/config/settings/add-ons/MailDaemon/Filter +// on the message's Entry. +// * Producer - Reads outbound messages from disk and inserts +// them into the queue. +// * SMTPSender - Sends the message, via the specified +// SMTP server, to the people in header field +// "MAIL:recipients", changes the the Entry's +// "MAIL:flags" field to no longer pending, changes the +// "MAIL:status" header field to "Sent", and adds a header +// field "MAIL:when" with the time it was sent. +// * Dumper - returns MD_DISCARD +// +// +// Standard chain types: +// +// Incoming Mail: Protocol - Parser - Notifier - Folder +// Outgoing Mail: Producer - SMTPSender +// +// "chains" are lists of addons that appear in, or can be +// added to, the "Accounts" list in the config panel, a tree- +// view ordered by the chain type and the chain's AccountName(). +// Their config views should be shown, one after the other, +// in the config panel. + +#endif /* ZOIDBERG_MAIL_ADDON_H */ diff --git a/headers/os/add-ons/mail_daemon/MailProtocol.h b/headers/os/add-ons/mail_daemon/MailProtocol.h new file mode 100644 index 0000000000..c150435e38 --- /dev/null +++ b/headers/os/add-ons/mail_daemon/MailProtocol.h @@ -0,0 +1,107 @@ +#ifndef ZOIDBERG_MAIL_PROTOCOL_H +#define ZOIDBERG_MAIL_PROTOCOL_H +/* Protocol - the base class for protocol filters +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +#include + + +class BHandler; +class BStringList; +class BMailChainRunner; + +class BMailProtocol : public BMailFilter +{ + public: + BMailProtocol(BMessage* settings, BMailChainRunner *runner); + // Open a connection based on 'settings'. 'settings' will + // contain a persistent uint32 ChainID field. At most one + // Protocol per ChainID will exist at a given time. + // The constructor of Mail::Protocol initializes manifest. + // It is your responsibility to fill in unique_ids, *and + // to keep it updated* in the course of whatever nefarious + // things your protocol does. + + virtual ~BMailProtocol(); + // Close the connection and clean up. This will be cal- + // led after FetchMessage() or FetchNewMessage() returns + // B_TIMED_OUT or B_ERROR, or when the settings for this + // Protocol are changed. + + virtual status_t GetMessage( + const char* uid, + BPositionIO** out_file, BMessage* out_headers, + BPath* out_folder_location + )=0; + // Downloads the message with id uid, writing the message's + // RFC2822 contents to *out_file and storing any headers it + // wants to add other than those from the message itself into + // out_headers. It may store a path (if this type of account + // supports folders) in *out_folder_location. + // + // Returns B_OK if the message is now available in out_file, + // B_NAME_NOT_FOUND if there is no message with id 'uid' on + // the server, or another error if the connection failed. + // + // B_OK will cause the message to be stored and processed. + // B_NAME_NOT_FOUND will cause appropriate recovery to be + // taken (if such exists) but not cause the connection to + // be terminated. Any other error will cause anything writen + // to be discarded and and the connection closed. + + // OBS: + // The Protocol may replace *out_file with a custom (read- + // only) BPositionIO-derived object that preserves the il- + // lusion that the message is writen to *out_file, but in + // fact only reads from the server and writes to *out_file + // on demand. This BPositionIO must guarantee that any + // data returned by Read() has also been writen to *out_- + // file. It must return a read error if reading from the + // network or writing to *out_file fails. + // + // The mail_daemon will delete *out_file before invoking + // FetchMessage() or FetchNewMessage() again. + + virtual status_t DeleteMessage(const char* uid)=0; + // Removes the message from the server. After this, it's + // assumed (but not required) that GetMessage(uid,...) + // et al will fail with B_NAME_NOT_FOUND. + + void CheckForDeletedMessages(); + // You can call this to trigger a sweep for deleted messages. + // Automatically called at the beginning of the chain. + + //------MailFilter calls + virtual status_t ProcessMailMessage + ( + BPositionIO** io_message, BEntry* io_entry, + BMessage* io_headers, BPath* io_folder, const char* io_uid + ); + + protected: + BStringList *manifest, *unique_ids; + BMessage *settings; + + BMailChainRunner *runner; + private: + inline void error_alert(const char *process, status_t error); + virtual void _ReservedProtocol1(); + virtual void _ReservedProtocol2(); + virtual void _ReservedProtocol3(); + virtual void _ReservedProtocol4(); + virtual void _ReservedProtocol5(); + + friend class DeletePass; + + BHandler *trash_monitor; + BStringList *uids_on_disk; + + uint32 _reserved[3]; +}; + +#endif // ZOIDBERG_MAIL_PROTOCOL_H diff --git a/headers/os/add-ons/mail_daemon/ProtocolConfigView.h b/headers/os/add-ons/mail_daemon/ProtocolConfigView.h new file mode 100644 index 0000000000..aa36e67fb9 --- /dev/null +++ b/headers/os/add-ons/mail_daemon/ProtocolConfigView.h @@ -0,0 +1,38 @@ +#ifndef ZOIDBERG_PROTOCOL_CONFIG_VIEW_H +#define ZOIDBERG_PROTOCOL_CONFIG_VIEW_H +/* ProtocolConfigView - the standard config view for all protocols +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +#include + +typedef enum { + B_MAIL_PROTOCOL_HAS_AUTH_METHODS = 1, + B_MAIL_PROTOCOL_HAS_FLAVORS = 2, + B_MAIL_PROTOCOL_HAS_USERNAME = 4, + B_MAIL_PROTOCOL_HAS_PASSWORD = 8, + B_MAIL_PROTOCOL_HAS_HOSTNAME = 16, + B_MAIL_PROTOCOL_CAN_LEAVE_MAIL_ON_SERVER = 32 +} b_mail_protocol_config_options; + +class BMailProtocolConfigView : public BView { + public: + BMailProtocolConfigView(uint32 options_mask = B_MAIL_PROTOCOL_HAS_FLAVORS | B_MAIL_PROTOCOL_HAS_USERNAME | B_MAIL_PROTOCOL_HAS_PASSWORD | B_MAIL_PROTOCOL_HAS_HOSTNAME); + virtual ~BMailProtocolConfigView(); + + void SetTo(BMessage *archive); + + void AddFlavor(const char *label); + void AddAuthMethod(const char *label,bool needUserPassword = true); + + virtual status_t Archive(BMessage *into, bool deep = true) const; + virtual void GetPreferredSize(float *width, float *height); + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage *msg); + + private: + uint32 _reserved[5]; +}; + +#endif /* ZOIDBERG_PROTOCOL_CONFIG_VIEW_H */ diff --git a/headers/os/add-ons/mail_daemon/RemoteStorageProtocol.h b/headers/os/add-ons/mail_daemon/RemoteStorageProtocol.h new file mode 100644 index 0000000000..35cac91895 --- /dev/null +++ b/headers/os/add-ons/mail_daemon/RemoteStorageProtocol.h @@ -0,0 +1,41 @@ +#ifndef ZOIDBERG_MAIL_REMOTESTORAGEPROTOCOL_H +#define ZOIDBERG_MAIL_REMOTESTORAGEPROTOCOL_H +/* RemoteStorageProtocol - the base class for protocol filters +** +** Copyright 2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +#include +#include + +class BMailRemoteStorageProtocol : public BMailProtocol { + public: + BMailRemoteStorageProtocol(BMessage *settings, BMailChainRunner *runner); + virtual ~BMailRemoteStorageProtocol(); + + virtual status_t GetMessage(const char *mailbox, const char *message, BPositionIO **, BMessage *headers) = 0; + virtual status_t AddMessage(const char *mailbox, BPositionIO *data, BString *id) = 0; + + virtual status_t DeleteMessage(const char *mailbox, const char *message) = 0; + virtual status_t CopyMessage(const char *mailbox, const char *to_mailbox, BString *message) = 0; + + virtual status_t CreateMailbox(const char *mailbox) = 0; + virtual status_t DeleteMailbox(const char *mailbox) = 0; + + void SyncMailbox(const char *mailbox); + + //----Mail::Protocol stuff + virtual status_t GetMessage( + const char* uid, + BPositionIO** out_file, BMessage* out_headers, + BPath* out_folder_location); + virtual status_t DeleteMessage(const char* uid); + + //---Data members + BStringList mailboxes; + + private: + BHandler *handler; +}; + +#endif // ZOIDBERG_MAIL_REMOTESTORAGEPROTOCOL_H diff --git a/headers/os/add-ons/mail_daemon/StringList.h b/headers/os/add-ons/mail_daemon/StringList.h new file mode 100644 index 0000000000..9b7b94040b --- /dev/null +++ b/headers/os/add-ons/mail_daemon/StringList.h @@ -0,0 +1,73 @@ +#ifndef ZOIDBERG_STRING_LIST_H +#define ZOIDBERG_STRING_LIST_H +/* StringList - a string list implementation +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +class BList; + +class BStringList : public BFlattenable { + public: + BStringList(); + BStringList(const BStringList&); + + ~BStringList(void); + + BStringList &operator=(const BStringList &from); + +/* Flattenable stuff */ + virtual bool IsFixedSize() const; //--false for obvious reasons + virtual type_code TypeCode() const; + virtual ssize_t FlattenedSize() const; + virtual status_t Flatten(void *buffer, ssize_t size) const; + virtual bool AllowsTypeCode(type_code code) const; + virtual status_t Unflatten(type_code c, const void *buf, ssize_t size); + +/* Adding and removing items. */ + void AddItem(const char *item); + void AddList(const BStringList *newItems); + bool RemoveItem(const char *item); + void MakeEmpty(); + +/* Retrieving items. */ + const char *ItemAt(int32) const; + int32 IndexOf(const char *) const; + +/* Querying the list. */ + bool HasItem(const char *item) const; + int32 CountItems() const; + bool IsEmpty() const; + +/* Determining differences between lists */ + void NotHere(BStringList &other_list, BStringList *results); + void NotThere(BStringList &other_list, BStringList *results); + +/* Useful list logic operators */ + BStringList &operator += (const char *item); + BStringList &operator += (BStringList &list); + + BStringList &operator -= (const char *item); + BStringList &operator -= (BStringList &list); + + BStringList operator | (BStringList &list); + BStringList &operator |= (BStringList &list); + + BStringList operator ^ (BStringList &list); + BStringList &operator ^= (BStringList &list); + + bool operator == (BStringList &list); + const char *operator [] (int32 index); + + private: + void *_buckets[256]; + int32 _items; + BList *_indexed; + + uint32 _reserved[5]; +}; + +#endif /* ZOIDBERG_STRING_LIST_H */ diff --git a/headers/private/mail/FileConfigView.h b/headers/private/mail/FileConfigView.h new file mode 100644 index 0000000000..9e4ee913d5 --- /dev/null +++ b/headers/private/mail/FileConfigView.h @@ -0,0 +1,56 @@ +#ifndef FILE_CONFIG_VIEW +#define FILE_CONFIG_VIEW +/* FileConfigView - a file configuration view for filters +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include + +class BTextControl; +class BButton; + +class BFileControl : public BView +{ + public: + BFileControl(BRect rect,const char *name,const char *label,const char *pathOfFile = NULL,uint32 flavors = B_DIRECTORY_NODE); + ~BFileControl(); + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage *msg); + + void SetText(const char *pathOfFile); + const char *Text() const; + + void SetEnabled(bool enabled); + + virtual void GetPreferredSize(float *width, float *height); + + private: + BTextControl *fText; + BButton *fButton; + + BFilePanel *fPanel; + + uint32 _reserved[5]; +}; + +class BMailFileConfigView : public BFileControl +{ + public: + BMailFileConfigView(const char *label,const char *name,bool useMeta = false,const char *defaultPath = NULL,uint32 flavors = B_DIRECTORY_NODE); + + void SetTo(BMessage *archive,BMessage *metadata); + virtual status_t Archive(BMessage *into,bool deep = true) const; + + private: + BMessage *fMeta; + bool fUseMeta; + const char *fName; + + uint32 _reserved[5]; +}; + +#endif /* FILE_CONFIG_VIEW */ diff --git a/headers/private/mail/MDRLanguage.h b/headers/private/mail/MDRLanguage.h new file mode 100644 index 0000000000..823cd52a0e --- /dev/null +++ b/headers/private/mail/MDRLanguage.h @@ -0,0 +1,51 @@ +#ifndef ZOIDBERG_MDR_LANGUAGE_H +#define ZOIDBERG_MDR_LANGUAGE_H +/* +** Mail Daemon Replacement interim International Language Macros. +** +** Copyright 2003 Dr. Zoidberg Enterprises. All rights reserved. +** +** The input for the language macro system is the MDR_DIALECT define which is +** set by the makefile (you compile a version for the desired language, it +** can't change at run-time using this temporary internationalization system). +** MDR_DIALECT was set to 0 for English-USA, 1 for Japanese, and other numbers +** for other language dialects. If it's not present, we use English-USA. +** +** $Log: MDRLanguage.h,v $ +** Revision 1.1 2004/09/20 22:31:42 nwhitehorn +** Imported MDR. Some code still not entirely functional -- I haven't been able to figure out how to detect SSL, so IMAP and POP have it turned off. PPP auto-detect is also not functional at the moment. Other than that, it seems to work beautifully. Packaging will come later. +** +** Revision 1.3 2003/02/05 22:43:17 agmsmith +** Default language (judging by the use of "color" in the bemail +** preferences) changed to be English-USA. +** +** Revision 1.2 2003/01/30 23:52:12 agmsmith +** Initial simple language system macros done. +** +** Revision 1.1 2003/01/30 23:26:07 agmsmith +** Starting to add a simplistic internationalization system for "Koki", +** who wants to use BeMail in Japanese. It should later be replaced by +** a more comprehensive system, but at least this one will mark out the +** spots where translated text is needed. +*/ + +#define MDR_DIALECT_ENGLISH_USA 0 +#define MDR_DIALECT_JAPANESE 1 + +#ifndef MDR_DIALECT + #define MDR_DIALECT MDR_DIALECT_ENGLISH_USA +#endif + +#if (MDR_DIALECT == MDR_DIALECT_ENGLISH_USA) + #define MDR_DIALECT_CHOICE(EnglishUSA,Japanese) EnglishUSA + #define MDR_COUNTRY "United States of America" + #define MDR_LANGUAGE "English" +#elif (MDR_DIALECT == MDR_DIALECT_JAPANESE) + #define MDR_DIALECT_CHOICE(EnglishUSA,Japanese) Japanese + #define MDR_COUNTRY "Japan" + #define MDR_LANGUAGE "Japanese" +#else + #error "Unrecognized value specified for MDR_DIALECT macro constant." +#endif + +#endif /* ZOIDBERG_MDR_LANGUAGE_H */ diff --git a/headers/private/mail/NodeMessage.h b/headers/private/mail/NodeMessage.h new file mode 100644 index 0000000000..50a4011a9a --- /dev/null +++ b/headers/private/mail/NodeMessage.h @@ -0,0 +1,35 @@ +#ifndef ZOIDBERG_GARGOYLE_NODE_MESSAGE_H +#define ZOIDBERG_GARGOYLE_NODE_MESSAGE_H +/* NodeMessage - "streaming" interface and support functions for BNodes +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +/* + These functions gives a nice BMessage interface to node attributes, + by letting you transfer attributes to and from BMessages. It makes + it so you can use all the convenient Find...() and Add...() functions + provided by BMessage for attributes too. You use it as follows: + + BMessage m; + BNode n(path); + if (reading) { n>>m; printf("woohoo=%s\n",m.FindString("woohoo")) } + else { m.AddString("woohoo","it's howdy doody time"); n< +#include + +#ifdef B_BEOS_VERSION_DANO +#define _IMPEXP_MAIL +#endif + +_IMPEXP_MAIL BNode& operator<<(BNode& n, const BMessage& m); +_IMPEXP_MAIL BNode& operator>>(BNode& n, BMessage& m); +inline const BMessage& operator>>(const BMessage& m, BNode& n){n<>m;return m;} + +#endif /* ZOIDBERG_GARGOYLE_NODE_MESSAGE_H */ diff --git a/headers/private/mail/crypt.h b/headers/private/mail/crypt.h new file mode 100644 index 0000000000..a7db5b5cf3 --- /dev/null +++ b/headers/private/mail/crypt.h @@ -0,0 +1,17 @@ +#ifndef ZOIDBERG_CRYPT_H +#define ZOIDBERG_CRYPT_H +/* crypt - simple encryption algorithm used for passwords +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#define PASSWORD_LENGTH 32 + + +char *get_passwd(BMessage *msg,const char *name); +bool set_passwd(BMessage *msg,const char *name,const char *password); + +void passwd_crypt(char *in,char *out,int length); + +#endif /* ZOIDBERG_CRYPT_H */ diff --git a/headers/private/mail/des.h b/headers/private/mail/des.h new file mode 100644 index 0000000000..e65744c435 --- /dev/null +++ b/headers/private/mail/des.h @@ -0,0 +1,57 @@ +#ifndef ZOIDBERG_DES_H +#define ZOIDBERG_DES_H +/* DES - encryption algorithm, removed double and triple DES +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +/* des.h - adapted from d3des.h: + * + * Headers and defines for d3des.c + * Graven Imagery, 1992. + * + * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge + * (GEnie : OUTER; CIS : [71755,204]) + */ + +#define DES_ENCRYPT 0 /* MODE == encrypt */ +#define DES_DECRYPT 1 /* MODE == decrypt */ + + +#ifdef __cplusplus +extern "C" { +#endif + +extern void des_setkey(unsigned char *, short); +/* hexkey[8] MODE + * Sets the internal key register according to the hexadecimal + * key contained in the 8 bytes of hexkey, according to the DES, + * for encryption or decryption according to MODE. + */ + +extern void des_usekey(unsigned long *); +/* cookedkey[32] + * Loads the internal key register with the data in cookedkey. + */ + +extern void des_cpkey(unsigned long *); +/* cookedkey[32] + * Copies the contents of the internal key register into the storage + * located at &cookedkey[0]. + */ + +extern void des_crypt(unsigned char *, unsigned char *); +/* from[8] to[8] + * Encrypts/Decrypts (according to the key currently loaded in the + * internal key register) one block of eight bytes at address 'from' + * into the block at address 'to'. They can be the same. + */ + +extern void des_encrypt(char *from,char *to); +extern void des_decrypt(char *from,int fromLength,char *to); + +#ifdef __cplusplus +} +#endif + +#endif /* ZOIDBERG_DES_H */ diff --git a/headers/private/mail/mail_util.h b/headers/private/mail/mail_util.h new file mode 100644 index 0000000000..8c736ff1c6 --- /dev/null +++ b/headers/private/mail/mail_util.h @@ -0,0 +1,85 @@ +#ifndef ZOIDBERG_GARGOYLE_MAIL_UTIL_H +#define ZOIDBERG_GARGOYLE_MAIL_UTIL_H +/* mail util - header parsing +** +** Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +class BString; + +// The next couple of functions are our wrapper around convert_to_utf8 and +// convert_from_utf8 so that they can also convert from UTF-8 to UTF-8 by +// specifying the MDR_UTF8_CONVERSION constant as the conversion operation. + +status_t mail_convert_to_utf8(uint32 srcEncoding, const char *src, + int32 *srcLen, char *dst, int32 *dstLen, int32 *state, + char substitute = B_SUBSTITUTE); + +status_t mail_convert_from_utf8(uint32 dstEncoding, const char *src, + int32 *srcLen, char *dst, int32 *dstLen, int32 *state, + char substitute = B_SUBSTITUTE); + + +void trim_white_space(BString &string); +// Remove leading and trailing white space from the string. + +void SubjectToThread(BString &string); +// Convert a subject to the core words (remove the extraneous RE: re: etc). + +time_t ParseDateWithTimeZone(const char *DateString); +// Converts a date to a time. Handles time zones too, unlike parsedate. + +ssize_t rfc2047_to_utf8(char **buffer, size_t *bufLen, size_t strLen = 0); +ssize_t utf8_to_rfc2047(char **bufp, ssize_t length,uint32 charset, char encoding); +// convert (in place) RFC 2047-style escape sequences ("=?...?.?...?=") +// in the first strLen characters of *buffer into UTF-8, and return the +// length of the converted string or an error code less than 0 on error. +// +// This may cause the string to grow. If it grows bigger than *bufLen, +// *buffer will be reallocated using realloc(), and its new length stored +// in *bufLen. +// +// Unidentified charsets and conversion errors cause +// the offending text to be skipped. + +void FoldLineAtWhiteSpaceAndAddCRLF (BString &string); +// Insert CRLF at various spots in the given string (before white space) so +// that the line length is mostly under 78 bytes. Also makes sure there is a +// CRLF at the very end. + +ssize_t nextfoldedline(const char** header, char **buffer, size_t *buflen); +ssize_t readfoldedline(FILE *file, char **buffer, size_t *buflen); +ssize_t readfoldedline(BPositionIO &in, char **buffer, size_t *buflen); +// Return in *buffer a \n-terminated line (even if the original is \r\n +// terminated or not terminated at all (last line in file situation)) from a +// memory buffer, FILE* or BPositionIO, after folding \r?\n(\s)->$1. Return +// the length of the folded string directly, or a negative error code if there +// was a memory allocation error or file read error. It will return zero only +// when trying to read at end of file. *header, *file and &in are left +// pointing to the first character after the line read. +// +// if buffer is not NULL return a pointer to the buffer in *buffer +// if *buffer is not NULL, use the preallocated buffer, though it may get +// realloc'd (so use malloc to allocate it and expect to have your *buffer +// pointer modified to point to the new buffer if a realloc happens). +// if buflen is not NULL, return the final size of the buffer in buflen +// if buffer is not NULL, buflen is not NULL, and *buffer is not NULL +// *buffer is a buffer of size *buflen +// if buffer is NULL or *buffer is NULL, and buflen is not NULL then +// start with a buffer of size *buflen + +status_t parse_header(BMessage &headers, BPositionIO &input); + +void extract_address(BString &address); + // retrieves the mail address only from an address header formatted field + +void extract_address_name(BString &address); + // Given a header field (usually the From: e-mail address) with gobbledygook in + // it, find the longest human-readable phrase (usually the person's name). + +void get_address_list(BList &list, const char *string, void (*cleanupFunc)(BString &) = NULL); + +#endif /* ZOIDBERG_GARGOYLE_MAIL_UTIL_H */ diff --git a/headers/private/mail/regex.h b/headers/private/mail/regex.h new file mode 100644 index 0000000000..73ed1f16c3 --- /dev/null +++ b/headers/private/mail/regex.h @@ -0,0 +1,606 @@ +/* Definitions for data structures and routines for the regular + expression library, version 0.12. + Copyright (C) 1985,89,90,91,92,93,95,96,97,98 Free Software Foundation, Inc. + + This file is part of the GNU C Library. Its master source is NOT part of + the C library, however. The master source lives in /gd/gnu/lib. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* BeOS PPC has no regex in libroot */ +#if !__POWERPC__ +# include +#else + +#ifndef _REGEX_H +#define _REGEX_H 1 + +#ifdef __BEOS__ +# undef __STDC__ +# define __STDC__ 1 +#endif + +/* Allow the use in C++ code. */ +#ifdef __cplusplus +extern "C" { +#endif + +/* POSIX says that must be included (by the caller) before + . */ + +#if !defined _POSIX_C_SOURCE && !defined _POSIX_SOURCE && defined VMS +/* VMS doesn't have `size_t' in , even though POSIX says it + should be there. */ +# include +#endif + +/* The following two types have to be signed and unsigned integer type + wide enough to hold a value of a pointer. For most ANSI compilers + ptrdiff_t and size_t should be likely OK. Still size of these two + types is 2 for Microsoft C. Ugh... */ +typedef long int s_reg_t; +typedef unsigned long int active_reg_t; + +/* The following bits are used to determine the regexp syntax we + recognize. The set/not-set meanings are chosen so that Emacs syntax + remains the value 0. The bits are given in alphabetical order, and + the definitions shifted by one from the previous bit; thus, when we + add or remove a bit, only one other definition need change. */ +typedef unsigned long int reg_syntax_t; + +/* If this bit is not set, then \ inside a bracket expression is literal. + If set, then such a \ quotes the following character. */ +#define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) + +/* If this bit is not set, then + and ? are operators, and \+ and \? are + literals. + If set, then \+ and \? are operators and + and ? are literals. */ +#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) + +/* If this bit is set, then character classes are supported. They are: + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. + If not set, then character classes are not supported. */ +#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) + +/* If this bit is set, then ^ and $ are always anchors (outside bracket + expressions, of course). + If this bit is not set, then it depends: + ^ is an anchor if it is at the beginning of a regular + expression or after an open-group or an alternation operator; + $ is an anchor if it is at the end of a regular expression, or + before a close-group or an alternation operator. + + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because + POSIX draft 11.2 says that * etc. in leading positions is undefined. + We already implemented a previous draft which made those constructs + invalid, though, so we haven't changed the code back. */ +#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) + +/* If this bit is set, then special characters are always special + regardless of where they are in the pattern. + If this bit is not set, then special characters are special only in + some contexts; otherwise they are ordinary. Specifically, + * + ? and intervals are only special when not after the beginning, + open-group, or alternation operator. */ +#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) + +/* If this bit is set, then *, +, ?, and { cannot be first in an re or + immediately after an alternation or begin-group operator. */ +#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) + +/* If this bit is set, then . matches newline. + If not set, then it doesn't. */ +#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) + +/* If this bit is set, then . doesn't match NUL. + If not set, then it does. */ +#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) + +/* If this bit is set, nonmatching lists [^...] do not match newline. + If not set, they do. */ +#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) + +/* If this bit is set, either \{...\} or {...} defines an + interval, depending on RE_NO_BK_BRACES. + If not set, \{, \}, {, and } are literals. */ +#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) + +/* If this bit is set, +, ? and | aren't recognized as operators. + If not set, they are. */ +#define RE_LIMITED_OPS (RE_INTERVALS << 1) + +/* If this bit is set, newline is an alternation operator. + If not set, newline is literal. */ +#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) + +/* If this bit is set, then `{...}' defines an interval, and \{ and \} + are literals. + If not set, then `\{...\}' defines an interval. */ +#define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) + +/* If this bit is set, (...) defines a group, and \( and \) are literals. + If not set, \(...\) defines a group, and ( and ) are literals. */ +#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) + +/* If this bit is set, then \ matches . + If not set, then \ is a back-reference. */ +#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) + +/* If this bit is set, then | is an alternation operator, and \| is literal. + If not set, then \| is an alternation operator, and | is literal. */ +#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) + +/* If this bit is set, then an ending range point collating higher + than the starting range point, as in [z-a], is invalid. + If not set, then when ending range point collates higher than the + starting range point, the range is ignored. */ +#define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) + +/* If this bit is set, then an unmatched ) is ordinary. + If not set, then an unmatched ) is invalid. */ +#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) + +/* If this bit is set, succeed as soon as we match the whole pattern, + without further backtracking. */ +#define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) + +/* If this bit is set, do not process the GNU regex operators. + If not set, then the GNU regex operators are recognized. */ +#define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) + +/* If this bit is set, turn on internal regex debugging. + If not set, and debugging was on, turn it off. + This only works if regex.c is compiled -DDEBUG. + We define this bit always, so that all that's needed to turn on + debugging is to recompile regex.c; the calling code can always have + this bit set, and it won't affect anything in the normal case. */ +#define RE_DEBUG (RE_NO_GNU_OPS << 1) + +/* This global variable defines the particular regexp syntax to use (for + some interfaces). When a regexp is compiled, the syntax used is + stored in the pattern buffer, so changing this does not affect + already-compiled regexps. */ +extern reg_syntax_t re_syntax_options; + +/* Define combinations of the above bits for the standard possibilities. + (The [[[ comments delimit what gets put into the Texinfo file, so + don't delete them!) */ +/* [[[begin syntaxes]]] */ +#define RE_SYNTAX_EMACS 0 + +#define RE_SYNTAX_AWK \ + (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ + | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ + | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) + +#define RE_SYNTAX_GNU_AWK \ + ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DEBUG) \ + & ~(RE_DOT_NOT_NULL | RE_INTERVALS | RE_CONTEXT_INDEP_OPS)) + +#define RE_SYNTAX_POSIX_AWK \ + (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ + | RE_INTERVALS | RE_NO_GNU_OPS) + +#define RE_SYNTAX_GREP \ + (RE_BK_PLUS_QM | RE_CHAR_CLASSES \ + | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \ + | RE_NEWLINE_ALT) + +#define RE_SYNTAX_EGREP \ + (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \ + | RE_NEWLINE_ALT | RE_NO_BK_PARENS \ + | RE_NO_BK_VBAR) + +#define RE_SYNTAX_POSIX_EGREP \ + (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES) + +/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ +#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC + +#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC + +/* Syntax bits common to both basic and extended POSIX regex syntax. */ +#define _RE_SYNTAX_POSIX_COMMON \ + (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ + | RE_INTERVALS | RE_NO_EMPTY_RANGES) + +#define RE_SYNTAX_POSIX_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM) + +/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes + RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this + isn't minimal, since other operators, such as \`, aren't disabled. */ +#define RE_SYNTAX_POSIX_MINIMAL_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) + +#define RE_SYNTAX_POSIX_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ + | RE_UNMATCHED_RIGHT_PAREN_ORD) + +/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS + replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */ +#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) +/* [[[end syntaxes]]] */ + +/* Maximum number of duplicates an interval can allow. Some systems + (erroneously) define this in other header files, but we want our + value, so remove any previous define. */ +#ifdef RE_DUP_MAX +# undef RE_DUP_MAX +#endif +/* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */ +#define RE_DUP_MAX (0x7fff) + + +/* POSIX `cflags' bits (i.e., information for `regcomp'). */ + +/* If this bit is set, then use extended regular expression syntax. + If not set, then use basic regular expression syntax. */ +#define REG_EXTENDED 1 + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +#define REG_ICASE (REG_EXTENDED << 1) + +/* If this bit is set, then anchors do not match at newline + characters in the string. + If not set, then anchors do match at newlines. */ +#define REG_NEWLINE (REG_ICASE << 1) + +/* If this bit is set, then report only success or fail in regexec. + If not set, then returns differ between not matching and errors. */ +#define REG_NOSUB (REG_NEWLINE << 1) + + +/* POSIX `eflags' bits (i.e., information for regexec). */ + +/* If this bit is set, then the beginning-of-line operator doesn't match + the beginning of the string (presumably because it's not the + beginning of a line). + If not set, then the beginning-of-line operator does match the + beginning of the string. */ +#define REG_NOTBOL 1 + +/* Like REG_NOTBOL, except for the end-of-line. */ +#define REG_NOTEOL (1 << 1) + + +/* If any error codes are removed, changed, or added, update the + `re_error_msg' table in regex.c. */ +typedef enum +{ +#ifdef _XOPEN_SOURCE + REG_ENOSYS = -1, /* This will never happen for this implementation. */ +#endif + + REG_NOERROR = 0, /* Success. */ + REG_NOMATCH, /* Didn't find a match (for regexec). */ + + /* POSIX regcomp return error codes. (In the order listed in the + standard.) */ + REG_BADPAT, /* Invalid pattern. */ + REG_ECOLLATE, /* Not implemented. */ + REG_ECTYPE, /* Invalid character class name. */ + REG_EESCAPE, /* Trailing backslash. */ + REG_ESUBREG, /* Invalid back reference. */ + REG_EBRACK, /* Unmatched left bracket. */ + REG_EPAREN, /* Parenthesis imbalance. */ + REG_EBRACE, /* Unmatched \{. */ + REG_BADBR, /* Invalid contents of \{\}. */ + REG_ERANGE, /* Invalid range end. */ + REG_ESPACE, /* Ran out of memory. */ + REG_BADRPT, /* No preceding re for repetition op. */ + + /* Error codes we've added. */ + REG_EEND, /* Premature end. */ + REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ + REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ +} reg_errcode_t; + +/* This data structure represents a compiled pattern. Before calling + the pattern compiler, the fields `buffer', `allocated', `fastmap', + `translate', and `no_sub' can be set. After the pattern has been + compiled, the `re_nsub' field is available. All other fields are + private to the regex routines. */ + +#ifndef RE_TRANSLATE_TYPE +# define RE_TRANSLATE_TYPE char * +#endif + +struct re_pattern_buffer +{ +/* [[[begin pattern_buffer]]] */ + /* Space that holds the compiled pattern. It is declared as + `unsigned char *' because its elements are + sometimes used as array indexes. */ + unsigned char *buffer; + + /* Number of bytes to which `buffer' points. */ + unsigned long int allocated; + + /* Number of bytes actually used in `buffer'. */ + unsigned long int used; + + /* Syntax setting with which the pattern was compiled. */ + reg_syntax_t syntax; + + /* Pointer to a fastmap, if any, otherwise zero. re_search uses + the fastmap, if there is one, to skip over impossible + starting points for matches. */ + char *fastmap; + + /* Either a translate table to apply to all characters before + comparing them, or zero for no translation. The translation + is applied to a pattern when it is compiled and to a string + when it is matched. */ + RE_TRANSLATE_TYPE translate; + + /* Number of subexpressions found by the compiler. */ + size_t re_nsub; + + /* Zero if this pattern cannot match the empty string, one else. + Well, in truth it's used only in `re_search_2', to see + whether or not we should use the fastmap, so we don't set + this absolutely perfectly; see `re_compile_fastmap' (the + `duplicate' case). */ +#if LITTLE_ENDIAN + unsigned can_be_null : 1; + + /* If REGS_UNALLOCATED, allocate space in the `regs' structure + for `max (RE_NREGS, re_nsub + 1)' groups. + If REGS_REALLOCATE, reallocate space if necessary. + If REGS_FIXED, use what's there. */ +#define REGS_UNALLOCATED 0 +#define REGS_REALLOCATE 1 +#define REGS_FIXED 2 + unsigned regs_allocated : 2; + + /* Set to zero when `regex_compile' compiles a pattern; set to one + by `re_compile_fastmap' if it updates the fastmap. */ + unsigned fastmap_accurate : 1; + + /* If set, `re_match_2' does not return information about + subexpressions. */ + unsigned no_sub : 1; + + /* If set, a beginning-of-line anchor doesn't match at the + beginning of the string. */ + unsigned not_bol : 1; + + /* Similarly for an end-of-line anchor. */ + unsigned not_eol : 1; + + /* If true, an anchor at a newline matches. */ + unsigned newline_anchor : 1; +#else + /* If true, an anchor at a newline matches. */ + unsigned newline_anchor : 1; + + /* Similarly for an end-of-line anchor. */ + unsigned not_eol : 1; + + /* If set, a beginning-of-line anchor doesn't match at the + beginning of the string. */ + unsigned not_bol : 1; + + /* If set, `re_match_2' does not return information about + subexpressions. */ + unsigned no_sub : 1; + + /* Set to zero when `regex_compile' compiles a pattern; set to one + by `re_compile_fastmap' if it updates the fastmap. */ + unsigned fastmap_accurate : 1; + + /* If REGS_UNALLOCATED, allocate space in the `regs' structure + for `max (RE_NREGS, re_nsub + 1)' groups. + If REGS_REALLOCATE, reallocate space if necessary. + If REGS_FIXED, use what's there. */ +#define REGS_UNALLOCATED 0 +#define REGS_REALLOCATE 1 +#define REGS_FIXED 2 + unsigned regs_allocated : 2; + + unsigned can_be_null : 1; +#endif +/* [[[end pattern_buffer]]] */ +}; + +typedef struct re_pattern_buffer regex_t; + +/* Type for byte offsets within the string. POSIX mandates this. */ +typedef int regoff_t; + + +/* This is the structure we store register match data in. See + regex.texinfo for a full description of what registers match. */ +struct re_registers +{ + unsigned num_regs; + regoff_t *start; + regoff_t *end; +}; + + +/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer, + `re_match_2' returns information about at least this many registers + the first time a `regs' structure is passed. */ +#ifndef RE_NREGS +# define RE_NREGS 30 +#endif + + +/* POSIX specification for registers. Aside from the different names than + `re_registers', POSIX uses an array of structures, instead of a + structure of arrays. */ +typedef struct +{ + regoff_t rm_so; /* Byte offset from string's start to substring's start. */ + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ +} regmatch_t; + +/* Declarations for routines. */ + +/* To avoid duplicating every routine declaration -- once with a + prototype (if we are ANSI), and once without (if we aren't) -- we + use the following macro to declare argument types. This + unfortunately clutters up the declarations a bit, but I think it's + worth it. */ + +#if __STDC__ + +# define _RE_ARGS(args) args + +#else /* not __STDC__ */ + +# define _RE_ARGS(args) () + +#endif /* not __STDC__ */ + +/* Sets the current default syntax to SYNTAX, and return the old syntax. + You can also simply assign to the `re_syntax_options' variable. */ +extern reg_syntax_t __re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); +extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax)); + +/* Compile the regular expression PATTERN, with length LENGTH + and syntax given by the global `re_syntax_options', into the buffer + BUFFER. Return NULL if successful, and an error string if not. */ +extern const char *__re_compile_pattern + _RE_ARGS ((const char *pattern, size_t length, + struct re_pattern_buffer *buffer)); +extern const char *re_compile_pattern + _RE_ARGS ((const char *pattern, size_t length, + struct re_pattern_buffer *buffer)); + + +/* Compile a fastmap for the compiled pattern in BUFFER; used to + accelerate searches. Return 0 if successful and -2 if was an + internal error. */ +extern int __re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); +extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer)); + + +/* Search in the string STRING (with length LENGTH) for the pattern + compiled into BUFFER. Start searching at position START, for RANGE + characters. Return the starting position of the match, -1 for no + match, or -2 for an internal error. Also return register + information in REGS (if REGS and BUFFER->no_sub are nonzero). */ +extern int __re_search + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, int range, struct re_registers *regs)); +extern int re_search + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, int range, struct re_registers *regs)); + + +/* Like `re_search', but search in the concatenation of STRING1 and + STRING2. Also, stop searching at index START + STOP. */ +extern int __re_search_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, int range, struct re_registers *regs, int stop)); +extern int re_search_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, int range, struct re_registers *regs, int stop)); + + +/* Like `re_search', but return how many characters in STRING the regexp + in BUFFER matched, starting at position START. */ +extern int __re_match + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, struct re_registers *regs)); +extern int re_match + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string, + int length, int start, struct re_registers *regs)); + + +/* Relates to `re_match' as `re_search_2' relates to `re_search'. */ +extern int __re_match_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, struct re_registers *regs, int stop)); +extern int re_match_2 + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1, + int length1, const char *string2, int length2, + int start, struct re_registers *regs, int stop)); + + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using BUFFER and REGS will use this memory + for recording register information. STARTS and ENDS must be + allocated with malloc, and must each be at least `NUM_REGS * sizeof + (regoff_t)' bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ +extern void __re_set_registers + _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, + unsigned num_regs, regoff_t *starts, regoff_t *ends)); +extern void re_set_registers + _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs, + unsigned num_regs, regoff_t *starts, regoff_t *ends)); + +#ifdef _REGEX_RE_COMP +# ifndef _CRAY +/* 4.2 bsd compatibility. */ +extern char *re_comp _RE_ARGS ((const char *)); +extern int re_exec _RE_ARGS ((const char *)); +# endif +#endif + +/* POSIX compatibility. */ +extern int __regcomp _RE_ARGS ((regex_t *__preg, const char *__pattern, + int __cflags)); +extern int regcomp _RE_ARGS ((regex_t *__preg, const char *__pattern, + int __cflags)); + +extern int __regexec _RE_ARGS ((const regex_t *__preg, + const char *__string, size_t __nmatch, + regmatch_t __pmatch[], int __eflags)); +extern int regexec _RE_ARGS ((const regex_t *__preg, + const char *__string, size_t __nmatch, + regmatch_t __pmatch[], int __eflags)); + +extern size_t __regerror _RE_ARGS ((int __errcode, const regex_t *__preg, + char *__errbuf, size_t __errbuf_size)); +extern size_t regerror _RE_ARGS ((int __errcode, const regex_t *__preg, + char *__errbuf, size_t __errbuf_size)); + +extern void __regfree _RE_ARGS ((regex_t *__preg)); +extern void regfree _RE_ARGS ((regex_t *__preg)); + + +#ifdef __cplusplus +} +#endif /* C++ */ + +#endif /* regex.h */ + +#endif /* __PPC__ */ diff --git a/headers/private/mail/status.h b/headers/private/mail/status.h new file mode 100644 index 0000000000..276dfd374a --- /dev/null +++ b/headers/private/mail/status.h @@ -0,0 +1,83 @@ +#ifndef ZOIDBERG_STATUS_WINDOW_H +#define ZOIDBERG_STATUS_WINDOW_H +/* StatusWindow - the status window while fetching/sending mails +** +** Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include + +class BStatusBar; +class BStringView; +class BMailStatusView; + +class BMailStatusWindow : public BWindow { + public: + BMailStatusWindow(BRect rect, const char *name, uint32 show_when); + ~BMailStatusWindow(); + + virtual void FrameMoved(BPoint origin); + virtual void WorkspaceActivated(int32 workspace, bool active); + virtual void MessageReceived(BMessage *msg); + + BMailStatusView *NewStatusView(const char *description, bool upstream); + void RemoveView(BMailStatusView *view); + int32 CountVisibleItems(); + + bool HasItems(void); + void SetShowCriterion(uint32); + void SetDefaultMessage(const BString &message); + + private: + friend class BMailStatusView; + + void SetBorderStyle(int32 look); + void ActuallyAddStatusView(BMailStatusView *status); + + BList fStatusViews; + uint32 fShowMode; + BView *fDefaultView; + BStringView *fMessageView; + float fMinWidth; + float fMinHeight; + int32 fWindowMoved; + int32 fLastWorkspace; + BRect fFrame; + + uint32 _reserved[5]; +}; + +class BMailStatusView : public BBox { + public: + void AddProgress(int32 how_much); + void SetMessage(const char *msg); + void SetMaximum(int32 max_bytes); + int32 CountTotalItems(); + void SetTotalItems(int32 items); + void AddItem(void); + void Reset(bool hide = true); + + virtual ~BMailStatusView(); + + private: + friend class BMailStatusWindow; + + BMailStatusView(BRect rect,const char *description,bool upstream); + void AddSelfToWindow(); + + BStatusBar *status; + BMailStatusWindow *window; + int32 items_now; + int32 total_items; + bool is_upstream; + bool by_bytes; + char pre_text[255]; + + uint32 _reserved[5]; +}; + +#endif /* ZOIDBERG_STATUS_WINDOW_H */ diff --git a/src/add-ons/Jamfile b/src/add-ons/Jamfile index c2b079c987..3cc0570c9d 100644 --- a/src/add-ons/Jamfile +++ b/src/add-ons/Jamfile @@ -81,6 +81,7 @@ KernelLd SubInclude OBOS_TOP src add-ons accelerants ; SubInclude OBOS_TOP src add-ons input_server ; SubInclude OBOS_TOP src add-ons kernel ; +SubInclude OBOS_TOP src add-ons mail_daemon ; SubInclude OBOS_TOP src add-ons media ; SubInclude OBOS_TOP src add-ons print ; SubInclude OBOS_TOP src add-ons translators ; diff --git a/src/add-ons/mail_daemon/inbound_filters/match_header/ConfigView.cpp b/src/add-ons/mail_daemon/inbound_filters/match_header/ConfigView.cpp new file mode 100644 index 0000000000..ba8d2e100c --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/match_header/ConfigView.cpp @@ -0,0 +1,173 @@ +/* RuleFilter's config view - performs action depending on matching a header value +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +const uint32 kMsgActionMoveTo = 'argm'; +const uint32 kMsgActionDelete = 'argd'; +const uint32 kMsgActionSetTo = 'args'; +const uint32 kMsgActionReplyWith = 'argr'; +const uint32 kMsgActionSetRead = 'arge'; + + +class RuleFilterConfig : public BView { + public: + RuleFilterConfig(BMessage *settings); + + virtual void MessageReceived(BMessage *msg); + virtual void AttachedToWindow(); + virtual status_t Archive(BMessage *into, bool deep = true) const; + virtual void GetPreferredSize(float *width, float *height); + private: + BTextControl *attr, *regex; + BFileControl *arg; + BPopUpMenu *menu, *outbound; + BMenuField *outbound_field; + int staging; + int32 chain; +}; + +#include + +RuleFilterConfig::RuleFilterConfig(BMessage *settings) : BView(BRect(0,0,260,85),"rulefilter_config", B_FOLLOW_LEFT | B_FOLLOW_TOP, 0) { + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + attr = new BTextControl(BRect(5,5,100,20),"attr",MDR_DIALECT_CHOICE ("If","条件:"),MDR_DIALECT_CHOICE ("header (e.g. Subject)","ヘッダ(例えばSubject)"),NULL); + attr->SetDivider(be_plain_font->StringWidth(MDR_DIALECT_CHOICE ("If ","条件: "))+ 4); + if (settings->HasString("attribute")) + attr->SetText(settings->FindString("attribute")); + AddChild(attr); + + regex = new BTextControl(BRect(104,5,255,20),"attr",MDR_DIALECT_CHOICE (" is "," が "),MDR_DIALECT_CHOICE ("value (can be a regular expression, like *spam*)","値(正規表現対応)"),NULL); + regex->SetDivider(be_plain_font->StringWidth(MDR_DIALECT_CHOICE (" is "," が ")) + 4); + if (settings->HasString("regex")) + regex->SetText(settings->FindString("regex")); + AddChild(regex); + + arg = new BFileControl(BRect(5,55,255,80),"arg",NULL,MDR_DIALECT_CHOICE ("this field is based on the Action","ここは動作によって意味が変わります")); + if (BControl *control = (BControl *)arg->FindView("select_file")) + control->SetEnabled(false); + if (settings->HasString("argument")) + arg->SetText(settings->FindString("argument")); + + outbound = new BPopUpMenu(MDR_DIALECT_CHOICE ("","<アカウントを選択>")); + BList list; + GetOutboundMailChains(&list); + if (settings->HasInt32("do_what")) + staging = settings->FindInt32("do_what"); + else + staging = -1; + if (staging == 3) + chain = settings->FindInt32("argument"); + else + chain = -1; + printf("Chain: %d\n",chain); + for (int32 i = 0; i < list.CountItems(); i++) { + BMenuItem *item = new BMenuItem(((BMailChain *)(list.ItemAt(i)))->Name(), new BMessage(((BMailChain *)(list.ItemAt(i)))->ID())); + outbound->AddItem(item); + if (((BMailChain *)(list.ItemAt(i)))->ID() == chain) + item->SetMarked(true); + delete (BMailChain *)(list.ItemAt(i)); + } + +} + + +void RuleFilterConfig::AttachedToWindow() { + menu = new BPopUpMenu(MDR_DIALECT_CHOICE ("","<動作を選択>")); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Move To","移動する"), new BMessage(kMsgActionMoveTo))); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Set Flags To","フラグを指定する"), new BMessage(kMsgActionSetTo))); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Delete Message","削除する"), new BMessage(kMsgActionDelete))); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply With","返事を書く"), new BMessage(kMsgActionReplyWith))); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Set As Read","既読にする"), new BMessage(kMsgActionSetRead))); + menu->SetTargetForItems(this); + + BMenuField *field = new BMenuField(BRect(5,30,210,50),"do_what",MDR_DIALECT_CHOICE ("Then","ならば"),menu); + field->ResizeToPreferred(); + field->SetDivider(be_plain_font->StringWidth(MDR_DIALECT_CHOICE ("Then","ならば")) + 8); + AddChild(field); + + outbound_field = new BMenuField(BRect(5,55,255,80),"reply","Foo",outbound); + outbound_field->ResizeToPreferred(); + outbound_field->SetDivider(0); + if (staging >= 0) { + menu->ItemAt(staging)->SetMarked(true); + MessageReceived(menu->ItemAt(staging)->Message()); + } else { + AddChild(arg); + } +} + +status_t RuleFilterConfig::Archive(BMessage *into, bool deep) const { + into->MakeEmpty(); + into->AddInt32("do_what",menu->IndexOf(menu->FindMarked())); + into->AddString("attribute",attr->Text()); + into->AddString("regex",regex->Text()); + if (into->FindInt32("do_what") == 3) { + printf("foo!"); + into->AddInt32("argument",outbound->FindMarked()->Message()->what); + } else + into->AddString("argument",arg->Text()); + + return B_OK; +} + +void RuleFilterConfig::MessageReceived(BMessage *msg) { + switch (msg->what) + { + case kMsgActionMoveTo: + case kMsgActionSetTo: + if (BControl *control = (BControl *)arg->FindView("file_path")) + arg->SetEnabled(true); + if (BControl *control = (BControl *)arg->FindView("select_file")) + control->SetEnabled(msg->what == kMsgActionMoveTo); + if (arg->Parent() == NULL) { + outbound_field->RemoveSelf(); + AddChild(arg); + } + break; + case kMsgActionDelete: + arg->SetEnabled(false); + if (arg->Parent() == NULL) { + outbound_field->RemoveSelf(); + AddChild(arg); + } + break; + case kMsgActionReplyWith: + if (outbound->Parent() == NULL) { + arg->RemoveSelf(); + AddChild(outbound_field); + } + break; + case kMsgActionSetRead: + arg->SetEnabled(false); + if (arg->Parent() == NULL) { + outbound_field->RemoveSelf(); + AddChild(arg); + } + break; + default: + BView::MessageReceived(msg); + } +} + +void RuleFilterConfig::GetPreferredSize(float *width, float *height) { + *width = 260; + *height = 55; +} + +BView* instantiate_config_panel(BMessage *settings,BMessage *metadata) { + return new RuleFilterConfig(settings); +} diff --git a/src/add-ons/mail_daemon/inbound_filters/match_header/Jamfile b/src/add-ons/mail_daemon/inbound_filters/match_header/Jamfile new file mode 100644 index 0000000000..bc81dc3215 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/match_header/Jamfile @@ -0,0 +1,12 @@ +SubDir OBOS_TOP src add-ons mail_daemon inbound_filters match_header ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +Addon Match\ Header : mail_daemon inbound_filters : + ConfigView.cpp + RuleFilter.cpp + StringMatcher.cpp ; + +LinkSharedOSLibs Match\ Header : + be mail ; diff --git a/src/add-ons/mail_daemon/inbound_filters/match_header/RuleFilter.cpp b/src/add-ons/mail_daemon/inbound_filters/match_header/RuleFilter.cpp new file mode 100644 index 0000000000..0b4d0b5e71 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/match_header/RuleFilter.cpp @@ -0,0 +1,119 @@ +/* Match Header - performs action depending on matching a header value +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include + +#include +#include + +#include "RuleFilter.h" + +//class StatusView; + +RuleFilter::RuleFilter(BMessage *settings) : BMailFilter(settings) { + // attribute is adapted to our "capitalize-each-word-in-the-header" policy + BString attr; + settings->FindString("attribute",&attr); + attr.CapitalizeEachWord(); + attribute = strdup(attr.String()); + + const char *regex = NULL; + settings->FindString("regex",®ex); + matcher.SetPattern(regex,true); + + settings->FindString("argument",&arg); + settings->FindInt32("do_what",(long *)&do_what); + if (do_what == Z_SET_REPLY) + settings->FindInt32("argument",&chain_id); +} + +RuleFilter::~RuleFilter() +{ + if (attribute) + free((void *)attribute); +} + +status_t RuleFilter::InitCheck(BString* out_message) { + return B_OK; +} + +status_t RuleFilter::ProcessMailMessage +( + BPositionIO** , BEntry* entry, + BMessage* io_headers, BPath* io_folder, const char* +) { + const char *data; + if (!attribute) + return B_OK; //----That field doesn't exist? NO match + + if (io_headers->FindString(attribute,&data) < B_OK) { //--Maybe the capitalization was wrong? + BString capped(attribute); + capped.CapitalizeEachWord(); //----Enfore capitalization + if (io_headers->FindString(capped.String(),&data) < B_OK) //----This time it's *really* not there + return B_OK; //---No match + } + + if (data == NULL) //--- How would this happen? No idea + return B_OK; + + if (!matcher.Match(data)) + return B_OK; //-----There wasn't an error. We're just not supposed to do anything + + switch (do_what) { + case Z_MOVE_TO: + if (io_headers->ReplaceString("DESTINATION",arg) != B_OK) + io_headers->AddString("DESTINATION",arg); + break; + case Z_TRASH: + return B_MAIL_DISCARD; + case Z_FLAG: + { + BString string = arg; + BNode(entry).WriteAttrString("MAIL:filter_flags",&string); + } + break; + case Z_SET_REPLY: + BNode(entry).WriteAttr("MAIL:reply_with",B_INT32_TYPE,0,&chain_id,4); + break; + case Z_SET_READ: + if (io_headers->ReplaceString("STATUS", "Read") != B_OK) + io_headers->AddString("STATUS", "Read"); + break; + default: + fprintf(stderr,"Unknown do_what: 0x%04x!\n",do_what); + } + + return B_OK; +} + +status_t descriptive_name(BMessage *settings, char *buffer) { + const char *attribute = NULL; + settings->FindString("attribute",&attribute); + const char *regex = NULL; + settings->FindString("regex",®ex); + + if (!attribute || strlen(attribute) > 15) + return B_ERROR; + sprintf(buffer, "Match \"%s\"", attribute); + + if (!regex) + return B_OK; + + char reg[20]; + strncpy(reg, regex, 16); + if (strlen(regex) > 15) + strcpy(reg + 15, "..."); + + sprintf(buffer + strlen(buffer), " against \"%s\"", reg); + + return B_OK; +} + +BMailFilter* instantiate_mailfilter(BMessage* settings,BMailChainRunner *) +{ + return new RuleFilter(settings); +} diff --git a/src/add-ons/mail_daemon/inbound_filters/match_header/RuleFilter.h b/src/add-ons/mail_daemon/inbound_filters/match_header/RuleFilter.h new file mode 100644 index 0000000000..2af7f1507e --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/match_header/RuleFilter.h @@ -0,0 +1,44 @@ +#ifndef ZOIDBERG_RULE_FILTER_H +#define ZOIDBERG_RULE_FILTER_H +/* RuleFilter - performs action depending on matching a header value +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include + +#include "StringMatcher.h" + + +typedef enum { + Z_MOVE_TO, + Z_FLAG, + Z_TRASH, + Z_SET_REPLY, + Z_SET_READ +} z_mail_action_flags; + +class RuleFilter : public BMailFilter { + public: + RuleFilter(BMessage *settings); + virtual ~RuleFilter(); + + virtual status_t InitCheck(BString* out_message = NULL); + + virtual status_t ProcessMailMessage(BPositionIO** io_message, + BEntry* io_entry, + BMessage* io_headers, + BPath* io_folder, + const char* io_uid); + private: + StringMatcher matcher; + const char* attribute; + const char* arg; + int32 chain_id; + z_mail_action_flags do_what; +}; + +#endif /* ZOIDBERG_RULE_FILTER_H */ diff --git a/src/add-ons/mail_daemon/inbound_filters/match_header/StringMatcher.cpp b/src/add-ons/mail_daemon/inbound_filters/match_header/StringMatcher.cpp new file mode 100644 index 0000000000..659c8647c4 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/match_header/StringMatcher.cpp @@ -0,0 +1,186 @@ +//--------This file shamelessly stolen from Jeremy Friesner's excellent MUSCLE--------- +/* This file is Copyright 2000 Level Control Systems. See the included LICENSE.txt file for details. */ + +#include +#include + +#include "StringMatcher.h" + +#include +#include + +StringMatcher::StringMatcher() : _regExpValid(false) +{ + // empty +} + +StringMatcher :: StringMatcher(const char * str) : _regExpValid(false) +{ + SetPattern(str); +} + +StringMatcher::~StringMatcher() +{ + if (_regExpValid) regfree(&_regExp); +} + +bool StringMatcher::SetPattern(const char * str, bool isSimple) +{ + PortableString pattern; + + if (isSimple) + { + pattern = "^\\("; + + bool escapeMode = false; + for (const char * ptr = str; *ptr != '\0'; ptr++) + { + if (escapeMode) + { + escapeMode = false; + switch(*ptr) + { + case ',': case '|': case '(': case ')': case '?': + pattern += *ptr; + break; + + default: + pattern += '\\'; + pattern += *ptr; + break; + } + } + else + { + switch(*ptr) + { + case ',': case '|': + pattern += "\\|"; + break; + + case '.': case '(': case ')': + pattern += '\\'; + pattern += *ptr; + break; + + case '*': + pattern += ".*"; + break; + + case '?': + pattern += '.'; + break; + + case '\\': + escapeMode = true; + break; + + break; + + default: + pattern += *ptr; + break; + } + } + } + pattern += "\\)$"; + //printf("OUTPUT: pattern became '%s'.\n", pattern.Cstr()); + } + + // Free the old regular expression, if any + if (_regExpValid) + { + regfree(&_regExp); + _regExpValid = false; + } + + // And compile the new one + _regExpValid = (regcomp(&_regExp, (pattern.Length() > 0) ? pattern.String() : str, 0) == 0); + return _regExpValid; +} + + +bool +StringMatcher::Match(const char *str) const +{ +#ifdef __INTEL__ + char buffer[1024]; + if (strlen(str) > 1024) { + // internal Be regex seems to be broken with strings larger than a certain size :-/ + memcpy(buffer, str, 1023); + buffer[1023] = '\0'; + str = buffer; + } +#endif + + if (_regExpValid == false) + return false; + + int regExpStat = regexec(&_regExp, str, 0, NULL, 0); + + return (regExpStat != REG_NOMATCH); +} + + +bool IsRegexToken(char c) +{ + switch(c) + { + case '[': case ']': case '*': case '?': case '\\': case ',': case '|': case '(': case ')': + return true; + + default: + return false; + } +} + +void EscapeRegexTokens(PortableString & s) +{ + const char * str = s.String(); + + PortableString ret; + while(*str) + { + if (IsRegexToken(*str)) ret += '\\'; + ret += *str; + str++; + } + s = ret; +} + +bool HasRegexTokens(const char * str) +{ + while(*str) + { + if (IsRegexToken(*str)) return true; + else str++; + } + return false; +} + +bool MakeRegexCaseInsensitive(PortableString & str) +{ + bool changed = false; + PortableString ret; + for (uint32 i=0; i= 'A')&&(next <= 'Z')) + { + char buf[5]; + sprintf(buf, "[%c%c]", next, next+('a'-'A')); + ret += buf; + changed = true; + } + else if ((next >= 'a')&&(next <= 'z')) + { + char buf[5]; + sprintf(buf, "[%c%c]", next, next+('A'-'a')); + ret += buf; + changed = true; + } + else ret += next; + } + if (changed) str = ret; + return changed; +} diff --git a/src/add-ons/mail_daemon/inbound_filters/match_header/StringMatcher.h b/src/add-ons/mail_daemon/inbound_filters/match_header/StringMatcher.h new file mode 100644 index 0000000000..c4985834f5 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/match_header/StringMatcher.h @@ -0,0 +1,94 @@ +//--------This file shamelessly stolen from Jeremy Friesner's excellent MUSCLE--------- +/* This file is Copyright 2001 Level Control Systems. See the included LICENSE.txt file for details. */ + +#ifndef STRINGMATCHER_H +#define STRINGMATCHER_H + +#include + +#ifdef __BEOS__ +# if __POWERPC__ +# include "regex.h" // use included regex if system doesn't provide one +# else +# include +# endif +#else +# include +#endif + +class BString; +#define PortableString BString + +//////////////////////////////////////////////////////////////////////////// +// +// NOTE: This class is based on the psStringMatcher v1.3 class +// developed by Lars Jørgen Aas for the +// Prodigal Software File Requester. Used by permission. +// +//////////////////////////////////////////////////////////////////////////// + + +/** A utility class for doing globbing or regular expression matching. (A thin wrapper around the C regex calls) */ +class StringMatcher +{ +public: + /** Default Constructor. */ + StringMatcher(); + + /** A constructor that sets the simple expression. + * @param matchString the wildcard pattern or regular expression to match with + */ + StringMatcher(const char * matchString); + + /** Destructor */ + ~StringMatcher(); + + /** + * Set a new wildcard pattern or regular expression for this StringMatcher to use in future Match() calls. + * @param expression The new globbing pattern or regular expression to match with. + * @param isSimpleFormat If you wish to use the formal regex syntax, + * instead of the simple syntax, set isSimpleFormat to false. + * @return True on success, false on error (e.g. expression wasn't parsable, or out of memory) + */ + bool SetPattern(const char * const expression, bool isSimpleFormat=true); + + /** Returns true iff (string) is matched by the current expression. + * @param string a string to match against using our current expression. + * @return true iff (string) matches, false otherwise. + */ + bool Match(const char *string) const; + +private: + bool _regExpValid; + regex_t _regExp; +}; + +// Some regular expression utility functions + +/** Puts a backslash in front of any char in (str) that is "special" to the regex pattern matching. + * @param str The string to check for special regex chars and possibly modify to escape them. + */ +void EscapeRegexTokens(PortableString & str); + +/** Returns true iff any "special" chars are found in (str). + * @param str The string to check for special regex chars. + * @return True iff any special regex chars were found in (str). + */ +bool HasRegexTokens(const char * str); + +/** Returns true iff (c) is a regular expression "special" char. + * @param c an ASCII char + * @return true iff (c) is a special regex char. + */ +bool IsRegexToken(char c); + +/** Given a regular expression, makes it case insensitive by + * replacing every occurance of a letter with a upper-lower combo, + * e.g. Hello -> [Hh][Ee][Ll][Ll][Oo] + * @param str a string to check for letters, and possibly modify to make case-insensitive + * @return true iff anything was changed, false if no changes were necessary. + */ +bool MakeRegexCaseInsensitive(PortableString & str); + + +#endif diff --git a/src/add-ons/mail_daemon/inbound_filters/r5_filter/Jamfile b/src/add-ons/mail_daemon/inbound_filters/r5_filter/Jamfile new file mode 100644 index 0000000000..92579d3fd4 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/r5_filter/Jamfile @@ -0,0 +1,10 @@ +SubDir OBOS_TOP src add-ons mail_daemon inbound_filters r5_filter ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +Addon R5\ Daemon\ Filter : mail_daemon inbound_filters : + filter.cpp ; + +LinkSharedOSLibs R5\ Daemon\ Filter : + be mail ; diff --git a/src/add-ons/mail_daemon/inbound_filters/r5_filter/filter.cpp b/src/add-ons/mail_daemon/inbound_filters/r5_filter/filter.cpp new file mode 100644 index 0000000000..a3f52ee027 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_filters/r5_filter/filter.cpp @@ -0,0 +1,87 @@ +/* R5 Daemon Filter - a filter comparable with the one from the original mail_daemon +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NodeMessage.h" + +class CompatibilityFilter : public BMailFilter +{ + bool enabled; + BPath path; + BEntry filter; + status_t status; + + public: + CompatibilityFilter(BMessage*); + virtual status_t InitCheck(BString *err); + virtual status_t ProcessMailMessage + ( + BPositionIO** io_message, BEntry* io_entry, + BMessage* io_headers, BPath* io_folder, const char* io_uid + ); +}; + +CompatibilityFilter::CompatibilityFilter(BMessage* msg) + : BMailFilter(msg), enabled(msg->FindBool("enabled")), status(B_OK) +{ + if (find_directory(B_USER_ADDONS_DIRECTORY, &path) != B_OK) { + status = B_NAME_NOT_FOUND; + return; + } + path.Append("MailDaemon/Filter"); + BEntry filter(path.Path()); +} + +status_t CompatibilityFilter::InitCheck(BString* err) +{ + return status; +} + +status_t CompatibilityFilter::ProcessMailMessage + (BPositionIO** , BEntry* io_entry, BMessage* headers, BPath* , const char*) +{ + int32 ret; + + if (enabled && filter.InitCheck() == B_OK && filter.Exists()) + { + const char *refs[2]; + refs[0] = path.Path(); + + BPath epath; + io_entry->GetPath(&epath); + refs[1] = epath.Path(); + + thread_id fil = load_image(2, refs, (const char**)environ); + if (fil >= B_OK) + { + /*BNode node(io_entry); //---ATT-Someone explain this, please. + node << *message;*/ + + wait_for_thread(fil,&ret); + + /*message->MakeEmpty(); + node >> *message;*/ + } + else fprintf(stderr,"%s\n", strerror(fil)); + } + return B_OK; +} + +BMailFilter* instantiate_mailfilter(BMessage* settings, BMailChainRunner*) +{ + return new CompatibilityFilter(settings); +} diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/Jamfile b/src/add-ons/mail_daemon/inbound_protocols/imap/Jamfile new file mode 100644 index 0000000000..d37ffa9df2 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/Jamfile @@ -0,0 +1,14 @@ +SubDir OBOS_TOP src add-ons mail_daemon inbound_protocols imap ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +SubDirC++Flags -DBONE ; + +Addon IMAP : mail_daemon inbound_protocols : + imap_client.cpp + imap_config.cpp + NestedString.cpp ; + +LinkSharedOSLibs IMAP : + be mail socket bind ; diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/NestedString.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/NestedString.cpp new file mode 100644 index 0000000000..b8090b0cd4 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/NestedString.cpp @@ -0,0 +1,111 @@ +#include + +#include "NestedString.h" + +NestedString::NestedString() : string(NULL), we_own(false) {} + +NestedString::~NestedString() { + for (int32 i = 0; i < children.CountItems(); i++) + delete (NestedString *)(children.ItemAt(i)); + + if (we_own && string) + delete [] string; +} + +NestedString &NestedString::operator [] (int i) const { + return *((NestedString *)(children.ItemAt(i))); +} + +const char *NestedString::operator ()() const { + if (string == NULL) { + static BString result; //---This isn't at all thread-safe, but it doesn't matter, since it's single-threaded + result = "("; + for (int32 i = 0; i < CountItems(); i++) + result << (*this)[i]() << ' '; + result << ')'; + return result.String(); + } + return string; +} + +NestedString &NestedString::AdoptAndAdd(const char *add) { + + if (string != NULL) { + NestedString *to_add = new NestedString; + to_add->string = string; + to_add->we_own = we_own; + children.AddItem(to_add); + string = NULL; + } + + NestedString *to_add = new NestedString; + to_add->string = add; + to_add->we_own = true; + children.AddItem(to_add); + return *to_add; +} + +NestedString &NestedString::operator += (const char *add) { + //printf("Adding string: \"%s\"\n",add); + + if (string != NULL) { + NestedString *to_add = new NestedString; + to_add->string = string; + to_add->we_own = we_own; + children.AddItem(to_add); + string = NULL; + } + + NestedString *to_add = new NestedString; + to_add->string = add; + to_add->we_own = false; + children.AddItem(to_add); + return *to_add; +} + +NestedString &NestedString::operator += (BString &add) { + //printf("Adding string: \"%s\"\n",add.String()); + + if (string != NULL) { + NestedString *to_add = new NestedString; + to_add->string = string; + to_add->we_own = we_own; + children.AddItem(to_add); + string = NULL; + } + + NestedString *to_add = new NestedString; + to_add->string = new char[add.Length()+1]; + strcpy((char *)(to_add->string),add.String()); + to_add->we_own = false; + children.AddItem(to_add); + return *to_add; +} + +int32 NestedString::CountItems() const { + return children.CountItems(); +} + +bool NestedString::HasChildren() const { + return (children.CountItems() != 0); +} + +void NestedString::PrintToStream(int indentation) { + for (int j = 0; j < indentation; j++) + printf("\t"); + printf("%d items:\n",CountItems()); + /*if (CountItems() == 1) { + for (int j = 0; j < indentation; j++) + printf("\t"); + printf("0: %s",(*this)()); + }*/ + for (int32 i = 0; i < CountItems(); i++) { + for (int j = 0; j < indentation; j++) + printf("\t"); + if ((*this)[i].HasChildren()) + (*this)[i].PrintToStream(indentation+1); + else + printf("%d: %s\n",i,(*this)[i]()); + } +} + diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/NestedString.h b/src/add-ons/mail_daemon/inbound_protocols/imap/NestedString.h new file mode 100644 index 0000000000..c5aecdf8ed --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/NestedString.h @@ -0,0 +1,24 @@ +#include +#include + +class NestedString { + public: + NestedString(); + ~NestedString(); + + NestedString &operator [] (int) const; + const char *operator ()() const; + + NestedString &AdoptAndAdd(const char *); + NestedString &operator += (const char *); + NestedString &operator += (BString &); + + int32 CountItems() const; + bool HasChildren() const; + + void PrintToStream(int indent = 0); + private: + BList children; + const char *string; + bool we_own; +}; diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_client.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_client.cpp new file mode 100644 index 0000000000..b9a30d962d --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_client.cpp @@ -0,0 +1,1150 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BONE + #include + #include + #include + #include +#else + #include +#endif + +#ifdef IMAPSSL + #include + #include +#endif + +#include "NestedString.h" + +#define CRLF "\r\n" +#define xEOF 236 +const bigtime_t kIMAP4ClientTimeout = 1000000*60; // 60 sec + +enum { OK,BAD,NO,CONTINUE, NOT_COMMAND_RESPONSE }; + +struct mailbox_info { + int32 exists; + int32 next_uid; + BString server_mb_name; +}; + +class IMAP4Client : public BMailRemoteStorageProtocol { + public: + IMAP4Client(BMessage *settings, BMailChainRunner *run); + virtual ~IMAP4Client(); + + virtual status_t GetMessage(const char *mailbox, const char *message, BPositionIO **, BMessage *headers); + virtual status_t AddMessage(const char *mailbox, BPositionIO *data, BString *id); + + virtual status_t DeleteMessage(const char *mailbox, const char *message); + virtual status_t CopyMessage(const char *mailbox, const char *to_mailbox, BString *message); + + virtual status_t CreateMailbox(const char *mailbox); + virtual status_t DeleteMailbox(const char *mailbox); + + void GetUniqueIDs(); + + status_t ReceiveLine(BString &out); + status_t SendCommand(const char *command); + + status_t Select(const char *mb, bool force_reselect = false, bool queue_new_messages = true, bool noop = true, bool no_command = false, bool ignore_forced_reselect = false); + status_t Close(); + + virtual status_t InitCheck(BString *) { if (net < 0 && err == B_OK) return net; return err; } + + int GetResponse(BString &tag, NestedString *parsed_response, bool report_literals = false, bool recursion_flag = false); + bool WasCommandOkay(BString &response); + + void InitializeMailboxes(); + + private: + friend class NoopWorker; + friend class IMAP4PartialReader; + + NoopWorker *noop; + BMessageRunner *nooprunner; + + int32 commandCount; + int net; + BString selected_mb, inbox_name, hierarchy_delimiter, mb_root; + BList box_info; + status_t err; + + #ifdef IMAPSSL + SSL_CTX *ctx; + SSL *ssl; + BIO *sbio; + + bool use_ssl; + #endif + + bool force_reselect; +}; + +class NoopWorker : public BHandler { + public: + NoopWorker(IMAP4Client *a) : us(a), last_run(0) {} + void MessageReceived(BMessage *msg) { + if (msg->what != 'impn' /* IMaP Noop */) + return; + + if ((time(NULL) - last_run) < 9) + return; + + us->Select(us->inbox_name.String()); + last_run = time(NULL); + } + private: + IMAP4Client *us; + time_t last_run; +}; + +IMAP4Client::IMAP4Client(BMessage *settings, BMailChainRunner *run) : BMailRemoteStorageProtocol(settings,run), commandCount(0), net(-1), selected_mb(""), noop(NULL), force_reselect(false) { + err = B_OK; + + mb_root = settings->FindString("root"); + #ifdef IMAPSSL + use_ssl = (settings->FindInt32("flavor") == 1); + #endif + + int port = settings->FindInt32("port"); + + if (port <= 0) + #ifdef IMAPSSL + port = use_ssl ? 993 : 143; + #else + port = 143; + #endif + +//-----Open TCP link + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Opening connection...","接続中...")); + + uint32 hostIP = inet_addr(settings->FindString("server")); // first see if we can parse it as a numeric address + if ((hostIP == 0)||(hostIP == (uint32)-1)) { + struct hostent * he = gethostbyname(settings->FindString("server")); + hostIP = he ? *((uint32*)he->h_addr) : 0; + } + + if (hostIP == 0) { + BString error; + error << "Could not connect to IMAP server " << settings->FindString("server"); + if ((port != 143) && (port != 993)) + error << ":" << port; + error << ": Host not found."; + runner->ShowError(error.String()); + net = -1; + runner->Stop(); + return; + } + + net = socket(AF_INET, SOCK_STREAM, 0); + if (net >= 0) { + struct sockaddr_in saAddr; + memset(&saAddr, 0, sizeof(saAddr)); + saAddr.sin_family = AF_INET; + saAddr.sin_port = htons(port); + saAddr.sin_addr.s_addr = hostIP; + int result = connect(net, (struct sockaddr *) &saAddr, sizeof(saAddr)); + if (result < 0) { +#ifdef BONE + close(net); +#else + closesocket(net); +#endif + net = -1; + BString error; + error << "Could not connect to IMAP server " << settings->FindString("server"); + if ((port != 143) && (port != 993)) + error << ":" << port; + error << '.'; + runner->ShowError(error.String()); + runner->Stop(); + return; + } + } else { + BString error; + error << "Could not connect to IMAP server " << settings->FindString("server"); + if ((port != 143) && (port != 993)) + error << ":" << port; + error << ". (" << strerror(errno) << ')'; + runner->ShowError(error.String()); + net = -1; + runner->Stop(); + return; + } + +#ifdef IMAPSSL + if (use_ssl) { + SSL_library_init(); + SSL_load_error_strings(); + RAND_seed(this,sizeof(IMAP4Client)); + /*--- Because we're an add-on loaded at an unpredictable time, all + the memory addresses and things contained in ourself are + esssentially random. */ + + ctx = SSL_CTX_new(SSLv23_method()); + ssl = SSL_new(ctx); + sbio=BIO_new_socket(net,BIO_NOCLOSE); + SSL_set_bio(ssl,sbio,sbio); + + if (SSL_connect(ssl) <= 0) { + BString error; + error << "Could not connect to IMAP server " << settings->FindString("server"); + if (port != 993) + error << ":" << port; + error << ". (SSL Connection Error)"; + runner->ShowError(error.String()); + SSL_CTX_free(ctx); + #ifdef BONE + close(net); + #else + closesocket(net); + #endif + runner->Stop(); + return; + } + } + + #endif + +//-----Wait for welcome message + BString response; + ReceiveLine(response); + +//-----Log in + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Authenticating...","認証中...")); + + const char *password = settings->FindString("password"); + { + char *passwd = get_passwd(settings,"cpasswd"); + if (passwd) + password = passwd; + } + + BString command = "LOGIN "; + command << "\"" << settings->FindString("username") << "\" "; + command << "\"" << password << "\""; + SendCommand(command.String()); + if (!WasCommandOkay(response)) { + response.Prepend("Login failed. Please check your username and password.\n("); + response << ')'; + runner->ShowError(response.String()); + err = B_ERROR; + runner->Stop(); + return; + } + + runner->ReportProgress(0,0,"Logged in"); + + InitializeMailboxes(); + GetUniqueIDs(); + + BStringList to_dl; + unique_ids->NotThere(*manifest,&to_dl); + + noop = new NoopWorker(this); + runner->AddHandler(noop); + nooprunner = new BMessageRunner(BMessenger(noop,runner),new BMessage('impn'),10e6); + + if (to_dl.CountItems() > 0) + runner->GetMessages(&to_dl,-1); +} + +IMAP4Client::~IMAP4Client() { + if (selected_mb != "") + SendCommand("CLOSE"); + SendCommand("LOGOUT"); + + for (int32 i = 0; i < box_info.CountItems(); i++) + delete (struct mailbox_info *)(box_info.ItemAt(i)); + + delete noop; + +#ifdef IMAPSSL + if (use_ssl) { + SSL_shutdown(ssl); + SSL_CTX_free(ctx); + } +#endif + +#ifdef BONE + close(net); +#else + closesocket(net); +#endif +} + +void IMAP4Client::InitializeMailboxes() { + BString command; + command << "LSUB \"" << mb_root << "\" \"*\""; + + SendCommand(command.String()); + + BString tag; + char expected[255]; + ::sprintf(expected,"a%.7ld",commandCount); + create_directory(runner->Chain()->MetaData()->FindString("path"),0777); + const char *path = runner->Chain()->MetaData()->FindString("path"); + int val; + do { + NestedString response; + val = GetResponse(tag,&response); + if (val != NOT_COMMAND_RESPONSE) + break; + + if (tag == expected) + break; + + if (response[3]()[0] != '.') { + struct mailbox_info *info = new struct mailbox_info; + info->exists = -1; + info->next_uid = -1; + info->server_mb_name = response[3](); + box_info.AddItem(info); + BString parsed_name = response[3](); + if ((mb_root != "") && (strncmp(mb_root.String(),parsed_name.String(),mb_root.Length()) == 0)) + parsed_name.Remove(0,mb_root.Length()); + + if (strcasecmp(response[2](),"NIL")) { + hierarchy_delimiter = response[2](); + if (strcmp(response[2](),"/")) { + if (strcmp(response[2](),"\\")) + parsed_name.ReplaceAll('/','\\'); + else + parsed_name.ReplaceAll('/','-'); + + parsed_name.ReplaceAll(response[2](),"/"); + } + } + if (parsed_name.ByteAt(0) == '/') + parsed_name.Remove(0,1); + + mailboxes += parsed_name.String(); + if (strcasecmp(parsed_name.String(),"INBOX") == 0) + inbox_name = parsed_name; + + BPath blorp(path); + blorp.Append(parsed_name.String()); + create_directory(blorp.Path(),0777); + } + } while (1); + + + if (hierarchy_delimiter == "" || hierarchy_delimiter == "NIL") { + SendCommand("LIST \"\" \"\""); + NestedString dem; + GetResponse(tag,&dem); + hierarchy_delimiter = dem[2](); + if (hierarchy_delimiter == "" || hierarchy_delimiter == "NIL") + hierarchy_delimiter = "/"; + } + + if (mb_root.ByteAt(mb_root.Length() - 1) != hierarchy_delimiter.ByteAt(0)) { + command = "SELECT "; + command << mb_root; + SendCommand(command.String()); + if (WasCommandOkay(command)) { + struct mailbox_info *info = new struct mailbox_info; + info->exists = -1; + info->next_uid = -1; + info->server_mb_name = mb_root; + + mailboxes += ""; + box_info.AddItem(info); + + if (strcasecmp(mb_root.String(),"INBOX") == 0) + inbox_name = ""; + SendCommand("CLOSE"); + } + } +} + +#define dump_stringlist(a) printf("BStringList %s:\n",#a); \ + for (int32 i = 0; i < a->CountItems(); i++)\ + puts(a->ItemAt(i)); \ + puts("Done\n"); + +status_t IMAP4Client::AddMessage(const char *mailbox, BPositionIO *data, BString *id) { + Select(mailbox); //---Update info + + const int32 box_index = mailboxes.IndexOf(mailbox); + char expected[255]; + BString tag; + + BString command = "APPEND \""; + off_t size; + data->Seek(0,SEEK_END); + size = data->Position(); + + BString attributes = "\\Seen"; + + { + BNode *node = dynamic_cast(data); + + if (node != NULL) { + BString status; + node->ReadAttrString(B_MAIL_ATTR_STATUS,&status); + /*if (status == "Sent") + attributes += " \\Sent"; + if (status == "Pending") + attributes += " \\Sent";*/ + if (status == "Replied") + attributes += " \\Answered"; + } + } + + + command << ((struct mailbox_info *)(box_info.ItemAt(box_index)))->server_mb_name << "\" (" << attributes << ") {" << size << '}'; + SendCommand(command.String()); + status_t err = ReceiveLine(command); + if (err < B_OK) + return err; + + char *buffer = new char[size]; + data->ReadAt(0,buffer,size); +#ifdef IMAPSSL + if (use_ssl) { + SSL_write(ssl,buffer,size); + SSL_write(ssl,"\r\n",2); + } else +#endif + { + send(net,buffer,size,0); + send(net,"\r\n",2,0); + } + Select(mailbox,false,false,false,true); + + if (((struct mailbox_info *)(box_info.ItemAt(box_index)))->next_uid <= 0) { + command = "FETCH "; + command << ((struct mailbox_info *)(box_info.ItemAt(box_index)))->exists << " UID"; + + SendCommand(command.String()); + ::sprintf(expected,"a%.7ld",commandCount); + *id = ""; + while (1) { + NestedString response; + GetResponse(tag,&response); + + if (tag == expected) + break; + + *id = response[2][1](); + } + } else { + *id = ""; + *id << (((struct mailbox_info *)(box_info.ItemAt(box_index)))->next_uid - 1); + } + + return B_OK; +} + +status_t IMAP4Client::DeleteMessage(const char *mailbox, const char *message) { + BString command = "UID STORE "; + command << message << " +FLAGS.SILENT (\\Deleted)"; + + if (Select(mailbox,false,true,true,false,true) < B_OK) + return B_ERROR; + + SendCommand(command.String()); + if (!WasCommandOkay(command)) { + command.Prepend("Error while deleting message: "); + runner->ShowError(command.String()); + return B_ERROR; + } + + force_reselect = true; + + return B_OK; +} + +status_t IMAP4Client::CopyMessage(const char *mailbox, const char *to_mailbox, BString *message) { + struct mailbox_info *to_mb = (struct mailbox_info *)(box_info.ItemAt(mailboxes.IndexOf(to_mailbox))); + char expected[255]; + BString tag; + + Select(mailbox); + + BString command = "UID COPY "; + command << *message << " \"" << to_mb->server_mb_name << '\"'; + SendCommand(command.String()); + if (!WasCommandOkay(command)) + return B_ERROR; + + Select(to_mailbox,false,false,true); //---Update mailbox info + + if (to_mb->next_uid <= 0) { + command = "FETCH "; + command << to_mb->exists << " UID"; + + SendCommand(command.String()); + ::sprintf(expected,"a%.7ld",commandCount); + *message = ""; + while (1) { + NestedString response; + GetResponse(tag,&response); + + if (tag == expected) + break; + + *message = response[2][1](); + } + } else { + *message = ""; + *message << (to_mb->next_uid - 1); + } + + return B_OK; +} + +status_t IMAP4Client::CreateMailbox(const char *mailbox) { + Close(); + + struct mailbox_info *info = new struct mailbox_info; + info->exists = -1; + info->next_uid = -1; + info->server_mb_name = mailbox; + info->server_mb_name.ReplaceAll("/",hierarchy_delimiter.String()); + if ((mb_root.ByteAt(mb_root.Length() - 1) != hierarchy_delimiter.ByteAt(0)) && (mb_root.Length() > 0)) + info->server_mb_name.Prepend(hierarchy_delimiter); + + info->server_mb_name.Prepend(mb_root.String()); + + BString command; + command << "CREATE \"" << info->server_mb_name << '\"'; + SendCommand(command.String()); + BString response; + WasCommandOkay(response); + //--- Deliberately ignore errors in the case of extant, but unsubscribed, mailboxes + + command = "SUBSCRIBE \""; + command << info->server_mb_name << '\"'; + SendCommand(command.String()); + + if (!WasCommandOkay(response)) { + command = "Error creating mailbox "; + command << mailbox << ". The server said: \n" << response << "\nThis may mean you can't create a new mailbox in this location."; + runner->ShowError(command.String()); + delete info; + return B_ERROR; + } + + box_info.AddItem(info); + + return B_OK; +} + +status_t IMAP4Client::DeleteMailbox(const char *mailbox) { + Close(); + + if (!mailboxes.HasItem(mailbox)) + return B_ERROR; + + BString command; + + command = "UNSUBSCRIBE \""; + command << ((struct mailbox_info *)(box_info.ItemAt(mailboxes.IndexOf(mailbox))))->server_mb_name << '\"'; + SendCommand(command.String()); + WasCommandOkay(command); + //---If this fails, that's fine. + + command = "DELETE \""; + command << ((struct mailbox_info *)(box_info.ItemAt(mailboxes.IndexOf(mailbox))))->server_mb_name << '\"'; + + SendCommand(command.String()); + if (!WasCommandOkay(command)) { + command = "Error deleting mailbox "; + command << mailbox << '.'; + runner->ShowError(command.String()); + + return B_ERROR; + } + + delete ((struct mailbox_info *)(box_info.RemoveItem(mailboxes.IndexOf(mailbox)))); + + return B_OK; +} + +void IMAP4Client::GetUniqueIDs() { + BString command; + char expected[255]; + BString tag; + BString uid; + struct mailbox_info *info; + + runner->ReportProgress(0,0,"Getting Unique IDs"); + + for (int32 i = 0; i < mailboxes.CountItems(); i++) { + Select(mailboxes[i],true,false /* We queue them as a group */); + + info = (struct mailbox_info *)(box_info.ItemAt(i)); + if (info->exists <= 0) + continue; + + command = "FETCH 1:"; + command << info->exists << " UID"; + SendCommand(command.String()); + + ::sprintf(expected,"a%.7ld",commandCount); + while(1) { + NestedString response; + GetResponse(tag,&response); + + if (tag == expected) + break; + + uid = mailboxes[i]; + uid << '/' << response[2][1](); + unique_ids->AddItem(uid.String()); + } + } +} + +status_t IMAP4Client::Close() { + if (selected_mb != "") { + BString worthless; + SendCommand("CLOSE"); + if (!WasCommandOkay(worthless)) + return B_ERROR; + + selected_mb = ""; + } + + return B_OK; +} + +status_t IMAP4Client::Select(const char *mb, bool reselect, bool queue_new_messages, bool noop, bool no_command, bool ignore_forced_reselect) { + if (force_reselect && !ignore_forced_reselect) { + reselect = true; + force_reselect = false; + } + + if (reselect) + Close(); + + struct mailbox_info *info = (struct mailbox_info *)(box_info.ItemAt(mailboxes.IndexOf(mb))); + if (info == NULL) + return B_NAME_NOT_FOUND; + + const char *real_mb = info->server_mb_name.String(); + + if ((selected_mb != real_mb) || (noop) || (no_command)) { + if ((selected_mb != "") && (selected_mb != real_mb)){ + BString trash; + if (SendCommand("CLOSE") < B_OK) + return B_ERROR; + selected_mb = ""; + WasCommandOkay(trash); + } + BString cmd; + if (selected_mb == real_mb) + cmd = "NOOP"; + else + cmd << "SELECT \"" << real_mb << '\"'; + + if (!no_command) + if (SendCommand(cmd.String()) < B_OK) + return B_ERROR; + + char expected[255]; + BString tag; + ::sprintf(expected,"a%.7ld",commandCount); + + int32 new_exists(-1), new_next_uid(-1), recent(-1); + + while(1) { + NestedString response; + if (GetResponse(tag,&response) < B_OK) + return B_ERROR; + + if (tag == expected) + break; + + if ((response.CountItems() > 1) && (strcasecmp(response[1](),"EXISTS") == 0)) + new_exists = atoi(response[0]()); + + if (response[0].CountItems() == 2 && strcasecmp(response[0][0](),"UIDNEXT") == 0) + new_next_uid = atol(response[0][1]()); + + if ((response.CountItems() > 1) && (strcasecmp(response[1](),"RECENT") == 0)) + recent = atoi(response[0]()); + } + + if ((queue_new_messages) && (recent > 0)) { + BString command = "FETCH "; + command << new_exists - recent + 1 << ':' << new_exists << " UID"; + SendCommand(command.String()); + ::sprintf(expected,"a%.7ld",commandCount); + BStringList list; + BString uid; + while(1) { + NestedString response; + if (GetResponse(tag,&response) < 0) + return B_ERROR; + + if (tag == expected) + break; + + if (strcmp(response[2][0](),"UID") != 0) + continue; //--- Courier IMAP blows. Hard. + + uid = mb; + uid << '/' << response[2][1](); + if (!unique_ids->HasItem(uid.String())) + list.AddItem(uid.String()); + } + + if (list.CountItems() > 0) { + (*unique_ids) += list; + runner->GetMessages(&list,-1); + } + } + + info->exists = new_exists; + info->next_uid = new_next_uid; + + selected_mb = real_mb; + } + + return B_OK; +} + +class IMAP4PartialReader : public BPositionIO { + public: + IMAP4PartialReader(IMAP4Client *client,BPositionIO *_slave,const char *id) : us(client), slave(_slave), done(false) { + strcpy(unique,id); + } + ~IMAP4PartialReader() { + delete slave; + us->runner->ReportProgress(0,1); + } + off_t Seek(off_t position, uint32 seek_mode) { + if (seek_mode == SEEK_END) { + if (!done) { + slave->Seek(0,SEEK_END); + FetchMessage("RFC822.TEXT"); + } + done = true; + } + return slave->Seek(position,seek_mode); + } + off_t Position() const { + return slave->Position(); + } + ssize_t WriteAt(off_t pos, const void *buffer, size_t amountToWrite) { + return slave->WriteAt(pos,buffer,amountToWrite); + } + ssize_t ReadAt(off_t pos, void *buffer, size_t amountToWrite) { + ssize_t bytes; + while ((bytes = slave->ReadAt(pos,buffer,amountToWrite)) < amountToWrite && !done) { + slave->Seek(0,SEEK_END); + FetchMessage("RFC822.TEXT"); + done = true; + } + return bytes; + } + private: + void FetchMessage(const char *part) { + BString command = "UID FETCH "; + command << unique << " (" << part << ')'; + us->SendCommand(command.String()); + static char cmd[255]; + ::sprintf(cmd,"a%.7ld"CRLF,us->commandCount); + NestedString response; + if (us->GetResponse(command,&response) != NOT_COMMAND_RESPONSE && command == cmd) + return; + + //response.PrintToStream(); + + us->WasCommandOkay(command); + for (int32 i = 0; (i+1) < response[2].CountItems(); i++) { + if (strcmp(response[2][i](),part) == 0) { + slave->Write(response[2][i+1](),strlen(response[2][i+1]())); + break; + } + } + + } + + IMAP4Client *us; + char unique[25]; + BPositionIO *slave; + bool done; +}; + +status_t IMAP4Client::GetMessage(const char *mailbox, const char *message, BPositionIO **data, BMessage *headers) { + { + //--- Error reporting for non-existant messages often simply doesn't exist. So we have to check first... + BString uid = mailbox; + uid << '/' << message; + if (!unique_ids->HasItem(uid.String())) { + uid.Prepend("This message ("); + uid.Append(") does not exist on the server. Possibly it was deleted by another client."); + runner->ShowError(uid.String()); + return B_NAME_NOT_FOUND; + } + } + + Select(mailbox); + + if (headers->FindBool("ENTIRE_MESSAGE")) { + BString command = "UID FETCH "; + command << message << " (FLAGS RFC822)"; + + SendCommand(command.String()); + static char cmd[255]; + ::sprintf(cmd,"a%.7ld"CRLF,commandCount); + NestedString response; + + if (GetResponse(command,&response,true) != NOT_COMMAND_RESPONSE && command == cmd) + return B_ERROR; + + for (int32 i = 0; i < response[2][1].CountItems(); i++) { + if (strcmp(response[2][1][i](),"\\Seen") == 0) { + headers->AddString("STATUS","Read"); + } + if (strcmp(response[2][1][i](),"\\Sent") == 0) { + if (headers->HasString("STATUS")) + headers->ReplaceString("STATUS","Sent"); + else + headers->AddString("STATUS","Sent"); + } + if (strcmp(response[2][1][i](),"\\Answered") == 0) { + if (headers->HasString("STATUS")) + headers->ReplaceString("STATUS","Replied"); + else + headers->AddString("STATUS","Replied"); + } + } + + WasCommandOkay(command); + (*data)->WriteAt(0,response[2][5](),strlen(response[2][5]())); + runner->ReportProgress(0,1); + return B_OK; + } else { + BString command = "UID FETCH "; + command << message << " (RFC822.SIZE FLAGS RFC822.HEADER)"; + SendCommand(command.String()); + static char cmd[255]; + ::sprintf(cmd,"a%.7ld"CRLF,commandCount); + NestedString response; + if (GetResponse(command,&response) != NOT_COMMAND_RESPONSE && command == cmd) + return B_ERROR; + + WasCommandOkay(command); + + for (int32 i = 0; i < response[2].CountItems(); i++) { + if (strcmp(response[2][i](),"RFC822.SIZE") == 0) { + i++; + headers->AddInt32("SIZE",atoi(response[2][i]())); + } else if (strcmp(response[2][i](),"FLAGS") == 0) { + i++; + for (int32 j = 0; j < response[2][i].CountItems(); j++) { + if (strcmp(response[2][i][j](),"\\Seen") == 0) + headers->AddString("STATUS","Read"); + else if (strcmp(response[2][i][j](),"\\Answered") == 0) { + if (headers->ReplaceString("STATUS","Replied") != B_OK) + headers->AddString("STATUS","Replied"); + } + } + } else if (strcmp(response[2][i](),"RFC822.HEADER") == 0) { + i++; + (*data)->Write(response[2][i](),strlen(response[2][i]())); + } + } + + *data = new IMAP4PartialReader(this,*data,message); + return B_OK; + } +} + +status_t +IMAP4Client::SendCommand(const char* command) +{ + if (net < 0) + return B_ERROR; + + static char cmd[255]; + ::sprintf(cmd,"a%.7ld %s"CRLF,++commandCount,command); +#ifdef IMAPSSL + if (use_ssl) + SSL_write(ssl,cmd,strlen(cmd)); + else +#endif + send(net,cmd,strlen(cmd),0); + + PRINT(("C: %s",cmd)); + + return B_OK; +} + +int32 +IMAP4Client::ReceiveLine(BString &out) +{ + if (net < 0) + return net; + + uint8 c = 0; + int32 len = 0,r; + out = ""; + + struct timeval tv; + struct fd_set fds; + + tv.tv_sec = long(kIMAP4ClientTimeout / 1e6); + tv.tv_usec = long(kIMAP4ClientTimeout-(tv.tv_sec * 1e6)); + + /* Initialize (clear) the socket mask. */ + FD_ZERO(&fds); + + /* Set the socket in the mask. */ + FD_SET(net, &fds); + int result; +#ifdef IMAPSSL + if ((use_ssl) && (SSL_pending(ssl))) + result = 1; + else +#endif + result = select(32, &fds, NULL, NULL, &tv); + + if (result < 0) + return errno; + + if(result > 0) + { + while(c != '\n' && c != xEOF) + { + #ifdef IMAPSSL + if (use_ssl) + r = SSL_read(ssl,&c,1); + else + #endif + r = recv(net,&c,1,0); + if(r <= 0) { + BString error; + error << "Connection to " << settings->FindString("server") << " lost."; + net = -1; + runner->Stop(); + runner->ShowError(error.String()); + return -1; + } + + out += (char)c; + len += r; + } + }else{ + // Log an error somewhere instead + runner->ShowError("IMAP Timeout."); + } + PRINT(("S:%s\n",out.String())); + return len; +} + +int IMAP4Client::GetResponse(BString &tag, NestedString *parsed_response, bool report_literals, bool internal_flag) { + if (net < 0) + return net; + + uint8 c = 0; + int32 r; + int8 delimiters_passed = internal_flag ? 2 : 0; + int answer = NOT_COMMAND_RESPONSE; + BString out; + bool in_quote = false; + bool was_cr = false; + int result; + + { + struct timeval tv; + struct fd_set fds; + + tv.tv_sec = long(kIMAP4ClientTimeout / 1e6); + tv.tv_usec = long(kIMAP4ClientTimeout-(tv.tv_sec * 1e6)); + + /* Initialize (clear) the socket mask. */ + FD_ZERO(&fds); + + /* Set the socket in the mask. */ + FD_SET(net, &fds); +#ifdef IMAPSSL + if ((use_ssl) && (SSL_pending(ssl))) + result = 1; + else +#endif + result = select(32, &fds, NULL, NULL, &tv); + } + + if (result < 0) + return errno; + + if (!internal_flag) + PRINT(("S: ")); + + if(result > 0) + { + while(c != '\n' && c != xEOF) + { +#ifdef IMAPSSL + if (use_ssl) + r = SSL_read(ssl,&c,1); + else +#endif + r = recv(net,&c,1,0); + if(r <= 0) { + BString error; + error << "Connection to " << settings->FindString("server") << " lost."; + net = -1; + runner->Stop(); + runner->ShowError(error.String()); + return -1; + } + + #if DEBUG + putchar(c); + #endif + + if ((isspace(c) || (internal_flag && (c == ')' || c == ']'))) && !in_quote) { + if (delimiters_passed == 0) { + tag = out; + out = ""; + delimiters_passed ++; + continue; + } + if (delimiters_passed == 1) { + + if (out == "NO") + answer = NO; + else if (out == "BAD") + answer = BAD; + else if (out == "OK") + answer = OK; + else if (parsed_response != NULL && out != "") + *parsed_response += out; + + out = ""; + delimiters_passed ++; + continue; + } + + if (c == '\r') { + was_cr = true; + continue; + } + + if (c == '\n' && was_cr) { + if (out.Length() == 0) + return answer; + if (out[0] == '{' && out[out.Length() - 1] == '}') { + int octets_to_read; + out.Truncate(out.Length() - 1); + octets_to_read = atoi(out.String() + 1); + out = ""; + char *buffer = new char[octets_to_read+1]; + buffer[octets_to_read] = 0; + int read_octets = 0; + int nibble_size; + while (read_octets < octets_to_read) { + #ifdef IMAPSSL + if (use_ssl) + nibble_size = SSL_read(ssl,buffer + read_octets,octets_to_read - read_octets); + else + #endif + nibble_size = recv(net,buffer + read_octets,octets_to_read - read_octets,0); + read_octets += nibble_size; + if (report_literals) + runner->ReportProgress(nibble_size,0); + } + + if (parsed_response != NULL) + parsed_response->AdoptAndAdd(buffer); + else + delete [] buffer; + + c = ' '; + continue; + } + } + + if (internal_flag && (c == ')' || c == ']')) { + if (parsed_response != NULL && out != "") + (*parsed_response) += out; + break; + } + + was_cr = false; + if (parsed_response != NULL && out != "") + (*parsed_response) += out; + out = ""; + continue; + } + + was_cr = false; + + if (c == '\"') { + in_quote = !in_quote; + continue; + } + if (c == '(' || c == '[') { + if (parsed_response != NULL) + (*parsed_response) += NULL; + + BString trash; + GetResponse(trash,&((*parsed_response)[parsed_response->CountItems() - 1]),report_literals,true); + continue; + } + + out += (char)c; + } + }else{ + // Log an error somewhere instead + runner->ShowError("IMAP Timeout."); + } + return answer; +} + +bool IMAP4Client::WasCommandOkay(BString &response) { + do { + response = ""; + if (ReceiveLine(response) < B_OK) { + runner->ShowError("No response from server"); + return false; + } + } while (response[0] == '*'); + + bool to_ret = false; + static char cmd[255]; + ::sprintf(cmd,"a%.7ld OK",commandCount); + if (strncmp(response.String(),cmd,strlen(cmd)) == 0) + to_ret = true; + + int32 i = response.FindFirst(' '); + i = response.FindFirst(' ',i+1); + response.Remove(0,i+1); + response.ReplaceAll("\r\n","\n"); + for (int32 i = response.Length()-1; response.String()[i] == '\n'; i--) + response.Truncate(i); + + return to_ret; +} + +BMailFilter *instantiate_mailfilter(BMessage *settings, BMailChainRunner *runner) +{ + return new IMAP4Client(settings,runner); +} diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_config.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_config.cpp new file mode 100644 index 0000000000..a7def77b02 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_config.cpp @@ -0,0 +1,79 @@ +/* IMAPConfig - config view for the IMAP protocol add-on +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +#include +#include + +#include + +class IMAPConfig : public BMailProtocolConfigView { + public: + IMAPConfig(BMessage *archive); + virtual ~IMAPConfig(); + virtual status_t Archive(BMessage *into, bool deep = true) const; + virtual void GetPreferredSize(float *width, float *height); +}; + +IMAPConfig::IMAPConfig(BMessage *archive) + : BMailProtocolConfigView(B_MAIL_PROTOCOL_HAS_USERNAME | B_MAIL_PROTOCOL_HAS_PASSWORD | B_MAIL_PROTOCOL_HAS_HOSTNAME | B_MAIL_PROTOCOL_CAN_LEAVE_MAIL_ON_SERVER + #ifdef IMAPSSL + | B_MAIL_PROTOCOL_HAS_FLAVORS) +{ + AddFlavor("Unencrypted"); + AddFlavor("IMAP-SSL"); + #else + ) { + #endif + + SetTo(archive); + + ((BControl *)(FindView("leave_mail_remote")))->SetValue(B_CONTROL_ON); + ((BControl *)(FindView("leave_mail_remote")))->Hide(); + + BRect frame = FindView("delete_remote_when_local")->Frame(); + + ((BControl *)(FindView("delete_remote_when_local")))->SetEnabled(true); + ((BControl *)(FindView("delete_remote_when_local")))->MoveBy(0,-25); + + + frame.right -= 10;// FindView("pass")->Frame().right; + /*frame.top += 10; + frame.bottom += 10;*/ + + BTextControl *folder = new BTextControl(frame,"root","Mailbox Root: ","",NULL); + folder->SetDivider(be_plain_font->StringWidth("Mailbox Root: ")); + + if (archive->HasString("root")) + folder->SetText(archive->FindString("root")); + + AddChild(folder); + + ResizeToPreferred(); +} + +IMAPConfig::~IMAPConfig() {} + +status_t IMAPConfig::Archive(BMessage *into, bool deep) const { + BMailProtocolConfigView::Archive(into,deep); + + if (into->ReplaceString("root",((BTextControl *)(FindView("root")))->Text()) != B_OK) + into->AddString("root",((BTextControl *)(FindView("root")))->Text()); + + into->PrintToStream(); + + return B_OK; +} + +void IMAPConfig::GetPreferredSize(float *width, float *height) { + BMailProtocolConfigView::GetPreferredSize(width,height); + *height -= 20; +} + +BView* instantiate_config_panel(BMessage *settings,BMessage *) { + return new IMAPConfig(settings); +} diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/Jamfile b/src/add-ons/mail_daemon/inbound_protocols/pop3/Jamfile new file mode 100644 index 0000000000..2a83002b45 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/Jamfile @@ -0,0 +1,15 @@ +SubDir OBOS_TOP src add-ons mail_daemon inbound_protocols pop3 ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +SubDirC++Flags -DBONE ; + +Addon POP3 : mail_daemon inbound_protocols : + MessageIO.cpp + pop3.cpp + SimpleMailProtocol.cpp + md5c.c ; + +LinkSharedOSLibs POP3 : + be mail socket bind ; diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/MessageIO.cpp b/src/add-ons/mail_daemon/inbound_protocols/pop3/MessageIO.cpp new file mode 100644 index 0000000000..72a519259a --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/MessageIO.cpp @@ -0,0 +1,138 @@ +/* BMailMessageIO - Glue code for reading/writing messages directly from the +** protocols but present a BPositionIO interface to the caller, while caching +** the data read/written in a slave file. +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +#include +#include +#include + +#include "MessageIO.h" +#include "SimpleMailProtocol.h" + +BMailMessageIO::BMailMessageIO(SimpleMailProtocol *protocol, BPositionIO *dump_to, int32 seq_id) : + slave(dump_to), + message_id(seq_id), + network(protocol), + size(0), + state(READ_HEADER_NEXT) { + //-----do nothing, and do it well----- + } + + +ssize_t BMailMessageIO::ReadAt(off_t pos, void *buffer, size_t amountToRead) { + status_t errorCode; + char lastBytes [5]; + off_t old_pos = slave->Position(); + + while ((pos + amountToRead) > size) { + if (state >= ALL_READING_DONE) + break; + switch (state) { + // Read (download from the mail server) just the message headers, + // and append a blank line if needed (so the header processing code + // can tell where the end of the headers is). Don't append too + // much otherwise the part after the header will appear mangled + // when it is overwritten in a full read. This can be useful for + // filters which discard the message after only reading the header, + // thus avoiding the time it takes to download the whole message. + case READ_HEADER_NEXT: + slave->SetSize(0); // Truncate the file. + slave->Seek(0,SEEK_SET); + errorCode = network->GetHeader(message_id,slave); + if (errorCode < 0) + return errorCode; + // See if it already ends in a blank line, if not, add enough + // end-of-lines to give a blank line. + slave->Seek (-4, SEEK_END); + strcpy (lastBytes, "xxxx"); + slave->Read (lastBytes, 4); + if (strcmp (lastBytes, "\r\n\r\n") != 0) { + if (strcmp (lastBytes + 2, "\r\n") == 0) + slave->Write("\r\n", 2); + else + slave->Write("\r\n\r\n", 4); + } + state = READ_BODY_NEXT; + break; + + // OK, they want more than the headers. Read the whole message, + // starting from the beginning (network->Retrieve does a seek to + // the start of the file for POP3 so we have to read the whole + // thing). This wastes a slight amount of time on high speed + // connections, and on dial-up modem connections, hopefully the + // modem's V.90 data compression will make it very quick to + // retransmit the header portion. + case READ_BODY_NEXT: + slave->SetSize(0); // Truncate the file. + slave->Seek(0,SEEK_SET); + errorCode = network->Retrieve(message_id,slave); + if (errorCode < 0) + return errorCode; + state = ALL_READING_DONE; + break; + + default: // Shouldn't happen. + return -1; + } + ResetSize(); + } + + // Put the file position back at where it was, if possible. That's because + // ReadAt isn't supposed to affect the file position. + if (old_pos < size) + slave->Seek (old_pos, SEEK_SET); + else + slave->Seek (0, SEEK_END); + + return slave->ReadAt(pos,buffer,amountToRead); +} + + +ssize_t BMailMessageIO::WriteAt(off_t pos, const void *buffer, size_t amountToWrite) { + ssize_t return_val; + + return_val = slave->WriteAt(pos,buffer,amountToWrite); + ResetSize(); + + return return_val; +} + +off_t BMailMessageIO::Seek(off_t position, uint32 seek_mode) { + ssize_t errorCode; + char tempBuffer [1]; + + if (seek_mode == SEEK_END) { + if (state != ALL_READING_DONE) { + // Force it to read the whole message to find the size of it. + state = READ_BODY_NEXT; // Skip the header reading step. + errorCode = ReadAt (size + 1, tempBuffer, sizeof (tempBuffer)); + if (errorCode < 0) + return errorCode; + } + } + return slave->Seek(position,seek_mode); +} + +off_t BMailMessageIO::Position() const { + return slave->Position(); +} + +void BMailMessageIO::ResetSize(void) { + off_t old = slave->Position(); + + slave->Seek(0,SEEK_END); + size = slave->Position(); + + slave->Seek(old,SEEK_SET); +} + +BMailMessageIO::~BMailMessageIO() { + delete slave; +} + diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/MessageIO.h b/src/add-ons/mail_daemon/inbound_protocols/pop3/MessageIO.h new file mode 100644 index 0000000000..e5d135d596 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/MessageIO.h @@ -0,0 +1,44 @@ +#ifndef ZOIDBERG_MAIL_MESSAGE_IO_H +#define ZOIDBERG_MAIL_MESSAGE_IO_H +/* MessageIO - Glue code for reading/writing messages directly from the +** protocols but present a BPositionIO interface to the caller, while caching +** the data read/written in a slave file. +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include + +class SimpleMailProtocol; + +class BMailMessageIO : public BPositionIO { + public: + BMailMessageIO(SimpleMailProtocol *protocol, BPositionIO *dump_to, int32 seq_id); + ~BMailMessageIO(); + + //----BPositionIO + virtual ssize_t ReadAt(off_t pos, void *buffer, size_t amountToRead); + virtual ssize_t WriteAt(off_t pos, const void *buffer, size_t amountToWrite); + + virtual off_t Seek(off_t position, uint32 seek_mode); + virtual off_t Position() const; + + private: + void ResetSize(void); + + BPositionIO *slave; + int32 message_id; + SimpleMailProtocol *network; + + size_t size; + enum MessageIOStateEnum { + READ_HEADER_NEXT, + READ_BODY_NEXT, + ALL_READING_DONE + } state; +}; + +#endif /* ZOIDBERG_MAIL_MESSAGE_IO_H */ diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/SimpleMailProtocol.cpp b/src/add-ons/mail_daemon/inbound_protocols/pop3/SimpleMailProtocol.cpp new file mode 100644 index 0000000000..0fb090b904 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/SimpleMailProtocol.cpp @@ -0,0 +1,125 @@ +/* SimpleMailProtocol - the base protocol implementation +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "SimpleMailProtocol.h" +#include "MessageIO.h" + +SimpleMailProtocol::SimpleMailProtocol(BMessage *settings, BMailChainRunner *run) : + BMailProtocol(settings,run), + error(B_OK), + last_message(-1) +{ +} + + +status_t +SimpleMailProtocol::Init() +{ + error = Open(settings->FindString("server"), settings->FindInt32("port"), + settings->FindInt32("flavor")); + if (error < B_OK) { + runner->Stop(); + return error; + } + + const char *password = settings->FindString("password"); + char *passwd = get_passwd(settings, "cpasswd"); + if (passwd) + password = passwd; + + error = Login(settings->FindString("username"), password, settings->FindInt32("auth_method")); + delete passwd; + + if (error < B_OK) { + runner->Stop(); + return error; + } + + if (settings->FindBool("login_and_do_nothing_else_of_any_importance")) + return error; + + error = UniqueIDs(); + if (error < B_OK) { + runner->Stop(); + return error; + } + + size_t maildrop_size = 0; + int32 num_messages; + + BStringList to_dl; + manifest->NotHere(*unique_ids, &to_dl); + + num_messages = to_dl.CountItems(); + if (num_messages == 0) { + runner->Stop(); + return error; + } + + for (int32 i = 0; i < to_dl.CountItems(); i++) + maildrop_size += MessageSize(unique_ids->IndexOf(to_dl[i])); + + runner->GetMessages(&to_dl, maildrop_size); + runner->Stop(); //---This gets queued + + return error; +} + + +SimpleMailProtocol::~SimpleMailProtocol() +{ +} + + +status_t +SimpleMailProtocol::GetMessage(const char *uid, BPositionIO **out_file, BMessage *out_headers, + BPath *out_folder_location) +{ + int32 to_retrieve = unique_ids->IndexOf(uid); + if (to_retrieve < 0) + return B_NAME_NOT_FOUND; + + out_headers->AddInt32("SIZE",MessageSize(to_retrieve)); + *out_file = new BMailMessageIO(this,*out_file,to_retrieve); + + if (out_folder_location != NULL) + out_folder_location->SetTo(""); + + if((*out_file)->ReadAt(0,&to_retrieve,1) < B_OK) + return B_MAIL_END_CHAIN; + + return B_OK; +} + + +status_t +SimpleMailProtocol::DeleteMessage(const char *uid) +{ +#if DEBUG + printf("ID is %d\n", (int)unique_ids->IndexOf(uid)); // What should we use for int32 instead of %d? +#endif + Delete(unique_ids->IndexOf(uid)); + return B_OK; +} + + +status_t +SimpleMailProtocol::InitCheck(BString* /*out_message*/) +{ + return error; +} diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/SimpleMailProtocol.h b/src/add-ons/mail_daemon/inbound_protocols/pop3/SimpleMailProtocol.h new file mode 100644 index 0000000000..9e2fd786a5 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/SimpleMailProtocol.h @@ -0,0 +1,76 @@ +#ifndef ZOIDBERG_MAIL_SIMPLEPROTOCOL_H +#define ZOIDBERG_MAIL_SIMPLEPROTOCOL_H + +#include + +class SimpleMailProtocol : public BMailProtocol { + public: + SimpleMailProtocol(BMessage *settings, BMailChainRunner *runner); + //---Constructor. Simply call this in yours, and most everything will be handled for you. + + virtual status_t Open(const char *server, int port, int protocol) = 0; + //---server is an ASCII representation of the server that you are logging in to + //---port is the remote port, -1 if you are to use default + //---protocol is the protocol to use (defined by your add-on and useful for using, say, SSL) -1 is default + + virtual status_t Login(const char *uid, const char *password, int method) = 0; + //---uid is the username provided + //---likewise password + //---method is the auth method to use, this works like protocol in Open + + virtual int32 Messages(void) = 0; + //---return the number of messages waiting + + virtual status_t GetHeader(int32 message, BPositionIO *write_to) = 0; + //---Retrieve the header of message into + + virtual status_t Retrieve(int32 message, BPositionIO *write_to) = 0; + //---get message number + //---write your message to + + virtual void Delete(int32 num) = 0; + //---delete message number num + + virtual size_t MessageSize(int32 index) = 0; + //---return the size in bytes of message number + + virtual size_t MailDropSize(void) = 0; + //---return the size of the entire maildrop in bytes + + virtual status_t UniqueIDs() = 0; + // Fill the protected member unique_ids with strings containing + // unique ids for all messages present on the server. This al- + // lows comparison of remote and local message manifests, so + // the local and remote contents can be kept in sync. + // + // The ID should be unique to this Chain; if that means + // this Protocol must add account/server info to differ- + // entiate it from other messages, then that info should + // be added before returning the IDs and stripped from the + // ID for use in GetMessage() et al, below. + // + // Returns B_OK if this was performed successfully, or another + // error if the connection has failed. + + //---------These implement hooks from up above--------- + //---------not user-servicable------------------------- + virtual status_t GetMessage( + const char* uid, + BPositionIO** out_file, BMessage* out_headers, + BPath* out_folder_location + ); + virtual status_t DeleteMessage(const char* uid); + virtual status_t InitCheck(BString* out_message = NULL); + virtual ~SimpleMailProtocol(); + + protected: + status_t Init(); + + private: + status_t error; + int32 last_message; + + uint32 _reserved[5]; +}; + +#endif // ZOIDBERG_MAIL_SIMPLEPROTOCOL_H diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/md5.h b/src/add-ons/mail_daemon/inbound_protocols/pop3/md5.h new file mode 100644 index 0000000000..53e35d4c7d --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/md5.h @@ -0,0 +1,56 @@ +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#ifndef MD5_H__ +#define MD5_H__ + +#include "md5global.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init PROTO_LIST ((MD5_CTX *)); +void MD5Update PROTO_LIST + ((MD5_CTX *, unsigned char *, unsigned int)); +void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); +void MD5Hmac(unsigned char *digest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len); +void MD5HexHmac(char *hexdigest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len); + +#ifdef __cplusplus +} +#endif +#endif /* MD5_H__ */ diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/md5c.c b/src/add-ons/mail_daemon/inbound_protocols/pop3/md5c.c new file mode 100644 index 0000000000..1a91ac1874 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/md5c.c @@ -0,0 +1,410 @@ +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +//#include "config.h" +#include "md5global.h" +#include "md5.h" +#include +#include +#include + +/* Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64])); +static void Encode PROTO_LIST + ((unsigned char *, UINT4 *, unsigned int)); +static void Decode PROTO_LIST + ((UINT4 *, unsigned char *, unsigned int)); +static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int)); +static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int)); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5Init (MD5_CTX *context) + /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void MD5Update (MD5_CTX *context, unsigned char *input, unsigned int inputLen) + /* context */ + /* input block */ + /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)&input[i], + inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void MD5Final (unsigned char digest[16], MD5_CTX *context) + /* message digest */ + /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (UINT4 state[4], unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (POINTER output, POINTER input, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (POINTER output, int value, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} + +/* +** Function: md5_hmac +** taken from the file rfc2104.txt +** written by Martin Schaaf +*/ +void +MD5Hmac(unsigned char *digest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len) +{ + MD5_CTX context; + unsigned char k_ipad[64]; /* inner padding - + * key XORd with ipad + */ + unsigned char k_opad[64]; /* outer padding - + * key XORd with opad + */ + /* unsigned char tk[16]; */ + int i; + + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + if (key_len > 64) { + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + MD5_CTX tctx; + + MD5Init(&tctx); + MD5Update(&tctx, (unsigned char*)key, key_len); + MD5Final(k_ipad, &tctx); + MD5Final(k_opad, &tctx); + } else { + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* + * perform inner MD5 + */ + MD5Init(&context); /* init context for 1st + * pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + MD5Update(&context, (unsigned char*)text, text_len); /* then text of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + MD5Init(&context); /* init context for 2nd + * pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st + * hash */ + MD5Final(digest, &context); /* finish up 2nd pass */ +} + + +void +MD5HexHmac(char *hexdigest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len) +{ + unsigned char digest[16]; + int i; + + MD5Hmac(digest, text, text_len, key, key_len); + for (i = 0; i < 16; i++) + sprintf(hexdigest + 2 * i, "%02x", digest[i]); +} diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/md5global.h b/src/add-ons/mail_daemon/inbound_protocols/pop3/md5global.h new file mode 100644 index 0000000000..14f6e44b39 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/md5global.h @@ -0,0 +1,41 @@ +/* + * For license terms, see the file COPYING in this directory. + * + * md5global.h Global declarations for MD5 module used by fetchmail + * + */ + +#ifndef MD5GLOBAL_H__ +#define MD5GLOBAL_H__ +/* GLOBAL.H - RSAREF types and constants + */ + +/* force prototypes on, we need ANSI C anyway */ +#ifndef PROTOTYPES +#define PROTOTYPES 1 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +#if SIZEOF_INT == 4 +typedef unsigned int UINT4; +#else +typedef unsigned long int UINT4; +#endif + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. +If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif + +#endif /* MD5GLOBAL_H__ */ diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/pop3.cpp b/src/add-ons/mail_daemon/inbound_protocols/pop3/pop3.cpp new file mode 100644 index 0000000000..0953695fbc --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/pop3.cpp @@ -0,0 +1,731 @@ +/* POP3Protocol - implementation of the POP3 protocol +** +** Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +#ifdef BONE + #include + #include +#else + #include +#endif + +#if POPSSL + #include + #include + #include +#else + #include "md5.h" +#endif + +#include "pop3.h" + +#define POP3_RETRIEVAL_TIMEOUT 60000000 +#define CRLF "\r\n" + +#define pop3_error(string) runner->ShowError(string) + +POP3Protocol::POP3Protocol(BMessage *settings, BMailChainRunner *status) + : SimpleMailProtocol(settings,status), + fNumMessages(-1), + fMailDropSize(0) +{ + #ifdef POPSSL + use_ssl = (settings->FindInt32("flavor") == 1); + #endif + Init(); +} + + +POP3Protocol::~POP3Protocol() +{ + SendCommand("QUIT" CRLF); + +#ifdef POPSSL + if (use_ssl) { + SSL_shutdown(ssl); + SSL_CTX_free(ctx); + } +#endif + +#ifdef BONE + close(conn); +#else + closesocket(conn); +#endif +} + + +status_t +POP3Protocol::Open(const char *server, int port, int) +{ + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Connecting to POP3 Server...","POP3サーバに接続しています...")); + + if (port <= 0) + #ifdef POPSSL + port = use_ssl ? 995 : 110; + #else + port = 110; + #endif + + fLog = ""; + + //-----Prime the error message + BString error_msg; + error_msg << MDR_DIALECT_CHOICE ("Error while connecting to server ","サーバに接続中にエラーが発生しました ") << server; + if (port != 110) + error_msg << ":" << port; + + uint32 hostIP = inet_addr(server); // first see if we can parse it as a numeric address + if ((hostIP == 0)||(hostIP == (uint32)-1)) { + struct hostent * he = gethostbyname(server); + hostIP = he ? *((uint32*)he->h_addr) : 0; + } + + if (hostIP == 0) { + error_msg << MDR_DIALECT_CHOICE (": Connection refused or host not found",": :接続が拒否されたかサーバーが見つかりません"); + pop3_error(error_msg.String()); + + return B_NAME_NOT_FOUND; + } + + conn = socket(AF_INET, SOCK_STREAM, 0); + if (conn >= 0) { + struct sockaddr_in saAddr; + memset(&saAddr, 0, sizeof(saAddr)); + saAddr.sin_family = AF_INET; + saAddr.sin_port = htons(port); + saAddr.sin_addr.s_addr = hostIP; + int result = connect(conn, (struct sockaddr *) &saAddr, sizeof(saAddr)); + if (result < 0) { +#ifdef BONE + close(conn); +#else + closesocket(conn); +#endif + conn = -1; + error_msg << ": " << strerror(errno); + pop3_error(error_msg.String()); + return errno; + } + } else { + error_msg << ": Could not allocate socket."; + pop3_error(error_msg.String()); + return B_ERROR; + } + + #ifdef POPSSL + if (use_ssl) { + SSL_library_init(); + SSL_load_error_strings(); + RAND_seed(this,sizeof(POP3Protocol)); + /*--- Because we're an add-on loaded at an unpredictable time, all + the memory addresses and things contained in ourself are + esssentially random. */ + + ctx = SSL_CTX_new(SSLv23_method()); + ssl = SSL_new(ctx); + sbio=BIO_new_socket(conn,BIO_NOCLOSE); + SSL_set_bio(ssl,sbio,sbio); + + if (SSL_connect(ssl) <= 0) { + BString error; + error << "Could not connect to POP3 server " << settings->FindString("server"); + if (port != 995) + error << ":" << port; + error << ". (SSL Connection Error)"; + runner->ShowError(error.String()); + SSL_CTX_free(ctx); + #ifdef BONE + close(conn); + #else + closesocket(net); + #endif + runner->Stop(); + return B_ERROR; + + } + } + + #endif + + BString line; + status_t err; + int32 tries = 200000; + // no endless loop here + while ((err = ReceiveLine(line)) == 0) { + if (tries-- < 0) + return B_ERROR; + } + + if (err < 0) { +#ifdef BONE + close(conn); +#else + closesocket(conn); +#endif + conn = -1; + error_msg << ": " << strerror(err); + pop3_error(error_msg.String()); + + return B_ERROR; + } + + if (strncmp(line.String(), "+OK", 3) != 0) { + error_msg << MDR_DIALECT_CHOICE (". The server said:\n","サーバのメッセージです\n") << line.String(); + pop3_error(error_msg.String()); + + return B_ERROR; + } + + fLog = line; + + return B_OK; +} + + +status_t POP3Protocol::Login(const char *uid, const char *password, int method) +{ + status_t err; + + BString error_msg; + error_msg << MDR_DIALECT_CHOICE ("Error while authenticating user ","ユーザー認証中にエラーが発生しました ") << uid; + + if (method == 1) { //APOP + int32 index = fLog.FindFirst("<"); + if(index != B_ERROR) { + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Sending APOP authentication...","APOP認証情報を送信中...")); + int32 end = fLog.FindFirst(">",index); + BString timestamp(""); + fLog.CopyInto(timestamp,index,end-index+1); + timestamp += password; + char md5sum[33]; + MD5Digest((unsigned char*)timestamp.String(),md5sum); + BString cmd = "APOP "; + cmd += uid; + cmd += " "; + cmd += md5sum; + cmd += CRLF; + + err = SendCommand(cmd.String()); + if (err != B_OK) { + error_msg << MDR_DIALECT_CHOICE (". The server said:\n","サーバのメッセージです\n") << fLog; + pop3_error(error_msg.String()); + + return err; + } + + return B_OK; + } else { + error_msg << MDR_DIALECT_CHOICE (": The server does not support APOP.","サーバはAPOPをサポートしていません"); + pop3_error(error_msg.String()); + return B_NOT_ALLOWED; + } + } + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Sending username...","ユーザーID送信中...")); + + BString cmd = "USER "; + cmd += uid; + cmd += CRLF; + + err = SendCommand(cmd.String()); + if (err != B_OK) { + error_msg << MDR_DIALECT_CHOICE (". The server said:\n","サーバのメッセージです\n") << fLog; + pop3_error(error_msg.String()); + + return err; + } + + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Sending password...","パスワード送信中...")); + cmd = "PASS "; + cmd += password; + cmd += CRLF; + + err = SendCommand(cmd.String()); + if (err != B_OK) { + error_msg << MDR_DIALECT_CHOICE (". The server said:\n","サーバのメッセージです\n") << fLog; + pop3_error(error_msg.String()); + + return err; + } + + return B_OK; +} + + +status_t POP3Protocol::Stat() +{ + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Getting mailbox size...","メールボックスのサイズを取得しています...")); + + if (SendCommand("STAT" CRLF) < B_OK) + return B_ERROR; + + int32 messages,dropSize; + if (sscanf(fLog.String(),"+OK %ld %ld",&messages,&dropSize) < 2) + return B_ERROR; + + fNumMessages = messages; + fMailDropSize = dropSize; + + return B_OK; +} + + +int32 POP3Protocol::Messages() +{ + if (fNumMessages < 0) + Stat(); + + return fNumMessages; +} + + +size_t POP3Protocol::MailDropSize() +{ + if (fNumMessages < 0) + Stat(); + + return fMailDropSize; +} + + +status_t POP3Protocol::Retrieve(int32 message, BPositionIO *write_to) +{ + status_t returnCode; + BString cmd; + cmd << "RETR " << message + 1 << CRLF; + returnCode = RetrieveInternal(cmd.String(), message, write_to, true); + runner->ReportProgress(0 /* bytes */, 1 /* messages */); + + if (returnCode == B_OK) { // Some debug code. + int32 message_len = MessageSize(message); + write_to->Seek (0, SEEK_END); + if (write_to->Position() != message_len) { + printf ("POP3Protocol::Retrieve Note: message size is %d, was " + "expecting %ld, for message #%ld. Could be a transmission error " + "or a bad POP server implementation (does it remove escape codes " + "when it counts size?).\n", + (int) write_to->Position(), message_len, message); + } + } + + return returnCode; +} + + +status_t POP3Protocol::GetHeader(int32 message, BPositionIO *write_to) +{ + BString cmd; + cmd << "TOP " << message + 1 << " 0" << CRLF; + return RetrieveInternal(cmd.String(),message,write_to, false); +} + + +status_t POP3Protocol::RetrieveInternal(const char *command, int32 message, + BPositionIO *write_to, bool post_progress) +{ + const int bufSize = 1024 * 30; + + // To avoid waiting for the non-arrival of the next data packet, try to + // receive only the message size, plus the 3 extra bytes for the ".\r\n" + // after the message. Of course, if we get it wrong (or it is a huge + // message or has lines starting with escaped periods), it will then switch + // back to receiving full buffers until the message is done. + int amountToReceive = MessageSize (message) + 3; + if (amountToReceive >= bufSize || amountToReceive <= 0) + amountToReceive = bufSize - 1; + + BString bufBString; // Used for auto-dealloc on return feature. + char *buf = bufBString.LockBuffer (bufSize); + int amountInBuffer = 0; + int amountReceived; + int testIndex; + char *testStr; + bool cont = true; + bool flushWholeBuffer = false; + write_to->Seek(0,SEEK_SET); + + if (SendCommand(command) != B_OK) + return B_ERROR; + + struct timeval tv; + struct fd_set fds; + + tv.tv_sec = long(POP3_RETRIEVAL_TIMEOUT / 1e6); + tv.tv_usec = long(POP3_RETRIEVAL_TIMEOUT-(tv.tv_sec * 1e6)); + + /* Initialize (clear) the socket mask. */ + FD_ZERO(&fds); + + /* Set the socket in the mask. */ + FD_SET(conn, &fds); + + while (cont) { + int result = 0; + + #ifdef POPSSL + if ((use_ssl) && (SSL_pending(ssl))) + result = 1; + else + #endif + result = select(32, &fds, NULL, NULL, &tv); + if (result == 0) { + // No data available, even after waiting a minute. + fLog = "POP3 timeout - no data received after a long wait."; + runner->Stop(); + return B_ERROR; + } + if (amountToReceive > bufSize - 1 - amountInBuffer) + amountToReceive = bufSize - 1 - amountInBuffer; + #ifdef POPSSL + if (use_ssl) + amountReceived = SSL_read(ssl,buf + amountInBuffer, amountToReceive); + else + #endif + amountReceived = recv(conn,buf + amountInBuffer, amountToReceive,0); + + if (amountReceived < 0) { + fLog = strerror(errno); + return errno; + } + if (amountReceived == 0) { + fLog = "POP3 data supposedly ready to receive but not received!"; + return B_ERROR; // Shouldn't happen, but... + } + amountToReceive = bufSize - 1; // For next time, read a full buffer. + amountInBuffer += amountReceived; + buf[amountInBuffer] = 0; // NUL stops tests past the end of buffer. + + // Look for lines starting with a period. A single period by itself on + // a line "\r\n.\r\n" marks the end of the message (thus the need for + // at least five characters in the buffer for testing). A period + // "\r\n.Stuff" at the start of a line get deleted "\r\nStuff", since + // POP adds one as an escape code to let you have message text with + // lines starting with a period. For convenience, assume that no + // messages start with a period on the very first line, so we can + // search for the previous line's "\r\n". + + for (testIndex = 0; testIndex <= amountInBuffer - 5; testIndex++) { + testStr = buf + testIndex; + if (testStr[0] == '\r' && testStr[1] == '\n' && testStr[2] == '.') { + if (testStr[3] == '\r' && testStr[4] == '\n') { + // Found the end of the message marker. Ignore remaining data. + if (amountInBuffer > testIndex + 5) + printf ("POP3Protocol::RetrieveInternal Ignoring %d bytes " + "of extra data past message end.\n", + amountInBuffer - (testIndex + 5)); + amountInBuffer = testIndex + 2; // Don't include ".\r\n". + buf[amountInBuffer] = 0; + cont = false; + } else { + // Remove an extra period at the start of a line. + // Inefficient, but it doesn't happen often that you have a + // dot starting a line of text. Of course, a file with a + // lot of double period lines will get processed very + // slowly. + memmove (buf + testIndex + 2, buf + testIndex + 3, + amountInBuffer - (testIndex + 3) + 1 /* for NUL at end */); + amountInBuffer--; + // Watch out for the end of buffer case, when the POP text + // is "\r\n..X". Don't want to leave the resulting + // "\r\n.X" in the buffer (flush out the whole buffer), + // since that will get mistakenly evaluated again in the + // next loop and delete a character by mistake. + if (testIndex >= amountInBuffer - 4 && testStr[2] == '.') { + printf ("POP3Protocol::RetrieveInternal: Jackpot! You have " + "hit the rare situation with an escaped period at the " + "end of the buffer. Aren't you happy it decodes it " + "correctly?\n"); + flushWholeBuffer = true; + } + } + } + } + + if (cont && !flushWholeBuffer) { + // Dump out most of the buffer, but leave the last 4 characters for + // comparison continuity, in case the line starting with a period + // crosses a buffer boundary. + if (amountInBuffer > 4) { + write_to->Write(buf, amountInBuffer - 4); + if (post_progress) + runner->ReportProgress(amountInBuffer - 4,0); + memmove (buf, buf + amountInBuffer - 4, 4); + amountInBuffer = 4; + } + } else { // Dump everything - end of message or flushing the whole buffer. + write_to->Write(buf, amountInBuffer); + if (post_progress) + runner->ReportProgress(amountInBuffer,0); + amountInBuffer = 0; + } + } + return B_OK; +} + + +status_t POP3Protocol::UniqueIDs() { + status_t ret = B_OK; + runner->ReportProgress(0,0,MDR_DIALECT_CHOICE ("Getting UniqueIDs...","固有のIDを取得中...")); + + ret = SendCommand("UIDL" CRLF); + if (ret != B_OK) return ret; + + BString result; + int32 uid_offset; + while (ReceiveLine(result) > 0) { + if (result.ByteAt(0) == '.') + break; + + uid_offset = result.FindFirst(' ') + 1; + result.Remove(0,uid_offset); + unique_ids->AddItem(result.String()); + } + + if (SendCommand("LIST"CRLF) != B_OK) + return B_ERROR; + + int32 b; + while (ReceiveLine(result) > 0) { + if (result.ByteAt(0) == '.') + break; + + b = result.FindLast(" "); + if (b >= 0) + b = atol(&(result.String()[b])); + else + b = 0; + sizes.AddItem((void *)(b)); + } + + return ret; +} + + +void POP3Protocol::Delete(int32 num) { + BString cmd = "DELE "; + cmd << (num+1) << CRLF; + if (SendCommand(cmd.String()) != B_OK) { + // Error + } + #if DEBUG + puts(fLog.String()); + #endif +} + + +size_t POP3Protocol::MessageSize(int32 index) { + return (size_t)(sizes.ItemAt(index)); +} + + +int32 +POP3Protocol::ReceiveLine(BString &line) +{ + int32 len = 0, rcv; + int8 c = 0; + bool flag = false; + + line = ""; + + struct timeval tv; + struct fd_set fds; + + tv.tv_sec = long(POP3_RETRIEVAL_TIMEOUT / 1e6); + tv.tv_usec = long(POP3_RETRIEVAL_TIMEOUT-(tv.tv_sec * 1e6)); + + /* Initialize (clear) the socket mask. */ + FD_ZERO(&fds); + + /* Set the socket in the mask. */ + FD_SET(conn, &fds); + int result = -1; + #ifdef POPSSL + if ((use_ssl) && (SSL_pending(ssl))) + result = 1; + else + #endif + result = select(32, &fds, NULL, NULL, &tv); + + if (result < 0) + return B_TIMEOUT; + + if (result > 0) { + while (true) { // Hope there's an end of line out there else this gets stuck. + #ifdef POPSSL + if (use_ssl) + rcv = SSL_read(ssl,&c,1); + else + #endif + rcv = recv(conn, &c, 1, 0); + if (rcv < 0) + return errno; //--An error! + //putchar(c); + if ((c == '\n') || (rcv == 0 /* EOF */)) + break; + + if (c == '\r') { + flag = true; + } else { + if (flag) { + len++; + line += '\r'; + flag = false; + } + len++; + line += (char)c; + } + } + } else { + fLog = "POP3 socket timeout."; + runner->Stop(); + } + return len; +} + +status_t + +POP3Protocol::SendCommand(const char *cmd) +{ + if (conn < 0 || conn > FD_SETSIZE) + return B_FILE_ERROR; + //printf(cmd); + // Flush any accumulated garbage data before we send our command, so we + // don't misinterrpret responses from previous commands (that got left over + // due to bugs) as being from this command. + + struct timeval tv; + tv.tv_sec = long(1000 / 1e6); + tv.tv_usec = long(1000-(tv.tv_sec * 1e6)); /* very short timeout, hangs with 0 in R5 */ + + struct fd_set fds; + + /* Initialize (clear) the socket mask. */ + FD_ZERO(&fds); + + /* Set the socket in the mask. */ + FD_SET(conn, &fds); + int result; + #ifdef POPSSL + if ((use_ssl) && (SSL_pending(ssl))) + result = 1; + else + #endif + result = select(32, &fds, NULL, NULL, &tv); + + if (result > 0) { + int amountReceived; + char tempString [1025]; + #ifdef POPSSL + if (use_ssl) + amountReceived = SSL_read(ssl,tempString, sizeof (tempString) - 1); + else + #endif + amountReceived = recv (conn,tempString, sizeof (tempString) - 1,0); + if (amountReceived < 0) + return errno; + tempString [amountReceived] = 0; + printf ("POP3Protocol::SendCommand Bug! Had to flush %d bytes: %s\n", + amountReceived, tempString); + //if (amountReceived == 0) + // break; + } +#ifdef POPSSL + if (use_ssl) { + SSL_write(ssl,cmd,::strlen(cmd)); + //SSL_write(ssl,"\r\n",2); + } else +#endif + if (send(conn, cmd, ::strlen(cmd), 0) < 0) { + fLog = strerror(errno); + printf ("POP3Protocol::SendCommand Send \"%s\" failed, code %d: %s\n", + cmd, errno, fLog.String()); + return errno; + } + + fLog = ""; + status_t err = B_OK; + + while (true) { + int32 len = ReceiveLine(fLog); + if (len <= 0 || fLog.ICompare("+OK", 3) == 0) + break; + + if (fLog.ICompare("-ERR", 4) == 0) { + printf("POP3Protocol::SendCommand \"%s\" got error message " + "from server: %s\n", cmd, fLog.String()); + err = B_ERROR; + break; + } else { + printf("POP3Protocol::SendCommand \"%s\" got nonsense message " + "from server: %s\n", cmd, fLog.String()); + err = B_BAD_VALUE; //-------If it's not +OK, and it's not -ERR, then what the heck is it? Presume an error + break; + } + } + return err; +} + + +void POP3Protocol::MD5Digest (unsigned char *in,char *ascii_digest) +{ + unsigned char digest[16]; + + #ifdef POPSSL + MD5(in, ::strlen((char*)in),digest); + #else + MD5_CTX context; + + MD5Init(&context); + MD5Update(&context, in, ::strlen((char*)in)); + MD5Final(digest, &context); + #endif + + for (int i = 0; i < 16; i++) + sprintf(ascii_digest+2*i, "%02x", digest[i]); + + return; +} + + +BMailFilter *instantiate_mailfilter(BMessage *settings, BMailChainRunner *runner) +{ + return new POP3Protocol(settings,runner); +} + + +BView* instantiate_config_panel(BMessage *settings,BMessage *) +{ + BMailProtocolConfigView *view = new BMailProtocolConfigView(B_MAIL_PROTOCOL_HAS_USERNAME | B_MAIL_PROTOCOL_HAS_AUTH_METHODS | B_MAIL_PROTOCOL_HAS_FLAVORS | B_MAIL_PROTOCOL_HAS_PASSWORD | B_MAIL_PROTOCOL_HAS_HOSTNAME | B_MAIL_PROTOCOL_CAN_LEAVE_MAIL_ON_SERVER); + view->AddAuthMethod("Plain Text"); + view->AddAuthMethod("APOP"); + + #if POPSSL + view->AddFlavor("Unencrypted"); + view->AddFlavor("SSL"); + #endif + + view->SetTo(settings); + + return view; +} + diff --git a/src/add-ons/mail_daemon/inbound_protocols/pop3/pop3.h b/src/add-ons/mail_daemon/inbound_protocols/pop3/pop3.h new file mode 100644 index 0000000000..83ab3d4e35 --- /dev/null +++ b/src/add-ons/mail_daemon/inbound_protocols/pop3/pop3.h @@ -0,0 +1,52 @@ +#ifndef ZOIDBERG_POP3_H +#define ZOIDBERG_POP3_H +/* POP3Protocol - implementation of the POP3 protocol +** +** Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +#include +#include + +#include "SimpleMailProtocol.h" + +class POP3Protocol : public SimpleMailProtocol { + public: + POP3Protocol(BMessage *settings, BMailChainRunner *status); + ~POP3Protocol(); + + status_t Open(const char *server, int port, int protocol); + status_t Login(const char *uid, const char *password, int method); + status_t UniqueIDs(); + status_t Retrieve(int32 message, BPositionIO *write_to); + status_t GetHeader(int32 message, BPositionIO *write_to); + void Delete(int32 num); + size_t MessageSize(int32 index); + status_t Stat(); + int32 Messages(void); + size_t MailDropSize(void); + +protected: + status_t RetrieveInternal(const char *command,int32 message, BPositionIO *write_to, bool show_progress); + + int32 ReceiveLine(BString &line); + status_t SendCommand(const char* cmd); + void MD5Digest (unsigned char *in, char *out); // MD5 Digest + +private: + int conn; + BString fLog; + int32 fNumMessages; + size_t fMailDropSize; + BList sizes; + + #ifdef POPSSL + SSL_CTX *ctx; + SSL *ssl; + BIO *sbio; + + bool use_ssl; + #endif +}; + +#endif /* ZOIDBERG_POP3_H */ diff --git a/src/add-ons/mail_daemon/outbound_filters/fortune/ConfigView.cpp b/src/add-ons/mail_daemon/outbound_filters/fortune/ConfigView.cpp new file mode 100644 index 0000000000..1d01264545 --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_filters/fortune/ConfigView.cpp @@ -0,0 +1,86 @@ +/* ConfigView - the configuration view for the Fortune filter +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include "ConfigView.h" + +#include +#include +#include + +#include + +#include + + +ConfigView::ConfigView() + : BView(BRect(0,0,20,20),"fortune_filter",B_FOLLOW_LEFT | B_FOLLOW_TOP,0) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + // determine font height + font_height fontHeight; + GetFontHeight(&fontHeight); + float itemHeight = (int32)(fontHeight.ascent + fontHeight.descent + fontHeight.leading) + 13; + + BRect rect(5,4,250,25); + rect.bottom = rect.top - 2 + itemHeight; + BMailFileConfigView *fview = new BMailFileConfigView(MDR_DIALECT_CHOICE ("Fortune File:","予言ファイル:"),"fortune_file",false,"/boot/beos/etc/fortunes/default",B_FILE_NODE); + AddChild(fview); + + rect.top = rect.bottom + 8; + rect.bottom = rect.top - 2 + itemHeight; + BTextControl * control = new BTextControl(rect,"tag_line",MDR_DIALECT_CHOICE ("Tag Line:","見出し:"),NULL,NULL); + control->SetDivider(control->StringWidth(control->Label()) + 6); + AddChild(control); + + ResizeToPreferred(); +} + + +void ConfigView::SetTo(BMessage *archive) +{ + BString path = archive->FindString("fortune_file"); + if (path == B_EMPTY_STRING) + path = "/boot/beos/etc/fortunes/default"; + + if (BMailFileConfigView *control = (BMailFileConfigView *)FindView("fortune_file")) + control->SetTo(archive,NULL); + + path = archive->FindString("tag_line"); + if (!archive->HasString("tag_line")) + path = "Fortune Cookie Says:\n\n"; + + path.Truncate(path.Length() - 2); + if (BTextControl *control = (BTextControl *)FindView("tag_line")) + control->SetText(path.String()); +} + + +status_t ConfigView::Archive(BMessage *into,bool) const +{ + if (BMailFileConfigView *control = (BMailFileConfigView *)FindView("fortune_file")) + { + control->Archive(into); + } + + if (BTextControl *control = (BTextControl *)FindView("tag_line")) + { + BString line = control->Text(); + if (line != B_EMPTY_STRING) + line << "\n\n"; + if (into->ReplaceString("tag_line",line.String()) != B_OK) + into->AddString("tag_line",line.String()); + } + return B_OK; +} + + +void ConfigView::GetPreferredSize(float *width, float *height) +{ + *width = 258; + *height = (ChildAt(0)->Bounds().Height() + 8) * CountChildren(); +} + diff --git a/src/add-ons/mail_daemon/outbound_filters/fortune/ConfigView.h b/src/add-ons/mail_daemon/outbound_filters/fortune/ConfigView.h new file mode 100644 index 0000000000..ecd3ea4815 --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_filters/fortune/ConfigView.h @@ -0,0 +1,22 @@ +#ifndef CONFIG_VIEW +#define CONFIG_VIEW +/* ConfigView - the configuration view for the Folder filter +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + + +class ConfigView : public BView +{ + public: + ConfigView(); + void SetTo(BMessage *archive); + + virtual status_t Archive(BMessage *into, bool deep = true) const; + virtual void GetPreferredSize(float *width, float *height); +}; + +#endif /* CONFIG_VIEW */ diff --git a/src/add-ons/mail_daemon/outbound_filters/fortune/Jamfile b/src/add-ons/mail_daemon/outbound_filters/fortune/Jamfile new file mode 100644 index 0000000000..6995ad0acd --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_filters/fortune/Jamfile @@ -0,0 +1,11 @@ +SubDir OBOS_TOP src add-ons mail_daemon outbound_filters fortune ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +Addon Fortune : mail_daemon outbound_fitlers : + ConfigView.cpp + filter.cpp ; + +LinkSharedOSLibs Fortune : + be mail ; diff --git a/src/add-ons/mail_daemon/outbound_filters/fortune/filter.cpp b/src/add-ons/mail_daemon/outbound_filters/fortune/filter.cpp new file mode 100644 index 0000000000..2ea0c14433 --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_filters/fortune/filter.cpp @@ -0,0 +1,112 @@ +/* Add Fortune - adds fortunes to your mail +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include "ConfigView.h" + +#include +#include +#include +#include +#include + +#include + +#include "NodeMessage.h" + +class FortuneFilter : public BMailFilter +{ + BMessage *settings; + public: + FortuneFilter(BMessage*); + virtual status_t InitCheck(BString *err); + virtual status_t ProcessMailMessage + ( + BPositionIO** io_message, BEntry* io_entry, + BMessage* io_headers, BPath* io_folder, const char* io_uid + ); +}; + +FortuneFilter::FortuneFilter(BMessage* msg) + : BMailFilter(msg), settings(msg) +{ +} + +status_t FortuneFilter::InitCheck(BString* err) +{ + return B_OK; +} + +status_t FortuneFilter::ProcessMailMessage +(BPositionIO** io, BEntry* io_entry, BMessage* headers, BPath* , const char*) +{ + // What we want to do here is to change the message body. To do that we use the + // framework we already have by creating a new BEmailMessage based on the + // BPositionIO, changing the message body and rendering it back to disk. Of course + // this method ends up not being super-efficient, but it works. Ideas on how to + // improve this are welcome. + BString fortune_file; + BString tag_line; + BString fortune; + + BEmailMessage mail_message(*io); + BString mail_body(mail_message.BodyText()); + + // Obtain relevant settings + settings->FindString("fortune_file", &fortune_file); + settings->FindString("tag_line", &tag_line); + + // Add command to be executed + fortune_file.Prepend("/bin/fortune "); + + char buffer[768]; + FILE *fd; + + fd = popen(fortune_file.String(), "r"); + if (fd) + { + mail_body += "\n--\n"; + mail_body += tag_line; + + while (fgets(buffer, 768, fd)) + mail_body += buffer; + + mail_body += "\n"; + + pclose(fd); + + // Update the message body + mail_message.SetBodyTextTo(mail_body.String()); + + // Render it back to a BMallocIO object. We need this because we do not render + // the entire message in memory so if we try to change the BPositionIO object we + // have directly we will end up having the mail components pointing out to wrong + // locations in the BPositionIo itself. + BMallocIO shimmy_pipe; + mail_message.RenderToRFC822(&shimmy_pipe); + + // Now we use the BMallocIO object and overwrite the BPositionIO one with it. + (*io)->Seek(0, SEEK_SET); + (*io)->SetSize(0); + (*io)->Write(shimmy_pipe.Buffer(),shimmy_pipe.BufferLength()); + } + else + printf("Could not open pipe to fortune!\n"); + + return B_OK; +} + +BMailFilter* instantiate_mailfilter(BMessage* settings, BMailChainRunner*) +{ + return new FortuneFilter(settings); +} + +BView* instantiate_config_panel(BMessage *settings,BMessage *) +{ + ConfigView *view = new ConfigView(); + view->SetTo(settings); + + return view; +} diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/Jamfile b/src/add-ons/mail_daemon/outbound_protocols/smtp/Jamfile new file mode 100644 index 0000000000..f5837cd811 --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/Jamfile @@ -0,0 +1,13 @@ +SubDir OBOS_TOP src add-ons mail_daemon outbound_protocols smtp ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +SubDirC++Flags -DBONE ; + +Addon SMTP : mail_daemon outbound_protocols : + smtp.cpp + md5c.c ; + +LinkSharedOSLibs SMTP : + be mail socket bind ; diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/md5.h b/src/add-ons/mail_daemon/outbound_protocols/smtp/md5.h new file mode 100644 index 0000000000..53e35d4c7d --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/md5.h @@ -0,0 +1,56 @@ +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#ifndef MD5_H__ +#define MD5_H__ + +#include "md5global.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init PROTO_LIST ((MD5_CTX *)); +void MD5Update PROTO_LIST + ((MD5_CTX *, unsigned char *, unsigned int)); +void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); +void MD5Hmac(unsigned char *digest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len); +void MD5HexHmac(char *hexdigest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len); + +#ifdef __cplusplus +} +#endif +#endif /* MD5_H__ */ diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/md5c.c b/src/add-ons/mail_daemon/outbound_protocols/smtp/md5c.c new file mode 100644 index 0000000000..1a91ac1874 --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/md5c.c @@ -0,0 +1,410 @@ +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +//#include "config.h" +#include "md5global.h" +#include "md5.h" +#include +#include +#include + +/* Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64])); +static void Encode PROTO_LIST + ((unsigned char *, UINT4 *, unsigned int)); +static void Decode PROTO_LIST + ((UINT4 *, unsigned char *, unsigned int)); +static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int)); +static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int)); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5Init (MD5_CTX *context) + /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void MD5Update (MD5_CTX *context, unsigned char *input, unsigned int inputLen) + /* context */ + /* input block */ + /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((POINTER)&context->buffer[index], (POINTER)&input[i], + inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void MD5Final (unsigned char digest[16], MD5_CTX *context) + /* message digest */ + /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (UINT4 state[4], unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. +*/ + MD5_memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (POINTER output, POINTER input, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (POINTER output, int value, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} + +/* +** Function: md5_hmac +** taken from the file rfc2104.txt +** written by Martin Schaaf +*/ +void +MD5Hmac(unsigned char *digest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len) +{ + MD5_CTX context; + unsigned char k_ipad[64]; /* inner padding - + * key XORd with ipad + */ + unsigned char k_opad[64]; /* outer padding - + * key XORd with opad + */ + /* unsigned char tk[16]; */ + int i; + + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + if (key_len > 64) { + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + MD5_CTX tctx; + + MD5Init(&tctx); + MD5Update(&tctx, (unsigned char*)key, key_len); + MD5Final(k_ipad, &tctx); + MD5Final(k_opad, &tctx); + } else { + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* + * perform inner MD5 + */ + MD5Init(&context); /* init context for 1st + * pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + MD5Update(&context, (unsigned char*)text, text_len); /* then text of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + MD5Init(&context); /* init context for 2nd + * pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st + * hash */ + MD5Final(digest, &context); /* finish up 2nd pass */ +} + + +void +MD5HexHmac(char *hexdigest, + const unsigned char* text, int text_len, + const unsigned char* key, int key_len) +{ + unsigned char digest[16]; + int i; + + MD5Hmac(digest, text, text_len, key, key_len); + for (i = 0; i < 16; i++) + sprintf(hexdigest + 2 * i, "%02x", digest[i]); +} diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/md5global.h b/src/add-ons/mail_daemon/outbound_protocols/smtp/md5global.h new file mode 100644 index 0000000000..14f6e44b39 --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/md5global.h @@ -0,0 +1,41 @@ +/* + * For license terms, see the file COPYING in this directory. + * + * md5global.h Global declarations for MD5 module used by fetchmail + * + */ + +#ifndef MD5GLOBAL_H__ +#define MD5GLOBAL_H__ +/* GLOBAL.H - RSAREF types and constants + */ + +/* force prototypes on, we need ANSI C anyway */ +#ifndef PROTOTYPES +#define PROTOTYPES 1 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +#if SIZEOF_INT == 4 +typedef unsigned int UINT4; +#else +typedef unsigned long int UINT4; +#endif + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. +If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif + +#endif /* MD5GLOBAL_H__ */ diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.cpp b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.cpp new file mode 100644 index 0000000000..612781ed63 --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.cpp @@ -0,0 +1,678 @@ +/* SMTPProtocol - implementation of the SMTP protocol +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "smtp.h" +#include "md5.h" + +#include + +#ifdef BONE + #include + #include + #include + #include +#else + #include +#endif + +#define CRLF "\r\n" +#define SMTP_RESPONSE_SIZE 8192 + +#ifdef DEBUG +# define D(x) x +# define bug printf +#else +# define D(x) ; +#endif + + +// Authentication types recognized. Not all methods are implemented. +enum AuthType { + LOGIN = 1, + PLAIN = 1 << 2, + CRAM_MD5 = 1 << 3, + DIGEST_MD5 = 1 << 4 +}; + + +SMTPProtocol::SMTPProtocol(BMessage *message, BMailChainRunner *run) + : BMailFilter(message), + fSettings(message), + runner(run), + fAuthType(0) +{ + BString error_msg; + int32 authMethod = fSettings->FindInt32("auth_method"); + + if (authMethod == 2) { + // POP3 authentification is handled here instead of SMTPProtocol::Login() + // because some servers obviously don't like establishing the connection + // to the SMTP server first... + fStatus = POP3Authentification(); + if (fStatus < B_OK) { + error_msg << MDR_DIALECT_CHOICE ("POP3 authentification failed. The server said:\n","POP3認証に失敗しました\n") << fLog; + runner->ShowError(error_msg.String()); + return; + } + } + + fStatus = Open(fSettings->FindString("server"), fSettings->FindInt32("port"), authMethod == 1); + if (fStatus < B_OK) { + error_msg << MDR_DIALECT_CHOICE ("Error while opening connection to ","接続中にエラーが発生しました") << fSettings->FindString("server"); + + if (fSettings->FindInt32("port") > 0) + error_msg << ":" << fSettings->FindInt32("port"); + + // << strerror(err) - BNetEndpoint sucks, we can't use this; + if (fLog.Length() > 0) + error_msg << ". The server says:\n" << fLog; + else + error_msg << MDR_DIALECT_CHOICE (": Connection refused or host not found.",";接続が拒否されたかサーバーが見つかりません"); + + runner->ShowError(error_msg.String()); + return; + } + + const char *password = fSettings->FindString("password"); + char *passwd = get_passwd(fSettings, "cpasswd"); + if (passwd) + password = passwd; + + fStatus = Login(fSettings->FindString("username"), password); + delete passwd; + + if (fStatus < B_OK) { + //-----This is a really cool kind of error message. How can we make it work for POP3? + error_msg << MDR_DIALECT_CHOICE ("Error while logging in to ","ログイン中にエラーが発生しました\n") << fSettings->FindString("server") + << MDR_DIALECT_CHOICE (". The server said:\n","サーバーエラー\n") << fLog; + runner->ShowError(error_msg.String()); + } +} + + +SMTPProtocol::~SMTPProtocol() +{ + Close(); +} + + +// Check for errors? +status_t +SMTPProtocol::InitCheck(BString *verbose) +{ + if (verbose != NULL && fStatus < B_OK) { + *verbose << MDR_DIALECT_CHOICE ("Error while fetching mail from ","受信中にエラーが発生しました") << fSettings->FindString("server") + << ": " << strerror(fStatus); + } + return fStatus; +} + + +// Process EMail to be sent + +status_t +SMTPProtocol::ProcessMailMessage(BPositionIO **io_message, BEntry */*io_entry*/, + BMessage *io_headers, BPath */*io_folder*/, const char */*io_uid*/) +{ + const char *from = io_headers->FindString("MAIL:from"); + const char *to = io_headers->FindString("MAIL:recipients"); + if (!to) + to = io_headers->FindString("MAIL:to"); + + if (to == NULL || from == NULL) + fLog = "Invalid message headers"; + + if (to && from && Send(to, from, *io_message) == B_OK) { + runner->ReportProgress(0, 1); + return B_OK; + } + + BString error; + MDR_DIALECT_CHOICE ( + error << "An error occurred while sending the message " << io_headers->FindString("MAIL:subject") << " to " << to << ":\n" << fLog;, + error << io_headers->FindString("MAIL:subject") << "を" << to << "\nへ送信中にエラーが発生しました:\n" << fLog; + ) + + runner->ShowError(error.String()); + runner->ReportProgress(0, 1); + return B_ERROR; +} + + +// Opens connection to server + +status_t +SMTPProtocol::Open(const char *address, int port, bool esmtp) +{ + runner->ReportProgress(0, 0, MDR_DIALECT_CHOICE ("Connecting to server...","接続中...")); + + if (port <= 0) + port = 25; + + uint32 hostIP = inet_addr(address); // first see if we can parse it as a numeric address + if ((hostIP == 0)||(hostIP == (uint32)-1)) { + struct hostent * he = gethostbyname(address); + hostIP = he ? *((uint32*)he->h_addr) : 0; + } + + if (hostIP == 0) + return EHOSTUNREACH; + + _fd = socket(AF_INET, SOCK_STREAM, 0); + if (_fd >= 0) { + struct sockaddr_in saAddr; + memset(&saAddr, 0, sizeof(saAddr)); + saAddr.sin_family = AF_INET; + saAddr.sin_port = htons(port); + saAddr.sin_addr.s_addr = hostIP; + int result = connect(_fd, (struct sockaddr *) &saAddr, sizeof(saAddr)); + if (result < 0) { +#ifdef BONE + close(_fd); +#else + closesocket(_fd); +#endif + _fd = -1; + return errno; + } + } else { + return errno; + } + + BString line; + ReceiveResponse(line); + + char *cmd = new char[::strlen(address)+8]; + if (!esmtp) + ::sprintf(cmd,"HELO %s"CRLF, address); + else + ::sprintf(cmd,"EHLO %s"CRLF, address); + + if (SendCommand(cmd) != B_OK) { + delete[] cmd; + return B_ERROR; + } + + delete[] cmd; + + // Check auth type + if (esmtp) { + const char *res = fLog.String(); + char *p; + if ((p = ::strstr(res, "250-AUTH")) != NULL) { + if(::strstr(p, "LOGIN")) + fAuthType |= LOGIN; + if(::strstr(p, "PLAIN")) + fAuthType |= PLAIN; + if(::strstr(p, "CRAM-MD5")) + fAuthType |= CRAM_MD5; + if(::strstr(p, "DIGEST-MD5")) + fAuthType |= DIGEST_MD5; + } + } + return B_OK; +} + + +status_t +SMTPProtocol::POP3Authentification() +{ + // find the POP3 filter of the other chain - identify by name... + BList chains; + if (GetInboundMailChains(&chains) < B_OK) { + fLog = "Cannot get inbound chains"; + return B_ERROR; + } + + BMailChainRunner *parent = runner; + BMailChain *chain = NULL; + for (int i = chains.CountItems(); i-- > 0;) + { + chain = (BMailChain *)chains.ItemAt(i); + if (chain != NULL && !strcmp(chain->Name(), parent->Chain()->Name())) + break; + chain = NULL; + } + if (chain != NULL) + { + // found mail chain! let's check for the POP3 protocol + BMessage msg; + entry_ref ref; + if (chain->GetFilter(0, &msg, &ref) >= B_OK) + { + BPath path(&ref); + if (path.InitCheck() >= B_OK && !strcmp(path.Leaf(), "POP3")) + { + // protocol matches, go execute it! + + image_id image = load_add_on(path.Path()); + + fLog = "Cannot load POP3 add-on"; + if (image >= B_OK) + { + BMailFilter *(* instantiate)(BMessage *, BMailChainRunner *); + status_t status = get_image_symbol(image, "instantiate_mailfilter", + B_SYMBOL_TYPE_TEXT, (void **)&instantiate); + if (status >= B_OK) + { + msg.AddInt32("chain", chain->ID()); + msg.AddBool("login_and_do_nothing_else_of_any_importance",true); + + // instantiating and deleting should be enough + BMailFilter *filter = (*instantiate)(&msg, runner); + delete filter; + } + else + fLog = "Cannot run POP3 add-on, symbol not found"; + + unload_add_on(image); + return status; + } + } + } + else + fLog = "Could not get inbound protocol"; + } + else + fLog = "Cannot find inbound chain"; + + for (int i = chains.CountItems(); i-- > 0;) + { + chain = (BMailChain *)chains.ItemAt(i); + delete chain; + } + + return B_ERROR; +} + + +status_t +SMTPProtocol::Login(const char *_login, const char *password) +{ + if (fAuthType == 0) + return B_OK; + + const char *login = _login; + char hex_digest[33]; + BString out; + + int32 loginlen = ::strlen(login); + int32 passlen = ::strlen(password); + + if (fAuthType & CRAM_MD5) { + //******* CRAM-MD5 Authentication ( not tested yet.) + SendCommand("AUTH CRAM-MD5"CRLF); + const char *res = fLog.String(); + + if (strncmp(res, "334", 3) != 0) + return B_ERROR; + char *base = new char[::strlen(&res[4])+1]; + int32 baselen = ::strlen(base); + baselen = ::decode_base64(base, base, baselen); + base[baselen] = '\0'; + + D(bug("base: %s\n", base)); + + ::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen, + (const unsigned char *)password, (int)passlen); + + D(bug("%s\n%s\n", base, hex_digest)); + + delete[] base; + + BString preResponse, postResponse; + preResponse = login; + preResponse << " " << hex_digest << CRLF; + char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10); + baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */); + resp[baselen] = 0; + postResponse.UnlockBuffer(); + postResponse.Append(CRLF); + + SendCommand(postResponse.String()); + + res = fLog.String(); + if (atol(res) < 500) + return B_OK; + } + if (fAuthType & DIGEST_MD5) { + //******* DIGEST-MD5 Authentication ( not written yet..) + fLog = "DIGEST-MD5 Authentication is not supported"; + } + if (fAuthType & LOGIN) { + //******* LOGIN Authentication ( tested. works fine) + ssize_t encodedsize; // required by our base64 implementation + + SendCommand("AUTH LOGIN"CRLF); + const char *res = fLog.String(); + + if (strncmp(res, "334", 3) != 0) + return B_ERROR; + + // Send login name as base64 + char *login64 = new char[loginlen*3 + 6]; + encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */); + login64[encodedsize] = 0; + strcat (login64, CRLF); + SendCommand(login64); + delete[] login64; + + res = fLog.String(); + if (strncmp(res,"334",3) != 0) + return B_ERROR; + + // Send password as base64 + login64 = new char[passlen*3 + 6]; + encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */); + login64[encodedsize] = 0; + strcat (login64, CRLF); + SendCommand(login64); + delete[] login64; + + res = fLog.String(); + if (atol(res) < 500) + return B_OK; + } + if (fAuthType & PLAIN) { + //******* PLAIN Authentication ( not tested yet.) + BString preResponse, postResponse; + char *stringPntr; + ssize_t encodedLength; + stringPntr = preResponse.LockBuffer(loginlen * 2 + passlen + 3); + sprintf (stringPntr, "%s%c%s%c%s", login, 0, login, 0, password); + preResponse.UnlockBuffer(loginlen * 2 + passlen + 3); + stringPntr = postResponse.LockBuffer(preResponse.Length() * 3); + encodedLength = ::encode_base64(stringPntr, preResponse.String(), + preResponse.Length(), true /* headerMode */); + stringPntr[encodedLength] = 0; + postResponse.UnlockBuffer(); + postResponse.Prepend("AUTH PLAIN "); + postResponse << CRLF; + + SendCommand(postResponse.String()); + + const char *res = fLog.String(); + if (atol(res) < 500) + return B_OK; + } + return B_ERROR; +} + + +void +SMTPProtocol::Close() +{ + + BString cmd = "QUIT"; + cmd += CRLF; + + if (SendCommand(cmd.String()) != B_OK) { + // Error + } +#ifdef BONE + close(_fd); +#else + closesocket(_fd); +#endif +} + + +/** Send mail */ + +status_t +SMTPProtocol::Send(const char *to, const char *from, BPositionIO *message) +{ + BString cmd = from; + cmd.Remove(0, cmd.FindFirst("\" <") + 2); + cmd.Prepend("MAIL FROM: "); + cmd += CRLF; + if (SendCommand(cmd.String()) != B_OK) + return B_ERROR; + + int32 len = strlen(to); + BString addr(""); + for (int32 i = 0;i < len;i++) { + char c = to[i]; + if (c != ',') + addr += (char)c; + if (c == ','||i == len-1) { + if(addr.Length() == 0) + continue; + cmd = "RCPT TO: "; + cmd << addr.String() << CRLF; + if (SendCommand(cmd.String()) != B_OK) + return B_ERROR; + + addr =""; + } + } + + cmd = "DATA"; + cmd += CRLF; + if (SendCommand(cmd.String()) != B_OK) + return B_ERROR; + + // Send the message data. Convert lines starting with a period to start + // with two periods and so on. The actual sequence is CR LF Period. The + // SMTP server will remove the periods. Of course, the POP server may then + // add some of its own, but the POP client should take care of them. + + ssize_t amountRead; + ssize_t amountToRead; + ssize_t amountUnread; + ssize_t bufferLen = 0; + const int bufferMax = 2000; + bool foundCRLFPeriod; + int i; + bool messageEndedWithCRLF = false; + + message->Seek(0, SEEK_END); + amountUnread = message->Position(); + message->Seek(0, SEEK_SET); + char *data = new char[bufferMax]; + + while (true) { + // Fill the buffer if it is getting low, but not every time, to avoid + // small reads. + if (bufferLen < bufferMax / 2) { + amountToRead = bufferMax - bufferLen; + if (amountToRead > amountUnread) + amountToRead = amountUnread; + if (amountToRead > 0) { + amountRead = message->Read (data + bufferLen, amountToRead); + if (amountRead <= 0 || amountRead > amountToRead) + amountUnread = 0; // Just stop reading when an error happens. + else { + amountUnread -= amountRead; + bufferLen += amountRead; + } + } + } + + // Look for the next CRLFPeriod triple. + foundCRLFPeriod = false; + for (i = 0; i <= bufferLen - 3; i++) { + if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') { + foundCRLFPeriod = true; + // Send data up to the CRLF, and include the period too. + if (send (_fd,data, i + 3,0) < 0) { + amountUnread = 0; // Stop when an error happens. + bufferLen = 0; + break; + } + runner->ReportProgress (i + 2 /* Don't include the double period here */,0); + // Move the data over in the buffer, but leave the period there + // so it gets sent a second time. + memmove(data, data + (i + 2), bufferLen - (i + 2)); + bufferLen -= i + 2; + break; + } + } + + if (!foundCRLFPeriod) { + if (amountUnread <= 0) { // No more data, all we have is in the buffer. + if (bufferLen > 0) { + send (_fd,data, bufferLen,0); + runner->ReportProgress (bufferLen,0); + if (bufferLen >= 2) + messageEndedWithCRLF = (data[bufferLen-2] == '\r' && + data[bufferLen-1] == '\n'); + } + break; // Finished! + } + + // Send most of the buffer, except a few characters to overlap with + // the next read, in case the CRLFPeriod is split between reads. + if (bufferLen > 3) { + if (send (_fd,data, bufferLen - 3,0) < 0) + break; // Stop when an error happens. + runner->ReportProgress (bufferLen - 3,0); + memmove (data, data + bufferLen - 3, 3); + bufferLen = 3; + } + } + } + delete [] data; + + if (messageEndedWithCRLF) + cmd = "."CRLF; // The standard says don't add extra CRLF. + else + cmd = CRLF"."CRLF; + + if (SendCommand(cmd.String()) != B_OK) + return B_ERROR; + + return B_OK; +} + + +// Receives response from server. + +int32 +SMTPProtocol::ReceiveResponse(BString &out) +{ + out = ""; + int32 len = 0,r; + char buf[SMTP_RESPONSE_SIZE]; + bigtime_t timeout = 1000000*180; // timeout 180 secs + + struct timeval tv; + struct fd_set fds; + + tv.tv_sec = long(timeout / 1e6); + tv.tv_usec = long(timeout-(tv.tv_sec * 1e6)); + + /* Initialize (clear) the socket mask. */ + FD_ZERO(&fds); + + /* Set the socket in the mask. */ + FD_SET(_fd, &fds); + int result = select(32, &fds, NULL, NULL, &tv); + if (result < 0) + return errno; + + if (result > 0) { + while (1) { + r = recv(_fd,buf, SMTP_RESPONSE_SIZE - 1,0); + if (r <= 0) + break; + + len += r; + out.Append(buf, r); + if (strstr(buf, CRLF)) + break; + } + } else + fLog = "SMTP socket timeout."; + + D(bug("S:%s\n", out.String())); + return len; +} + + +// Sends SMTP command. Result kept in fLog + +status_t +SMTPProtocol::SendCommand(const char *cmd) +{ + D(bug("C:%s\n", cmd)); + + if (send(_fd,cmd, ::strlen(cmd),0) == B_ERROR) + return B_ERROR; + + fLog = ""; + + // Receive + while (1) { + int32 len = ReceiveResponse(fLog); + + if (len <= 0) { + D(bug("SMTP: len == %ld\n", len)); + return B_ERROR; + } + + if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-')) + { + int32 num = atol(fLog.String()); + D(bug("ReplyNumber: %ld\n", num)); + + if (num >= 500) + return B_ERROR; + + break; + } + } + + return B_OK; +} + + +// Instantiate hook +BMailFilter * +instantiate_mailfilter(BMessage *settings, BMailChainRunner *status) +{ + return new SMTPProtocol(settings, status); +} + + +// Configuration interface +BView * +instantiate_config_panel(BMessage *settings, BMessage *) +{ + BMailProtocolConfigView *view = new BMailProtocolConfigView(B_MAIL_PROTOCOL_HAS_AUTH_METHODS | B_MAIL_PROTOCOL_HAS_USERNAME | B_MAIL_PROTOCOL_HAS_PASSWORD | B_MAIL_PROTOCOL_HAS_HOSTNAME); + + view->AddAuthMethod(MDR_DIALECT_CHOICE ("None","無し"), false); + view->AddAuthMethod(MDR_DIALECT_CHOICE ("ESMTP","ESMTP")); + view->AddAuthMethod(MDR_DIALECT_CHOICE ("POP3 before SMTP","送信前に受信する"), false); + + BTextControl *control = (BTextControl *)(view->FindView("host")); + control->SetLabel(MDR_DIALECT_CHOICE ("SMTP Host: ","SMTPサーバ: ")); + //control->SetDivider(be_plain_font->StringWidth("SMTP Host: ")); + + view->SetTo(settings); + + return view; +} diff --git a/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.h b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.h new file mode 100644 index 0000000000..bbe16ebb5c --- /dev/null +++ b/src/add-ons/mail_daemon/outbound_protocols/smtp/smtp.h @@ -0,0 +1,44 @@ +#ifndef ZOIDBERG_SMTP_H +#define ZOIDBERG_SMTP_H +/* SMTPProtocol - implementation of the SMTP protocol +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +#include + + +class SMTPProtocol : public BMailFilter { + public: + SMTPProtocol(BMessage *message, BMailChainRunner *runner); + ~SMTPProtocol(); + + virtual status_t InitCheck(BString *verbose); + virtual status_t ProcessMailMessage(BPositionIO **io_message, BEntry *io_entry, + BMessage *io_headers, BPath *io_folder, const char *io_uid); + + //----Perfectly good holdovers from the old days + status_t Open(const char *server, int port, bool esmtp); + status_t Login(const char *uid, const char *password); + void Close(); + status_t Send(const char *to, const char *from, BPositionIO *message); + + int32 ReceiveResponse(BString &line); + status_t SendCommand(const char *cmd); + + private: + status_t POP3Authentification(); + + int _fd; + BString fLog; + BMessage *fSettings; + BMailChainRunner *runner; + int32 fAuthType; + + status_t fStatus; +}; + +#endif /* ZOIDBERG_SMTP_H */ diff --git a/src/add-ons/mail_daemon/system_filters/inbox/Jamfile b/src/add-ons/mail_daemon/system_filters/inbox/Jamfile new file mode 100644 index 0000000000..ad518cb002 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/inbox/Jamfile @@ -0,0 +1,10 @@ +SubDir OBOS_TOP src add-ons mail_daemon system_filters inbox ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +Addon Inbox : mail_daemon inbound_filters : + filter.cpp ; + +LinkSharedOSLibs Inbox : + be mail ; diff --git a/src/add-ons/mail_daemon/system_filters/inbox/filter.cpp b/src/add-ons/mail_daemon/system_filters/inbox/filter.cpp new file mode 100644 index 0000000000..a64a403b59 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/inbox/filter.cpp @@ -0,0 +1,410 @@ +/* Inbox - places the incoming mail to their destination folder +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +struct mail_header_field +{ + const char *rfc_name; + + const char *attr_name; + type_code attr_type; + // currently either B_STRING_TYPE and B_TIME_TYPE +}; + +static const mail_header_field gDefaultFields[] = +{ + { "To", B_MAIL_ATTR_TO, B_STRING_TYPE }, + { "From", B_MAIL_ATTR_FROM, B_STRING_TYPE }, + { "Cc", B_MAIL_ATTR_CC, B_STRING_TYPE }, + { "Date", B_MAIL_ATTR_WHEN, B_TIME_TYPE }, + { "Reply-To", B_MAIL_ATTR_REPLY, B_STRING_TYPE }, + { "Subject", B_MAIL_ATTR_SUBJECT, B_STRING_TYPE }, + { "X-Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE }, // Priorities with prefered + { "Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE }, // one first - the numeric + { "X-Msmail-Priority", B_MAIL_ATTR_PRIORITY, B_STRING_TYPE }, // one (has more levels). + { "Mime-Version", B_MAIL_ATTR_MIME, B_STRING_TYPE }, + { "STATUS", B_MAIL_ATTR_STATUS, B_STRING_TYPE }, + { "THREAD", "MAIL:thread", B_STRING_TYPE }, //---Not supposed to be used for this (we add it in Parser), but why not? + { "NAME", B_MAIL_ATTR_NAME, B_STRING_TYPE }, + { NULL, NULL, 0 } +}; + + +class FolderFilter : public BMailFilter +{ + BString dest_string; + BDirectory destination; + int32 chain_id; + BMailChainRunner *runner; + int fNumberOfFilesSaved; + int size_limit; // Messages larger than this many bytes get partially downloaded. -1 for always do full download. + + public: + FolderFilter(BMessage*,BMailChainRunner *); + virtual ~FolderFilter(); + virtual status_t InitCheck(BString *err); + virtual status_t ProcessMailMessage + ( + BPositionIO** io_message, BEntry* io_entry, + BMessage* io_headers, BPath* io_folder, const char* io_uid + ); +}; + + +FolderFilter::FolderFilter(BMessage* msg,BMailChainRunner *therunner) + : BMailFilter(msg), + runner(therunner), chain_id(msg->FindInt32("chain")), size_limit(-1) +{ + fNumberOfFilesSaved = 0; + dest_string = runner->Chain()->MetaData()->FindString("path"); + create_directory(dest_string.String(),0777); + destination = dest_string.String(); + + if (msg->FindInt32("size_limit",(long *)&size_limit) != B_OK) + size_limit = -1; +} + + +FolderFilter::~FolderFilter () +{ + // Save the disk cache to the actual disk so mail data won't get lost if a + // crash happens soon after mail has been received or sent. Mostly put + // here because of unexpected mail daemon activity during debugging of + // kernel crashing software. + + if (fNumberOfFilesSaved > 0) + sync (); +} + + +status_t FolderFilter::InitCheck(BString* err) +{ + + status_t ret = destination.InitCheck(); + + if (ret==B_OK) return B_OK; + else + { + if (err) *err + << "FolderFilter failed: destination '" << dest_string + << "' not found (" << strerror(ret) << ")."; + return ret; + } +} + +status_t FolderFilter::ProcessMailMessage(BPositionIO**io, BEntry* e, BMessage* out_headers, BPath*loc, const char* io_uid) +{ + time_t dateAsTime; + const time_t *datePntr; + ssize_t dateSize; + char numericDateString [40]; + bool tempBool; + struct tm timeFields; + BString worker; + + BDirectory dir; + + BPath path = dest_string.String(); + if (out_headers->HasString("DESTINATION")) { + const char *string; + out_headers->FindString("DESTINATION",&string); + if (string[0] == '/') + path = string; + else + path.Append(string); + } else if (loc != NULL && loc->Path() != NULL && strcmp(loc->Path(),"") != 0) // --- Don't append folder names to overridden paths + path.Append(loc->Path()); + + create_directory(path.Path(),0777); + dir.SetTo(path.Path()); + + BNode node(e); + status_t err = 0; + bool haveReadWholeMessage = false; + // "ENTIRE_MESSAGE" really means the user has double clicked on a partial + // message and now it should be fully read, and then displayed to the user. + if ((out_headers->FindBool("ENTIRE_MESSAGE", &tempBool) == B_OK && tempBool) + || !out_headers->HasInt32("SIZE") + || (size_limit < 0 || size_limit >= out_headers->FindInt32("SIZE"))) { + err = (*io)->Seek(0,SEEK_END); // Force protocol to read the whole message. + if (err < 0) + { + BString error; + MDR_DIALECT_CHOICE ( + error << "Unable to read whole message from server, ignoring it. " + << "Subject \"" << out_headers->FindString("Subject") + << "\", save to dir \"" << path.Path() << + "\", error code: " << err << " " << strerror(err); + , + error << out_headers->FindString("Subject") << " のメッセージを " << + path.Path() << "に保存中にエラーが発生しました" << strerror(err); + ) + runner->ShowError(error.String()); + return B_MAIL_END_FETCH; // Stop reading further mail messages. + } + haveReadWholeMessage = true; + } + + BNodeInfo info(&node); + node.Sync(); + off_t size; + node.GetSize(&size); + // Note - sometimes the actual message size is a few bytes more than the + // registered size, so use >= when testing. And sometimes the message is + // actually slightly smaller, due to POP server errors (some count double + // dots correctly, some don't, if it causes problems, you'll get a partial + // message and waste time downloading it twice). + if (haveReadWholeMessage || + (out_headers->HasInt32("SIZE") && size >= out_headers->FindInt32("SIZE"))) { + info.SetType(B_MAIL_TYPE); + // A little fixup for incorrect registered sizes, so that the full + // length attribute gets written correctly later. + if (out_headers->HasInt32("SIZE")) + out_headers->ReplaceInt32("SIZE", size); + haveReadWholeMessage = true; + } else // Don't have the whole message. + info.SetType("text/x-partial-email"); + + BMessage attributes; + + attributes.AddString("MAIL:unique_id",io_uid); + attributes.AddString("MAIL:account",BMailChain(chain_id).Name()); + attributes.AddInt32("MAIL:chain",chain_id); + + size_t length = (*io)->Position(); + length -= out_headers->FindInt32(B_MAIL_ATTR_HEADER); + if (attributes.ReplaceInt32(B_MAIL_ATTR_CONTENT,length) != B_OK) + attributes.AddInt32(B_MAIL_ATTR_CONTENT,length); + + const char *buf; + time_t when; + for (int i = 0; gDefaultFields[i].rfc_name; ++i) + { + out_headers->FindString(gDefaultFields[i].rfc_name,&buf); + if (buf == NULL) + continue; + + switch (gDefaultFields[i].attr_type){ + case B_STRING_TYPE: + attributes.AddString(gDefaultFields[i].attr_name, buf); + break; + + case B_TIME_TYPE: + when = ParseDateWithTimeZone (buf); + if (when == -1) + when = time (NULL); // Use current time if it's undecodable. + attributes.AddData(B_MAIL_ATTR_WHEN, B_TIME_TYPE, &when, sizeof(when)); + break; + } + } + + if (out_headers->HasInt32("SIZE")) { + size_t size = out_headers->FindInt32("SIZE"); + attributes.AddData("MAIL:fullsize",B_SIZE_T_TYPE,&size,sizeof(size_t)); + } + + // add "New" status, if the status hasn't been set already + if (attributes.FindString(B_MAIL_ATTR_STATUS,&buf) < B_OK) + attributes.AddString(B_MAIL_ATTR_STATUS,"New"); + + node << attributes; + + // Move the message file out of the temporary directory, else it gets + // deleted. Partial messages have already been moved, so don't move them. + if (out_headers->FindBool("ENTIRE_MESSAGE", &tempBool) != B_OK + || tempBool == false) { + err = B_OK; + if (!dir.Contains(e)) + err = e->MoveTo(&dir); + if (err != B_OK) + { + BString error; + MDR_DIALECT_CHOICE ( + error << "An error occurred while moving the message " << + out_headers->FindString("Subject") << " to " << path.Path() << + ": " << strerror(err); + , + error << out_headers->FindString("Subject") << " のメッセージを " << + path.Path() << "に保存中にエラーが発生しました" << strerror(err); + ) + runner->ShowError(error.String()); + return err; + } + } + + // Generate a file name for the incoming message. See also + // Message::RenderTo which does a similar thing for outgoing messages. + + BString name = attributes.FindString("MAIL:subject"); + SubjectToThread (name); // Extract the core subject words. + if (name.Length() <= 0) + name = "No Subject"; + if (name[0] == '.') + name.Prepend ("_"); // Avoid hidden files, starting with a dot. + + // Convert the date into a year-month-day fixed digit width format, so that + // sorting by file name will give all the messages with the same subject in + // order of date. + dateAsTime = 0; + if (attributes.FindData(B_MAIL_ATTR_WHEN, B_TIME_TYPE, + (const void **) &datePntr, &dateSize) == B_OK) + dateAsTime = *datePntr; + localtime_r(&dateAsTime, &timeFields); + sprintf(numericDateString, "%04d%02d%02d%02d%02d%02d", + timeFields.tm_year + 1900, + timeFields.tm_mon + 1, + timeFields.tm_mday, + timeFields.tm_hour, + timeFields.tm_min, + timeFields.tm_sec); + name << " " << numericDateString; + + worker = attributes.FindString("MAIL:from"); + extract_address_name(worker); + name << " " << worker; + + name.Truncate(222); // reserve space for the uniquer + + // Get rid of annoying characters which are hard to use in the shell. + name.ReplaceAll('/','_'); + name.ReplaceAll('\'','_'); + name.ReplaceAll('"','_'); + name.ReplaceAll('!','_'); + name.ReplaceAll('<','_'); + name.ReplaceAll('>','_'); + while (name.FindFirst(" ") >= 0) // Remove multiple spaces. + name.Replace(" " /* Old */, " " /* New */, 1024 /* Count */); + + int32 uniquer = time(NULL); + worker = name; + int32 tries = 20; + while ((err = e->Rename(worker.String())) == B_FILE_EXISTS && --tries > 0) { + srand(rand()); + uniquer += (rand() >> 16) - 16384; + + worker = name; + worker << ' ' << uniquer; + } + if (err < B_OK) + printf("FolderFilter::ProcessMailMessage: could not rename mail (%s)! " + "(should be: %s)\n",strerror(err),worker.String()); + + fNumberOfFilesSaved++; + if (out_headers->FindBool("ENTIRE_MESSAGE")) { + entry_ref ref; + e->GetRef(&ref); + be_roster->Launch(&ref); + } + + return B_OK; +} + + +BMailFilter* instantiate_mailfilter(BMessage* settings, BMailChainRunner *run) +{ + return new FolderFilter(settings,run); +} + +class FolderConfig : public BView { + public: + FolderConfig(BMessage *settings, BMessage *meta_data) : BView(BRect(0,0,50,50),"folder_config",B_FOLLOW_ALL_SIDES,0) { + + const char *partial_text = MDR_DIALECT_CHOICE ( + "Partially download messages larger than", + "部分ダウンロードする"); + + view = new BMailFileConfigView( + MDR_DIALECT_CHOICE ("Destination Folder:","受信箱:"), + "path",true,"/boot/home/mail/in"); + view->SetTo(settings,meta_data); + view->ResizeToPreferred(); + + partial_box = new BCheckBox(BRect(view->Frame().left, view->Frame().bottom + 5, + view->Frame().left + 18 + be_plain_font->StringWidth(partial_text), + view->Frame().bottom + 25), "size_if", partial_text, new BMessage('SIZF')); + size = new BTextControl(BRect( + view->Frame().left + 20 + be_plain_font->StringWidth(partial_text), + view->Frame().bottom + 5, + view->Frame().left + 42 + be_plain_font->StringWidth(partial_text), + view->Frame().bottom + 25), "size", "", "", NULL); + AddChild(new BStringView(BRect( + view->Frame().left + 42 + be_plain_font->StringWidth(partial_text), + view->Frame().bottom + 5, view->Frame().right,view->Frame().bottom+21), + "kb", "KB")); + size->SetDivider(0); + if (settings->HasInt32("size_limit")) { + BString kb; + kb << int32(settings->FindInt32("size_limit")/1024); + size->SetText(kb.String()); + partial_box->SetValue(B_CONTROL_ON); + } else + size->SetEnabled(false); + AddChild(view); + SetViewColor(216,216,216); + AddChild(partial_box); + AddChild(size); + ResizeToPreferred(); + } + void MessageReceived(BMessage *msg) { + if (msg->what != 'SIZF') + return BView::MessageReceived(msg); + size->SetEnabled(partial_box->Value()); + } + void AttachedToWindow() { + partial_box->SetTarget(this); + } + void GetPreferredSize(float *width, float *height) { + view->GetPreferredSize(width,height); + *height += 25; + *width += 10; + } + status_t Archive(BMessage *into, bool) const { + into->MakeEmpty(); + view->Archive(into); + if (partial_box->Value()) + into->AddInt32("size_limit",atoi(size->Text()) * 1024); + return B_OK; + } + + + private: + BMailFileConfigView *view; + BTextControl *size; + BCheckBox *partial_box; +}; + +BView* instantiate_config_panel(BMessage *settings, BMessage *meta_data) +{ + + return new FolderConfig(settings,meta_data); +} + diff --git a/src/add-ons/mail_daemon/system_filters/notifier/ConfigView.cpp b/src/add-ons/mail_daemon/system_filters/notifier/ConfigView.cpp new file mode 100644 index 0000000000..258165af0f --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/notifier/ConfigView.cpp @@ -0,0 +1,160 @@ +/* ConfigView - the configuration view for the Notifier filter +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include "ConfigView.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +const uint32 kMsgNotifyMethod = 'nomt'; + + +ConfigView::ConfigView() + : BView(BRect(0,0,10,10),"notifier_config",B_FOLLOW_LEFT | B_FOLLOW_TOP,0) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + // determine font height + font_height fontHeight; + GetFontHeight(&fontHeight); + float itemHeight = (int32)(fontHeight.ascent + fontHeight.descent + fontHeight.leading) + 6; + + BRect frame(5,2,250,itemHeight + 2); + BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING,false,false); + + const char *notifyMethods[] = { + MDR_DIALECT_CHOICE ("Beep","音"), + MDR_DIALECT_CHOICE ("Alert","窓(メール毎)"), + MDR_DIALECT_CHOICE ("Keyboard LEDs","キーボードLED"), + MDR_DIALECT_CHOICE ("Central Alert","窓(一括)"), + "Central Beep","Log Window"}; + for (int32 i = 0,j = 1;i < 6;i++,j *= 2) + menu->AddItem(new BMenuItem(notifyMethods[i],new BMessage(kMsgNotifyMethod))); + + BMenuField *field = new BMenuField(frame,"notify", + MDR_DIALECT_CHOICE ("Method:","方法:"),menu); + field->ResizeToPreferred(); + field->SetDivider(field->StringWidth( + MDR_DIALECT_CHOICE ("Method:","方法:")) + 6); + AddChild(field); + + ResizeToPreferred(); +} + + +void ConfigView::AttachedToWindow() +{ + if (BMenuField *field = dynamic_cast(FindView("notify"))) + field->Menu()->SetTargetForItems(this); +} + + +void ConfigView::SetTo(BMessage *archive) +{ + int32 method = archive->FindInt32("notification_method"); + if (method < 0) + method = 1; + + BMenuField *field; + if ((field = dynamic_cast(FindView("notify"))) == NULL) + return; + + for (int32 i = field->Menu()->CountItems();i-- > 0;) + { + BMenuItem *item = field->Menu()->ItemAt(i); + item->SetMarked((method & (1L << i)) != 0); + } + UpdateNotifyText(); +} + + +void ConfigView::UpdateNotifyText() +{ + BMenuField *field; + if ((field = dynamic_cast(FindView("notify"))) == NULL) + return; + + BString label; + for (int32 i = field->Menu()->CountItems();i-- > 0;) + { + BMenuItem *item = field->Menu()->ItemAt(i); + if (!item->IsMarked()) + continue; + + if (label != "") + label.Prepend(" + "); + label.Prepend(item->Label()); + } + if (label == "") + label = "none"; + field->MenuItem()->SetLabel(label.String()); +} + + +void ConfigView::MessageReceived(BMessage *msg) +{ + switch (msg->what) + { + case kMsgNotifyMethod: + { + msg->PrintToStream(); + BMenuItem *item; + if (msg->FindPointer("source",(void **)&item) < B_OK) + break; + + item->SetMarked(!item->IsMarked()); + UpdateNotifyText(); + break; + } + default: + BView::MessageReceived(msg); + } +} + + +status_t ConfigView::Archive(BMessage *into,bool) const +{ + int32 method = 0; + + BMenuField *field; + if ((field = dynamic_cast(FindView("notify"))) != NULL) + { + for (int32 i = field->Menu()->CountItems();i-- > 0;) + { + BMenuItem *item = field->Menu()->ItemAt(i); + if (item->IsMarked()) + method |= 1L << i; + } + } + + if (into->ReplaceInt32("notification_method",method) != B_OK) + into->AddInt32("notification_method",method); + + return B_OK; +} + + +void ConfigView::GetPreferredSize(float *width, float *height) +{ + *width = 258; + *height = ChildAt(0)->Bounds().Height() + 8; +} + +BView* instantiate_config_panel(BMessage *settings,BMessage *) +{ + ConfigView *view = new ConfigView(); + view->SetTo(settings); + + return view; +} diff --git a/src/add-ons/mail_daemon/system_filters/notifier/ConfigView.h b/src/add-ons/mail_daemon/system_filters/notifier/ConfigView.h new file mode 100644 index 0000000000..229ed8fa0e --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/notifier/ConfigView.h @@ -0,0 +1,34 @@ +#ifndef CONFIG_VIEW +#define CONFIG_VIEW +/* ConfigView - the configuration view for the Notifier filter +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +enum { + do_beep = 1, + alert = 2, + blink_leds = 4, + big_doozy_alert = 8, + one_central_beep = 16, + log_window = 32 +}; + +class ConfigView : public BView +{ + public: + ConfigView(); + void SetTo(BMessage *archive); + virtual status_t Archive(BMessage *into, bool deep = true) const; + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage *msg); + virtual void GetPreferredSize(float *width, float *height); + + void UpdateNotifyText(); +}; + +#endif /* CONFIG_VIEW */ diff --git a/src/add-ons/mail_daemon/system_filters/notifier/Jamfile b/src/add-ons/mail_daemon/system_filters/notifier/Jamfile new file mode 100644 index 0000000000..166fe21c98 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/notifier/Jamfile @@ -0,0 +1,10 @@ +SubDir OBOS_TOP src add-ons mail_daemon system_filters notifier ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +Addon New\ Mail\ Notification : mail_daemon inbound_filters : + filter.cpp ConfigView.cpp ; + +LinkSharedOSLibs New\ Mail\ Notification : + be mail ; diff --git a/src/add-ons/mail_daemon/system_filters/notifier/filter.cpp b/src/add-ons/mail_daemon/system_filters/notifier/filter.cpp new file mode 100644 index 0000000000..4653b342c2 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/notifier/filter.cpp @@ -0,0 +1,134 @@ +/* New Mail Notification - notifies incoming e-mail +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "ConfigView.h" + +class NotifyFilter; + +class NotifyCallback : public BMailChainCallback { + public: + NotifyCallback (int32 notification_method, BMailChainRunner *us,NotifyFilter *ref2); + virtual void Callback(status_t result); + + uint32 num_messages; + private: + BMailChainRunner *chainrunner; + int32 strategy; + NotifyFilter *parent; +}; + +class NotifyFilter : public BMailFilter +{ + public: + NotifyFilter(BMessage*,BMailChainRunner*); + virtual status_t InitCheck(BString *err); + virtual status_t ProcessMailMessage + ( + BPositionIO** io_message, BEntry* io_entry, + BMessage* io_headers, BPath* io_folder, const char* io_uid + ); + + private: + friend class NotifyCallback; + + NotifyCallback *callback; + BMailChainRunner *_runner; + int32 strategy; +}; + + +NotifyFilter::NotifyFilter(BMessage* msg,BMailChainRunner *runner) + : BMailFilter(msg), _runner(runner), callback(NULL) +{ + strategy = msg->FindInt32("notification_method"); +} + +status_t NotifyFilter::InitCheck(BString* err) +{ + return B_OK; +} + +status_t NotifyFilter::ProcessMailMessage(BPositionIO**, BEntry*, BMessage*headers, BPath*, const char*) +{ + if (callback == NULL) { + callback = new NotifyCallback(strategy,_runner,this); + _runner->RegisterProcessCallback(callback); + } + + if (!headers->FindBool("ENTIRE_MESSAGE")) + callback->num_messages ++; + + return B_OK; +} + +NotifyCallback::NotifyCallback (int32 notification_method, BMailChainRunner *us,NotifyFilter *ref2) : + strategy(notification_method), + chainrunner(us), + num_messages(0), parent(ref2) +{ +} + +void NotifyCallback::Callback(status_t result) { + parent->callback = NULL; + + if (num_messages == 0) + return; + + if (strategy & do_beep) + system_beep("New E-mail"); + + if (strategy & alert) { + BString text; + MDR_DIALECT_CHOICE ( + text << "You have " << num_messages << " new message" << ((num_messages != 1) ? "s" : "") + << " for " << chainrunner->Chain()->Name() << ".", + + text << chainrunner->Chain()->Name() << "より\n" << num_messages << " 通のメッセージが届きました"); + + BAlert *alert = new BAlert(MDR_DIALECT_CHOICE ("New Messages","新着メッセージ"), text.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL); + alert->SetFeel(B_NORMAL_WINDOW_FEEL); + alert->Go(NULL); + } + + if (strategy & blink_leds) + be_app->PostMessage('mblk'); + + if (strategy & one_central_beep) + be_app->PostMessage('mcbp'); + + if (strategy & big_doozy_alert) { + BMessage msg('numg'); + msg.AddInt32("num_messages",num_messages); + msg.AddString("chain_name",chainrunner->Chain()->Name()); + msg.AddInt32("chain_id",chainrunner->Chain()->ID()); + + be_app->PostMessage(&msg); + } + + if (strategy & log_window) { + BString message; + message << num_messages << " new message" << ((num_messages != 1) ? "s" : ""); + chainrunner->ShowMessage(message.String()); + } +} + +BMailFilter* instantiate_mailfilter(BMessage* settings, BMailChainRunner *runner) +{ + return new NotifyFilter(settings,runner); +} + diff --git a/src/add-ons/mail_daemon/system_filters/outbox/Jamfile b/src/add-ons/mail_daemon/system_filters/outbox/Jamfile new file mode 100644 index 0000000000..782148572f --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/outbox/Jamfile @@ -0,0 +1,10 @@ +SubDir OBOS_TOP src add-ons mail_daemon system_filters outbox ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +Addon Outbox : mail_daemon inbound_filters : + filter.cpp ; + +LinkSharedOSLibs Outbox : + be mail ; diff --git a/src/add-ons/mail_daemon/system_filters/outbox/filter.cpp b/src/add-ons/mail_daemon/system_filters/outbox/filter.cpp new file mode 100644 index 0000000000..bc46f072c2 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/outbox/filter.cpp @@ -0,0 +1,108 @@ +/* Outbox - scans outgoing mail in a specific folder +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +class StatusChanger : public BMailChainCallback { + public: + StatusChanger(const char * entry); + void Callback(status_t result); + + private: + const char * to_change; +}; + +class DiskProducer : public BMailFilter +{ + BMailChainRunner *runner; + status_t init; + + public: + DiskProducer(BMessage*,BMailChainRunner*); + virtual status_t InitCheck(BString *err); + virtual status_t ProcessMailMessage + ( + BPositionIO** io_message, BEntry* io_entry, + BMessage* io_headers, BPath* io_folder, const char* io_uid + ); +}; + +DiskProducer::DiskProducer(BMessage* msg,BMailChainRunner*status) + : BMailFilter(msg), runner(status), init(B_OK) +{} + +status_t DiskProducer::InitCheck(BString* err) +{ + if (init != B_OK) + return init; + + return B_OK; +} + +status_t DiskProducer::ProcessMailMessage(BPositionIO**io, BEntry* e, BMessage* out_headers, BPath*, const char* io_uid) +{ + e->Remove(); + + BFile *file = new BFile(io_uid,B_READ_WRITE); + + e->SetTo(io_uid); + *file >> *out_headers; + *io = file; + + runner->RegisterMessageCallback(new StatusChanger(io_uid)); + + return B_OK; +} + +StatusChanger::StatusChanger(const char * entry) + : to_change(entry) +{ +} + +void StatusChanger::Callback(status_t result) { + BNode node(to_change); + + if (result == B_OK) { + mail_flags flags = B_MAIL_SENT; + + node.WriteAttr(B_MAIL_ATTR_FLAGS,B_INT32_TYPE,0,&flags,4); + node.WriteAttr(B_MAIL_ATTR_STATUS,B_STRING_TYPE,0,"Sent",5); + } else { + node.WriteAttr(B_MAIL_ATTR_STATUS,B_STRING_TYPE,0,"Error",6); + } +} + + +BMailFilter* instantiate_mailfilter(BMessage* settings, BMailChainRunner *runner) +{ + return new DiskProducer(settings,runner); +} + + +BView* instantiate_config_panel(BMessage *settings,BMessage *metadata) +{ + BMailFileConfigView *view = new BMailFileConfigView(MDR_DIALECT_CHOICE ("Source Folder:","送信箱:"),"path",true,"/boot/home/mail/out"); + view->SetTo(settings,metadata); + + return view; +} + diff --git a/src/add-ons/mail_daemon/system_filters/parser/Jamfile b/src/add-ons/mail_daemon/system_filters/parser/Jamfile new file mode 100644 index 0000000000..1837ebc5cd --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/parser/Jamfile @@ -0,0 +1,10 @@ +SubDir OBOS_TOP src add-ons mail_daemon system_filters parser ; + +UsePrivateHeaders mail ; +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +Addon Message\ Parser : mail_daemon inbound_filters : + filter.cpp ; + +LinkSharedOSLibs Message\ Parser : + be mail ; diff --git a/src/add-ons/mail_daemon/system_filters/parser/filter.cpp b/src/add-ons/mail_daemon/system_filters/parser/filter.cpp new file mode 100644 index 0000000000..5d69dbf336 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/parser/filter.cpp @@ -0,0 +1,91 @@ +/* Message Parser - parses the header of incoming e-mail +** +** Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include + +#include +#include + +class ParseFilter : public BMailFilter { + public: + ParseFilter(BMessage *msg); + + virtual status_t InitCheck(BString *err); + virtual status_t ProcessMailMessage(BPositionIO **ioMessage, BEntry *ioEntry, + BMessage *ioHeaders, BPath *ioFolder, const char *io_uid); + + private: + BString fNameField; +}; + + +ParseFilter::ParseFilter(BMessage *msg) + : BMailFilter(msg), + fNameField("From") +{ + const char *name = msg->FindString("name_field"); + if (name) + fNameField = name; +} + + +status_t +ParseFilter::InitCheck(BString* err) +{ + return B_OK; +} + + +status_t +ParseFilter::ProcessMailMessage(BPositionIO **data, BEntry */*entry*/, BMessage *headers, + BPath */*folder*/, const char */*uid*/) +{ + char byte; + (*data)->ReadAt(0,&byte, 1); + (*data)->Seek(SEEK_SET, 0); + + status_t status = parse_header(*headers, **data); + if (status < B_OK) + return status; + + // + // add pseudo-header THREAD, that contains the subject + // minus stuff in []s (added by mailing lists) and + // Re: prefixes, added by mailers when you reply. + // This will generally be the "thread subject". + // + BString string; + string.SetTo(headers->FindString("Subject")); + SubjectToThread(string); + headers->AddString("THREAD", string.String()); + + // name + if (headers->FindString(fNameField.String(), 0, &string) == B_OK) { + extract_address_name(string); + headers->AddString("NAME", string); + } + + // header length + headers->AddInt32(B_MAIL_ATTR_HEADER, (int32)((*data)->Position())); + // What about content length? If we do that, we have to D/L the + // whole message... + //--NathanW says let the disk consumer do that + + (*data)->Seek(0, SEEK_SET); + return B_OK; +} + + +BMailFilter * +instantiate_mailfilter(BMessage *settings, BMailChainRunner *) +{ + return new ParseFilter(settings); +} + diff --git a/src/add-ons/mail_daemon/system_filters/parser/retest.cpp b/src/add-ons/mail_daemon/system_filters/parser/retest.cpp new file mode 100644 index 0000000000..1622b64e65 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/parser/retest.cpp @@ -0,0 +1,22 @@ +/* To compile this test program, use this command line: +g++ -I../../include/numail -I../../include/public/ -I../../include/support/ -lbe -lmail -o Test -Wall retest.cpp +Then run Test with the subjects data file as input, or manually type in entries. +*/ +#include +#include +#include + +#include + +int main(int argc, char** argv) +{ + BString string; + char buf[1024]; + while (gets(buf)) + { + string = buf; + Zoidberg::Mail::SubjectToThread(string); + printf ("Input: \"%s\"\nOutput: \"%s\"\n\n", buf, string.String()); + } + return 0; +} diff --git a/src/add-ons/mail_daemon/system_filters/parser/subjects b/src/add-ons/mail_daemon/system_filters/parser/subjects new file mode 100644 index 0000000000..7642e8ad0a --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/parser/subjects @@ -0,0 +1,24 @@ +Re: [list]subject +Re: [list] subject +[list] Re: subject +[list]Re: subject +[list]Re[2]: subject +[list] Re [2] : subject +Re[2]:[list] subject +Re[2]:[list]subject +Re:[list]subject +Re [2] : [list] subject +Réf [2] : [list] subject +AW: Re [2]: [list] Réf: Re [5]: Fwd: Fwd [2]: subject +Re: [list]subject (fwd) +Re: [list] subject (fwd) +[list] Re: subject (fwd) +[list]Re: subject (fwd) +[list]Re[2]: subject (fwd) +[list] Re [2] : subject (fwd) +Re[2]:[list] subject (fwd) +Re[2]:[list]subject (fwd) +Re:[list]subject (fwd) +Re [2] : [list] subject (fwd) +Réf [2] : [list] subject (fwd) +AW: Re [2]: [list] Réf: Re [5]: Fwd: Fwd [2]: subject (fwd) diff --git a/src/add-ons/mail_daemon/system_filters/parser/thread.cpp b/src/add-ons/mail_daemon/system_filters/parser/thread.cpp new file mode 100644 index 0000000000..40de79fb64 --- /dev/null +++ b/src/add-ons/mail_daemon/system_filters/parser/thread.cpp @@ -0,0 +1 @@ +Moved to SubjectToThread in the mail library, AGMS 20030126. diff --git a/src/apps/Jamfile b/src/apps/Jamfile index 8d859fc9fb..5dfc27e935 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -1,6 +1,7 @@ SubDir OBOS_TOP src apps ; SubInclude OBOS_TOP src apps bin ; +SubInclude OBOS_TOP src apps bemail ; SubInclude OBOS_TOP src apps cdplayer ; SubInclude OBOS_TOP src apps clock ; SubInclude OBOS_TOP src apps codycam ; diff --git a/src/apps/bemail/BeMail.rsrc b/src/apps/bemail/BeMail.rsrc new file mode 100644 index 0000000000..bb1b6d9254 Binary files /dev/null and b/src/apps/bemail/BeMail.rsrc differ diff --git a/src/apps/bemail/BmapButton.cpp b/src/apps/bemail/BmapButton.cpp new file mode 100644 index 0000000000..5fbeef9af1 --- /dev/null +++ b/src/apps/bemail/BmapButton.cpp @@ -0,0 +1,387 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "BmapButton.h" +#include +#include +#include +#include +#include + +#ifndef B_BEOS_VERSION_5_0_4 +static rgb_color +mix_color(rgb_color color1, rgb_color color2, float portion) +{ + rgb_color ret; + ret.red = uint8(color1.red*portion + color2.red*(1-portion) + .5); + ret.green = uint8(color1.green*portion + color2.green*(1-portion) + .5); + ret.blue = uint8(color1.blue*portion + color2.blue*(1-portion) + .5); + ret.alpha = uint8(color1.alpha*portion + color2.alpha*(1-portion) + .5); + return ret; +} + +static inline rgb_color +disable_color(rgb_color color, rgb_color background) +{ + return mix_color(color, background, .5); +} +#endif // #ifndef B_BEOS_VERSION_5_0_4 + +BList BmapButton::fBitmapCache; +BLocker BmapButton::fBmCacheLock; + +struct BitmapItem +{ + BBitmap *bm; + int32 id; + int32 openCount; +}; + + +BmapButton::BmapButton(BRect frame, + const char *name, + const char *label, + int32 enabledID, + int32 disabledID, + int32 rollID, + int32 pressedID, + bool showLabel, + BMessage *message, + uint32 resizeMask, + uint32 flags) : + BControl(frame, name, label, message, resizeMask, flags), + fPressing(false), + fIsInBounds(false), + fShowLabel(showLabel), + fActive(true), + fIButtons(0) +{ + fEnabledBM = RetrieveBitmap(enabledID); + fDisabledBM = RetrieveBitmap(disabledID); + fRollBM = RetrieveBitmap(rollID); + fPressedBM = RetrieveBitmap(pressedID); +} + + +BmapButton::~BmapButton(void) +{ + ReleaseBitmap(fEnabledBM); + ReleaseBitmap(fDisabledBM); + ReleaseBitmap(fRollBM); + ReleaseBitmap(fPressedBM); +} + + +const BBitmap * +BmapButton::RetrieveBitmap(int32 id) +{ + // Lock access to the list + BAutolock lock(fBmCacheLock); + if (!lock.IsLocked()) + return NULL; + + // Check for the bitmap in the cache first + BitmapItem *item; + for (int32 i=0; (item=(BitmapItem *)fBitmapCache.ItemAt(i)) != NULL; i++) { + if (item->id == id) { + item->openCount++; + return item->bm; + } + } + + // If it's not in the cache, try to load it + BResources* res = BApplication::AppResources(); + if (!res) return NULL; + + size_t size = 0; + const void * data = res->LoadResource('BMAP', id, &size); + if (!data) return NULL; + BMemoryIO mio(data, size); + BMessage arch; + if (arch.Unflatten(&mio) != B_OK) return NULL; + + BArchivable* obj = instantiate_object(&arch); + BBitmap* bm = dynamic_cast(obj); + if (!bm) { + delete obj; + return NULL; + } + + item = (BitmapItem *)malloc(sizeof(BitmapItem)); + item->bm = bm; + item->id = id; + item->openCount = 1; + fBitmapCache.AddItem(item); + return bm; +} + + +status_t +BmapButton::ReleaseBitmap(const BBitmap *bm) +{ + // Lock access to the list + BAutolock lock(fBmCacheLock); + if (!lock.IsLocked()) + return B_ERROR; + + // Find the bitmap + BitmapItem *item; + for (int32 i=0; (item=(BitmapItem *)fBitmapCache.ItemAt(i)) != NULL; i++) { + if (item->bm == bm) { + // If it's no longer in use by any objects, free the resources + if (--item->openCount <= 0) { + fBitmapCache.RemoveItem(i); + delete item->bm; + free(item); + } + return B_OK; + } + } + return B_ERROR; +} + +#define F_SHOW_GEOMETRY 0 + +void +BmapButton::Draw(BRect updateRect) +{ + BRect bounds(Bounds()); + float labelHeight, labelWidth; + + #if F_SHOW_GEOMETRY + StrokeRect(bounds); + #endif + // Draw Label + if (fShowLabel) { + font_height fheight; + + BFont renderFont; + renderFont = *be_plain_font; + renderFont.GetHeight(&fheight); + SetFont(&renderFont); + + labelHeight = fheight.leading + fheight.ascent + fheight.descent + 1; + labelWidth = renderFont.StringWidth(Label())-2; + + BRect textRect; + textRect.left = (bounds.right-bounds.left-labelWidth+1)/2; + textRect.right = textRect.left+labelWidth; + textRect.bottom = bounds.bottom; + textRect.top = textRect.bottom-fheight.descent-fheight.ascent-1; + + // Only draw if it's within the update rect + if (updateRect.Intersects(textRect)) { + float baseLine = textRect.bottom-fheight.descent; + + if (IsFocus() && fActive) + SetHighColor(0, 0, 255); + else + SetHighColor(ViewColor()); + StrokeLine(BPoint(textRect.left, baseLine), + BPoint(textRect.right, baseLine)); + + if (IsEnabled()) + SetHighColor(0, 0, 0); + else { + const rgb_color black = { 0, 0, 0, 255 }; + SetHighColor(disable_color(black, ViewColor())); + } + MovePenTo(textRect.left, baseLine); + DrawString(Label()); + + #if F_SHOW_GEOMETRY + FrameRect(textRect); + #endif + } + } + else { + labelHeight = 0; + labelWidth = 0; + } + + // Draw Bitmap + + // Select the bitmap to use + const BBitmap *bm; + + if (!IsEnabled()) + bm = fDisabledBM; + else if (fPressing) { + if (fIsInBounds) + bm = fPressedBM; + else + bm = fRollBM; + } else { + if (fIsInBounds) + bm = fRollBM; + else + bm = fEnabledBM; + } + + // Draw the bitmap + if (bm) { + fBitmapRect = bm->Bounds(); + fBitmapRect.OffsetTo(0, 0); + fBitmapRect.OffsetBy((bounds.right-bounds.left-fBitmapRect.right-fBitmapRect.left)/2, + (bounds.bottom-bounds.top-labelHeight-fBitmapRect.bottom-fBitmapRect.top)/2); + // Update if within update rect + if (updateRect.Intersects(fBitmapRect)) { + DrawBitmap(bm, fBitmapRect); + #if F_SHOW_GEOMETRY + StrokeRect(fBitmapRect); + #endif + } + } +} + + +void +BmapButton::GetPreferredSize(float *width, float *height) +{ + BRect prefBounds; + + if (fEnabledBM) { + if (fShowLabel) { + float labelHeight, labelWidth; + font_height fheight; + BRect bmBounds(fEnabledBM->Bounds()); + BFont renderFont; + renderFont = *be_plain_font; + renderFont.GetHeight(&fheight); + SetFont(&renderFont); + + labelHeight = fheight.leading + fheight.ascent + fheight.descent + 1; + labelWidth = renderFont.StringWidth(Label()); + prefBounds.left = 0; + prefBounds.top = 0; + prefBounds.right = labelWidth > (bmBounds.right-bmBounds.left) ? labelWidth : + (bmBounds.right-bmBounds.left); + prefBounds.bottom = labelHeight + (bmBounds.bottom-bmBounds.top); + } else + prefBounds = fEnabledBM->Bounds(); + } else + prefBounds = Bounds(); + + *width = prefBounds.IntegerWidth(); + *height = prefBounds.IntegerHeight(); +} + + +void +BmapButton::MouseMoved(BPoint where, uint32 code, const BMessage *msg) +{ + // eliminate unused parameter warnings + (void)where; + (void)msg; + + if (IsEnabled() && fActive) { + switch(code) { + case B_ENTERED_VIEW: + fIsInBounds = true; + Invalidate(fBitmapRect); + break; + + case B_EXITED_VIEW: + fIsInBounds = false; + Invalidate(fBitmapRect); + break; + } + } +} + + +void +BmapButton::MouseDown(BPoint point) +{ + if (!IsEnabled()) + return; + // Save Mouse State + GetMouse(&point, &fButtons); + fWhere = point; + + if (fButtons & fIButtons) { + BMessage copy(*Message()); + copy.AddPoint("where", ConvertToScreen(fWhere)); + copy.AddInt32("buttons", fButtons); + Invoke(©); + return; + } + + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | B_SUSPEND_VIEW_FOCUS | B_NO_POINTER_HISTORY); + fPressing = true; + Invalidate(fBitmapRect); +} + + +void +BmapButton::MouseUp(BPoint where) +{ + if (atomic_and(&fPressing, 0)) { + SetMouseEventMask(0, 0); + if (Bounds().Contains(where) && IsEnabled()) { + BMessage copy(*Message()); + copy.AddPoint("where", ConvertToScreen(fWhere)); + copy.AddInt32("buttons", fButtons); + Invoke(©); + } + Invalidate(fBitmapRect); + } +} + + +void +BmapButton::ShowLabel(bool show) +{ + fShowLabel = show; +} + + +void +BmapButton::WindowActivated(bool active) +{ + fActive = active; + if (IsFocus() || fIsInBounds) { + fIsInBounds = false; + Invalidate(); + } + BControl::WindowActivated(active); +} + + +void +BmapButton::InvokeOnButton(uint32 button) +{ + fIButtons = button; +} + diff --git a/src/apps/bemail/BmapButton.h b/src/apps/bemail/BmapButton.h new file mode 100644 index 0000000000..f44b1afd82 --- /dev/null +++ b/src/apps/bemail/BmapButton.h @@ -0,0 +1,87 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _BMAP_BUTTON_H +#define _BMAP_BUTTON_H + +#include +#include +#include +#include + +class BBitmap; +class BResources; + +class BmapButton : public BControl { +public: + BmapButton(BRect frame, const char *name, const char *label, + int32 enabledID, int32 disabledID, int32 rollID, int32 pressedID, + bool showLabel, BMessage *message, uint32 resizeMask, + uint32 flags = B_WILL_DRAW | B_NAVIGABLE); + virtual ~BmapButton(void); + + // Hooks + virtual void Draw(BRect updateRect); + virtual void GetPreferredSize(float *width, float *height); + virtual void MouseMoved(BPoint where, uint32 code, const BMessage *msg); + virtual void MouseDown(BPoint point); + virtual void MouseUp(BPoint where); + virtual void WindowActivated(bool active); + + void InvokeOnButton(uint32 button); + void ShowLabel(bool show); + +protected: + const BBitmap *RetrieveBitmap(int32 id); + status_t ReleaseBitmap(const BBitmap *bm); + + const BBitmap *fEnabledBM; + const BBitmap *fDisabledBM; + const BBitmap *fRollBM; + const BBitmap *fPressedBM; + int32 fPressing; + int32 fIsInBounds; + uint32 fButtons; + bool fShowLabel; + bool fActive; + BRect fBitmapRect; + BPoint fWhere; + uint32 fIButtons; + +private: + static BList fBitmapCache; + static BLocker fBmCacheLock; +}; + +#endif // #ifndef _BMAP_BUTTON_H diff --git a/src/apps/bemail/ButtonBar.cpp b/src/apps/bemail/ButtonBar.cpp new file mode 100644 index 0000000000..7f90fb4efa --- /dev/null +++ b/src/apps/bemail/ButtonBar.cpp @@ -0,0 +1,185 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "ButtonBar.h" +#include +#include + +struct BBDivider { + float where; + float vmargin; + BmapButton *button; +}; + +static const int32 kDividerBlockSize = 8; + +ButtonBar::ButtonBar(BRect frame, const char *name, uint8 enabledOffset, uint8 disabledOffset, + uint8 rollOffset, uint8 pressedOffset, float Hmargin, float Vmargin, + uint32 resizeMask, int32 flags, border_style border) + : BBox(frame, name, resizeMask, flags, border), + fMaxHeight(0), + fMaxWidth(0), + fNextXOffset(Hmargin), + fHMargin(Hmargin), + fVMargin(Vmargin), + fEnabledOffset(enabledOffset), + fDisabledOffset(disabledOffset), + fRollOffset(rollOffset), + fPressedOffset(pressedOffset), + fDividerArray(NULL), + fDividers(0), + fShowLabels(true) +{ +} + +ButtonBar::~ButtonBar(void) +{ + if (fDividerArray) + free(fDividerArray); +} + +BmapButton *ButtonBar::AddButton(const char *label, int32 baseID, BMessage *msg) +{ + BmapButton *button; + + button = new BmapButton(BRect(0, 0, 31, 31), label, label, baseID+fEnabledOffset, + baseID+fDisabledOffset, baseID+fRollOffset, baseID+fPressedOffset, + fShowLabels, msg, B_FOLLOW_LEFT | B_FOLLOW_TOP); + + fButtonList.AddItem(button); + AddChild(button); + return button; +} + +void ButtonBar::Arrange(bool fixedWidth) +{ + // Reset Positioning Info + fNextXOffset = fHMargin; + fMaxHeight = 0; + fMaxWidth = 0; + + int32 i; + float width, height; + BmapButton *button; + + // Determine Largest button dimensions + for (i = 0; (button = (BmapButton *)fButtonList.ItemAt(i)) != NULL; i++) + { + button->GetPreferredSize(&width, &height); + if (height > fMaxHeight) + fMaxHeight = height; + if (width > fMaxWidth) + fMaxWidth = width; + } + + // Arrange buttons + for (i = 0; (button = (BmapButton *)fButtonList.ItemAt(i)) != NULL; i++) + { + button->MoveTo(fNextXOffset, fVMargin); + if (fixedWidth) { + button->ResizeTo(fMaxWidth, fMaxHeight); + fNextXOffset += fMaxWidth+fHMargin; + } else { + button->GetPreferredSize(&width, &height); + button->ResizeTo(width, fMaxHeight); + fNextXOffset += width+fHMargin; + } + } + + // Move dividers to match + for(i = 0; i < fDividers; i++) + { + if (fDividerArray[i].button) + fDividerArray[i].where = fDividerArray[i].button->Frame().right + floor(fHMargin/2); + else + fDividerArray[i].where = floor(fHMargin/2); + } +} + +void ButtonBar::GetPreferredSize(float *width, float *height) +{ + *width = fNextXOffset+fHMargin; + *height = fMaxHeight+(2*fVMargin); +} + +void ButtonBar::AttachedToWindow(void) +{ + if (Parent()) + SetViewColor(Parent()->ViewColor()); + BBox::AttachedToWindow(); +} + +void ButtonBar::Draw(BRect updateRect) +{ + BBox::Draw(updateRect); + rgb_color high = { 184, 184, 184, 255 }; + rgb_color low = { 232, 232, 232, 255 }; + BRect bounds = Bounds(); + float where, vmargin; + + BeginLineArray(fDividers*2); + for (int32 i=0; iShowLabel(show); + fShowLabels = show; +} diff --git a/src/apps/bemail/ButtonBar.h b/src/apps/bemail/ButtonBar.h new file mode 100644 index 0000000000..a7e17bfe05 --- /dev/null +++ b/src/apps/bemail/ButtonBar.h @@ -0,0 +1,80 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _BUTTON_BAR_H +#define _BUTTON_BAR_H + +#include + +#include "BmapButton.h" + +struct BBDivider; + +class ButtonBar : public BBox { +public: + ButtonBar(BRect frame, const char *name, uint8 enabledOffset, + uint8 disabledOffset, uint8 rollOffset, uint8 pressedOffset, + float Hmargin, float Vmargin, + uint32 resizeMask = B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, + int32 flags = B_NAVIGABLE_JUMP | B_FRAME_EVENTS | B_WILL_DRAW, + border_style border = B_FANCY_BORDER); + virtual ~ButtonBar( void ); + + // Hooks + virtual void GetPreferredSize(float *width, float *height); + virtual void AttachedToWindow(void); + virtual void Draw(BRect updateRect); + + void ShowLabels(bool show); + void Arrange(bool fixedWidth = true); + BmapButton *AddButton(const char *label, int32 baseID, BMessage *msg); + void AddDivider(float vmargin); + +protected: + float fMaxHeight; + float fMaxWidth; + float fNextXOffset; + float fHMargin; + float fVMargin; + uint8 fEnabledOffset; + uint8 fDisabledOffset; + uint8 fRollOffset; + uint8 fPressedOffset; + BList fButtonList; + BBDivider *fDividerArray; + int32 fDividers; + bool fShowLabels; +}; + +#endif // #ifndef _BUTTON_BAR_H diff --git a/src/apps/bemail/ComboBox.cpp b/src/apps/bemail/ComboBox.cpp new file mode 100644 index 0000000000..0c5603de6d --- /dev/null +++ b/src/apps/bemail/ComboBox.cpp @@ -0,0 +1,1965 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// +// ComboBox.cpp +// +// + +/* + TODO: + - Better up/down arrow handling (if text in input box matches a list item, + pressing down should select the next item, pressing up should select the + previous. If no item matched, the first or last item should be selected. + In any case, pressing up or down should show the popup if it is hidden + - Properly draw the label, taking alignment into account + - Draw nicer border around text input and popup window + - Escaping out of the popup menu should restore the text in the input to the + value it had previous to popping up the menu. + - Fix popup behavior when the widget is near the bottom of the screen. The + popup window should be able to go above the text input area. Also, the popup + should size itself in a smart manner so that it is small if there are few + choices and large if there are many and the window under it is big. Perhaps + the developer should be able to influence the size of the popup. + - Improve button drawing and (?) button behavior + - Fix and test enable/disable behavior + - Add auto-scrolling and/or drag-scrolling to the poup-menu + - Add support for other navigation keys, like page up, page down, home, end. + - Fix up choice functions (remove choice, add at index, etc) and make sure they + properly invalidate/scroll/etc the list when it is visible + - Change auto-complete behavior to be non-greedy, or perhaps add some type of + tab-cycling to the choices + - Add mode whereby you can pop up a list of only those items that match +*/ + +#include +#include +#include +#include +#include +#include +#include // for menu_info +#include +#include "ObjectList.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "ComboBox.h" + +//static const uint32 kTextControlInvokeMessage = 'tCIM'; +static const uint32 kTextInputModifyMessage = 'tIMM'; +static const uint32 kPopupButtonInvokeMessage = 'pBIM'; +static const uint32 kPopupWindowHideMessage = 'pUWH'; +static const uint32 kWindowMovedMessage = 'wMOV'; + +static const float kTextInputMargin = (float)3.0; +static const float kLabelRightMargin = (float)6.0; +static const float kButtonWidth = (float)15.0; + +#define disable_color(_c_) tint_color(_c_, B_DISABLED_LABEL_TINT) + +#define TV_MARGIN 3.0 +#define TV_DIVIDER_MARGIN 6.0 + +rgb_color create_color(uchar r, uchar g, uchar b, uchar a = 255); + +rgb_color create_color(uchar r, uchar g, uchar b, uchar a) { + rgb_color col; + col.red = r; + col.green = g; + col.blue = b; + col.alpha = a; + return col; +} + +class StringObjectList : public BObjectList {}; + +// ---------------------------------------------------------------------------- + +// ChoiceListView is similar to a BListView, but it's implementation is tied to +// BComboBox. BListView is not used because it requires that a BStringItem be +// created for each choice. ChoiceListView just pulls the choice strings +// directly from the BComboBox and draws them. +class BComboBox::ChoiceListView : public BView +{ + public: + ChoiceListView( BRect frame, BComboBox *parent); + virtual ~ChoiceListView(); + + virtual void Draw(BRect update); + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage *dragMessage); + virtual void KeyDown(const char *bytes, int32 numBytes); + virtual void SetFont(const BFont *font, uint32 properties = B_FONT_ALL); + + void ScrollToSelection(); + void InvalidateItem(int32 index, bool force = false); + BRect ItemFrame(int32 index); + void AdjustScrollBar(); + // XXX: add BArchivable functionality + + private: + inline float LineHeight(); + + BPoint fClickLoc; + font_height fFontHeight; + bigtime_t fClickTime; + rgb_color fForeCol; + rgb_color fBackCol; + rgb_color fSelCol; + int32 fSelIndex; + BComboBox *fParent; + bool fTrackingMouseDown; +}; + + +// ---------------------------------------------------------------------------- + +// TextInput is a somewhat modified version of the _BTextInput_ class defined +// in TextControl.cpp. + +class BComboBox::TextInput : public BTextView +{ + public: + TextInput(BRect rect, BRect trect, ulong rMask, ulong flags); + TextInput(BMessage *data); + virtual ~TextInput(); + static BArchivable *Instantiate(BMessage *data); + virtual status_t Archive(BMessage *data, bool deep = true) const; + + virtual void KeyDown(const char *bytes, int32 numBytes); + virtual void MakeFocus(bool state); + virtual void FrameResized(float x, float y); + virtual void Paste(BClipboard *clipboard); + + void AlignTextRect(); + + void SetInitialText(); + + // XXX: add BArchivable functionality + + protected: + virtual void InsertText(const char *inText, int32 inLength, int32 inOffset, + const text_run_array *inRuns); + virtual void DeleteText(int32 fromOffset, int32 toOffset); + + private: + char *fInitialText; + bool fClean; +}; + +// ---------------------------------------------------------------------------- + +class BComboBox::ComboBoxWindow : public BWindow +{ + public: + ComboBoxWindow(BComboBox *box); + virtual ~ComboBoxWindow(); + virtual void WindowActivated(bool active); + virtual void FrameResized(float width, float height); + + void DoPosition(); + BComboBox::ChoiceListView *ListView(); + BScrollBar *ScrollBar(); + + // XXX: add BArchivable functionality + + private: + BScrollBar *fScrollBar; + ChoiceListView *fListView; + BComboBox *fParent; +}; + +// ---------------------------------------------------------------------------- + +// In BeOS R4.5, SetEventMask(B_POINTER_EVENTS, ...) does not work for getting +// all mouse events as they happen. Specifically, when the user clicks on the +// window dressing (the borders or the title tab) no mouse event will be +// delivered until after the user releases the mouse button. This has the +// unfortunate side effect of allowing the user to move the window that +// contains the BComboBox around with no notification being sent to the +// BComboBox. We need to intercept the B_WINDOW_MOVED messages so that we can +// hide the popup window when the window moves. + +class BComboBox::MovedMessageFilter : public BMessageFilter +{ + public: + MovedMessageFilter(BHandler *target); + virtual filter_result Filter(BMessage *message, BHandler **target); + + private: + BHandler *fTarget; +}; + + +// ---------------------------------------------------------------------------- + + +BComboBox::ChoiceListView::ChoiceListView(BRect frame, BComboBox *parent) + : BView(frame, "_choice_list_view_", B_FOLLOW_ALL_SIDES, B_WILL_DRAW + | B_NAVIGABLE), + fClickLoc(-100, -100) +{ + fParent = parent; + GetFontHeight(&fFontHeight); + menu_info mi; + get_menu_info(&mi); + fForeCol = create_color(0, 0, 0); + fBackCol = mi.background_color; + fSelCol = create_color(144, 144, 144); + SetViewColor(B_TRANSPARENT_COLOR); + SetHighColor(fForeCol); + fTrackingMouseDown = false; + fClickTime = 0; +} + + +BComboBox::ChoiceListView::~ChoiceListView() +{ +} + + +void BComboBox::ChoiceListView::Draw(BRect update) +{ + float h = LineHeight(); + BRect rect(Bounds()); + int32 index; + int32 choices = fParent->fChoiceList->CountChoices(); + int32 selected = (fTrackingMouseDown) ? fSelIndex : fParent->CurrentSelection(); + + // draw each visible item + for (index = (int32)floor(update.top / h); index < choices; index++) + { + rect.top = index * h; + rect.bottom = rect.top + h; + SetLowColor((index == selected) ? fSelCol : fBackCol); + FillRect(rect, B_SOLID_LOW); + DrawString(fParent->fChoiceList->ChoiceAt(index), BPoint(rect.left + 2, + rect.bottom - fFontHeight.descent - 1)); + } + + // draw empty area on bottom + if (rect.bottom < update.bottom) + { + update.top = rect.bottom; + SetLowColor(fBackCol); + FillRect(update, B_SOLID_LOW); + } +} + + +void BComboBox::ChoiceListView::MouseDown(BPoint where) +{ + BRect rect(Window()->Frame()); + ConvertFromScreen(&rect); + if (!rect.Contains(where)) + { + // hide the popup window when the user clicks outside of it + if (fParent->Window()->Lock()) + { + fParent->HidePopupWindow(); + fParent->Window()->Unlock(); + } + + // HACK: the window is locked and unlocked so that it will get + // activated before we potentially send the mouse down event in the + // code below. Is there a way to wait until the window is activated + // before sending the mouse down? Should we call + // fParent->Window()->MakeActive(true) here? + + if (fParent->Window()->Lock()) + { + // resend the mouse event to the textinput, if necessary + BTextView *text = fParent->TextView(); + BPoint screenWhere(ConvertToScreen(where)); + rect = text->Window()->ConvertToScreen(text->Frame()); + if (rect.Contains(screenWhere)) + { + //printf(" resending mouse down to textinput\n"); + BMessage *msg = new BMessage(*Window()->CurrentMessage()); + msg->RemoveName("be:view_where"); + text->ConvertFromScreen(&screenWhere); + msg->AddPoint("be:view_where", screenWhere); + text->Window()->PostMessage(msg, text); + delete msg; + } + fParent->Window()->Unlock(); + } + + return; + } + + rect = Bounds(); + if (!rect.Contains(where)) + return; + + fTrackingMouseDown = true; + // check for double click + bigtime_t now = system_time(); + bigtime_t clickSpeed; + get_click_speed(&clickSpeed); + if ((now - fClickTime < clickSpeed) + && ((abs((int)(fClickLoc.x - where.x)) < 3) + && (abs((int)(fClickLoc.y - where.y)) < 3))) + { + // this is a double click + // XXX: what to do here? + printf("BComboBox::ChoiceListView::MouseDown() -- unhandled double click\n"); + } + fClickTime = now; + fClickLoc = where; + + float h = LineHeight(); + int32 oldIndex = fSelIndex; + fSelIndex = (int32)floor(where.y / h); + int32 choices = fParent->fChoiceList->CountChoices(); + if (fSelIndex < 0 || fSelIndex >= choices) + fSelIndex = -1; + + if (oldIndex != fSelIndex) + { + InvalidateItem(oldIndex); + InvalidateItem(fSelIndex); + } + // XXX: this probably isn't necessary since we are doing a SetEventMask + // whenever the popup window becomes visible which routes all mouse events + // to this view +// SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); +} + + +void BComboBox::ChoiceListView::MouseUp(BPoint /*where*/) +{ + if (fTrackingMouseDown) + { + fTrackingMouseDown = false; + if (fSelIndex >= 0) + fParent->Select(fSelIndex, true); + else + fParent->Deselect(); + } +// fClickLoc = where; +} + + +void BComboBox::ChoiceListView::MouseMoved(BPoint where, uint32 /*transit*/, + const BMessage */*dragMessage*/) +{ + if (fTrackingMouseDown) + { + float h = LineHeight(); + int32 oldIndex = fSelIndex; + fSelIndex = (int32)floor(where.y / h); + int32 choices = fParent->fChoiceList->CountChoices(); + if (fSelIndex < 0 || fSelIndex >= choices) + fSelIndex = -1; + + if (oldIndex != fSelIndex) + { + InvalidateItem(oldIndex); + InvalidateItem(fSelIndex); + } + } +} + + +void BComboBox::ChoiceListView::KeyDown(const char *bytes, int32 /*numBytes*/) +{ + BComboBox *cb = fParent; + BWindow *win = cb->Window(); + BComboBox::TextInput *text = dynamic_cast(cb->TextView()); + uchar aKey = bytes[0]; + + switch (aKey) + { + case B_UP_ARROW: // fall through + case B_DOWN_ARROW: + if (win->Lock()) + { + // change the selection + int32 index = cb->CurrentSelection(); + int32 choices = cb->fChoiceList->CountChoices(); + if (choices > 0) + { + if (index < 0) + { + // no previous selection, so select first or last item + // depending on whether this is a up or down arrow + cb->Select((aKey == B_UP_ARROW) ? choices - 1 : 0); + } + else + { + // select the previous or the next item, if possible, + // depending on whether this is an up or down arrow + if (aKey == B_UP_ARROW && (index - 1 >= 0)) + cb->Select(index - 1, true); + else if (aKey == B_DOWN_ARROW && (index + 1 < choices)) + cb->Select(index + 1, true); + } + } + win->Unlock(); + } + break; + default: + { // send all other key down events to the text input view + BMessage *msg = Window()->DetachCurrentMessage(); + if (msg) { + win->PostMessage(msg, text); + delete msg; + } + break; + } + } +} + + +void BComboBox::ChoiceListView::SetFont(const BFont *font, uint32 properties) +{ + BView::SetFont(font, properties); + GetFontHeight(&fFontHeight); + Invalidate(); +} + + +void BComboBox::ChoiceListView::ScrollToSelection() +{ + int32 selected = fParent->CurrentSelection(); + if (selected >= 0) + { + BRect frame(ItemFrame(selected)); + BRect bounds(Bounds()); + float newY = -1.0; // dummy value -- not used + bool doScroll = false; + + if (frame.bottom > bounds.bottom) + { + newY = frame.bottom - bounds.Height(); + doScroll = true; + } + else if (frame.top < bounds.top) + { + newY = frame.top; + doScroll = true; + } + if (doScroll) + ScrollTo(bounds.left, newY); + } +} + +// InvalidateItem() only does a real invalidate if the index is valid or the +// force flag is turned on + +void BComboBox::ChoiceListView::InvalidateItem(int32 index, bool force) +{ + int32 choices = fParent->fChoiceList->CountChoices(); + if ((index >= 0 && index < choices) || force) { + Invalidate(ItemFrame(index)); + } +} + +// This method doesn't check the index to see if it is valid, it just returns +// the BRect that an item and the index would have if it existed. + +BRect BComboBox::ChoiceListView::ItemFrame(int32 index) +{ + BRect rect(Bounds()); + float h = LineHeight(); + rect.top = index * h; + rect.bottom = rect.top + h; + return rect; +} + +// The window must be locked before this method is called + +void BComboBox::ChoiceListView::AdjustScrollBar() +{ + BScrollBar *sb = ScrollBar(B_VERTICAL); + if (sb) { + float h = LineHeight(); + float max = h * fParent->fChoiceList->CountChoices(); + BRect frame(Frame()); + float diff = max - frame.Height(); + float prop = frame.Height() / max; + if (diff < 0) { + diff = 0.0; + prop = 1.0; + } + sb->SetSteps(h, h * (frame.IntegerHeight() / h)); + sb->SetRange(0.0, diff); + sb->SetProportion(prop); + } +} + + +float BComboBox::ChoiceListView::LineHeight() +{ + return fFontHeight.ascent + fFontHeight.descent + fFontHeight.leading + 2; +} + + +// ---------------------------------------------------------------------------- +// #pragma mark - + + +BComboBox::TextInput::TextInput(BRect rect, BRect text_r, ulong rMask, + ulong flags) + : BTextView(rect, "_input_", text_r, be_plain_font, NULL, rMask, flags) +{ + MakeResizable(TRUE); + fInitialText = NULL; + fClean = FALSE; +} + + +BComboBox::TextInput::~TextInput() +{ + if (fInitialText) { + free(fInitialText); + fInitialText = NULL; + } +} + + +BComboBox::TextInput::TextInput(BMessage *data) + : BTextView(data) +{ + MakeResizable(TRUE); + fInitialText = NULL; + fClean = FALSE; +} + + +status_t BComboBox::TextInput::Archive(BMessage *data, bool) const +{ + BTextView::Archive(data); + return 0; +} + + +BArchivable *BComboBox::TextInput::Instantiate(BMessage *data) +{ + // XXX: is "TextInput" the correct name for this class? Perhaps + // BComboBox::TextInput? + if (!validate_instantiation(data, "TextInput")) + return NULL; + return new TextInput(data); +} + + +void BComboBox::TextInput::SetInitialText() +{ + if (fInitialText) + { + free(fInitialText); + fInitialText = NULL; + } + if (Text()) + fInitialText = strdup(Text()); +} + + +void BComboBox::TextInput::KeyDown(const char *bytes, int32 numBytes) +{ + BComboBox *cb; + uchar aKey = bytes[0]; + + switch (aKey) + { + case B_RETURN: + cb = cast_as(Parent(), BComboBox); + + ASSERT(cb); + + if (!cb->IsEnabled()) + break; + + ASSERT(fInitialText); + if (strcmp(fInitialText, Text()) != 0) + cb->CommitValue(); + free(fInitialText); + fInitialText = strdup(Text()); + { + int32 end = TextLength(); + Select(end, end); + } + // hide popup window if it's showing when the user presses the + // enter key + if (cb->fPopupWindow && cb->fPopupWindow->Lock()) { + if (!cb->fPopupWindow->IsHidden()) { + cb->HidePopupWindow(); + } + cb->fPopupWindow->Unlock(); + } + break; + case B_TAB: +// cb = cast_as(Parent(), BComboBox); +// ASSERT(cb); +// if (cb->fAutoComplete && cb->fCompletionIndex >= 0) { +// int32 from, to; +// cb->fText->GetSelection(&from, &to); +// if (from == to) { +// // HACK: this should never happen. The rest of the class +// // should be fixed so that fCompletionIndex is set to -1 if the +// // text is modified +// printf("BComboBox::TextInput::KeyDown() -- HACK! this shouldn't happen!"); +// cb->fCompletionIndex = -1; +// } +// +// const char *text = cb->fText->Text(); +// BString prefix; +// prefix.Append(text, from); +// +// int32 match; +// const char *completion; +// if (cb->fChoiceList->GetMatch( prefix.String(), +// cb->fCompletionIndex + 1, +// &match, +// &completion) == B_OK) +// { +// cb->fText->Delete(); // delete the selection +// cb->fText->Insert(completion); +// cb->fText->Select(from, from + strlen(completion)); +// cb->fCompletionIndex = match; +// cb->Select(cb->fCompletionIndex); +// } else { +// //system_beep(); +// } +// } else { + BView::KeyDown(bytes, numBytes); +// } + break; +#if 0 + case B_UP_ARROW: // fall through + case B_DOWN_ARROW: + cb = cast_as(Parent(), BComboBox); + ASSERT(cb); + if (cb->fChoiceList) { + cb = cast_as(Parent(), BComboBox); + ASSERT(cb); + if (!(cb->fPopupWindow)) { + cb->fPopupWindow = cb->CreatePopupWindow(); + } + if (cb->fPopupWindow->Lock()) { + // show popup window, if needed + if (cb->fPopupWindow->IsHidden()) { + cb->ShowPopupWindow(); + } else { + printf("Whoa!!! Erroneously got up/down arrow key down in TextInput::KeyDown()!\n"); + } + int32 index = cb->CurrentSelection(); + int32 choices = cb->fChoiceList->CountChoices(); + // select something, if no selection + if (index < 0 && choices > 0) { + if (aKey == B_UP_ARROW) { + cb->Select(choices - 1); + } else { + cb->Select(0); + } + } + cb->fPopupWindow->Unlock(); + } + } + break; +#endif + case B_ESCAPE: + cb = cast_as(Parent(), BComboBox); + ASSERT(cb); + if (cb->fChoiceList) + { + cb = cast_as(Parent(), BComboBox); + ASSERT(cb); + if (cb->fPopupWindow && cb->fPopupWindow->Lock()) + { + if (!cb->fPopupWindow->IsHidden()) + cb->HidePopupWindow(); + + cb->fPopupWindow->Unlock(); + } + } + break; + case ',': + { + int32 startSel, endSel; + GetSelection(&startSel, &endSel); + int32 length = TextLength(); + if (endSel == length) + Select(endSel, endSel); + BTextView::KeyDown(bytes, numBytes); + } + break; + default: + BTextView::KeyDown(bytes, numBytes); + break; + } +} + + +void BComboBox::TextInput::MakeFocus(bool state) +{ +//+ PRINT(("_BTextInput_::MakeFocus(state=%d, view=%s)\n", state, +//+ Parent()->Name())); + if (state == IsFocus()) + return; + + BComboBox *parent = cast_as(Parent(), BComboBox); + ASSERT(parent); + + BTextView::MakeFocus(state); + + if (state) + { + SetInitialText(); + fClean = TRUE; // text hasn't been dirtied yet. + + BMessage *m; + if (Window() && (m = Window()->CurrentMessage()) != 0 && m->what == B_KEY_DOWN) + { + // we're being focused by a keyboard event, so + // select all... + SelectAll(); + } + } + else + { + ASSERT(fInitialText); + if (strcmp(fInitialText, Text()) != 0) + parent->CommitValue(); + + free(fInitialText); + fInitialText = NULL; + fClean = FALSE; + BMessage *m; + if (Window() && (m = Window()->CurrentMessage()) != 0 && m->what == B_MOUSE_DOWN) + Select(0,0); + + // hide popup window if it's showing when the text input loses focus + if (parent->fPopupWindow && parent->fPopupWindow->Lock()) + { + if (!parent->fPopupWindow->IsHidden()) + parent->HidePopupWindow(); + + parent->fPopupWindow->Unlock(); + } + } + + // make sure the focus indicator gets drawn or undrawn + if (Window()) + { + BRect invalRect(Bounds()); + invalRect.InsetBy(-kTextInputMargin, -kTextInputMargin); + parent->Draw(invalRect); + parent->Flush(); + } +} + + +void BComboBox::TextInput::FrameResized(float x, float y) +{ + BTextView::FrameResized(x, y); + AlignTextRect(); +} + + +void BComboBox::TextInput::Paste(BClipboard *clipboard) +{ + BTextView::Paste(clipboard); + Invalidate(); +} + + +// What a hack... +void BComboBox::TextInput::AlignTextRect() +{ + BRect bounds = Bounds(); + BRect textRect = TextRect(); + + switch (Alignment()) + { + case B_ALIGN_LEFT: + textRect.OffsetTo(B_ORIGIN); + break; + + case B_ALIGN_CENTER: + textRect.OffsetTo((bounds.Width() - textRect.Width()) / 2, + textRect.top); + break; + + case B_ALIGN_RIGHT: + textRect.OffsetTo(bounds.Width() - textRect.Width(), textRect.top); + break; + } + + SetTextRect(textRect); +} + + +void BComboBox::TextInput::InsertText(const char *inText, int32 inLength, + int32 inOffset, const text_run_array *inRuns) +{ + char *ptr = NULL; + + // strip out any return characters + // limiting to a reasonable amount of chars for a text control. + // otherwise this code could malloc some huge amount which isn't good. + if (strpbrk(inText, "\r\n") && (inLength <= 1024)) + { + int32 len = inLength; + ptr = (char *) malloc(len+1); + if (ptr) + { + strncpy(ptr, inText, len); + ptr[len] = '\0'; + + char *p = ptr; + + while (len--) { + if (*p == '\n') + *p = ' '; + else if (*p == '\r') + *p = ' '; + + p++; + } + } + } + + BTextView::InsertText(ptr ? ptr : inText, inLength, inOffset, inRuns); + + BComboBox *parent = dynamic_cast(Parent()); + if (parent) + { + if (parent->fModificationMessage) + parent->Invoke(parent->fModificationMessage); + + BMessage *msg; + parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage), + parent); + delete msg; + } + + if (ptr) + free(ptr); +} + + +void BComboBox::TextInput::DeleteText(int32 fromOffset, int32 toOffset) +{ + BTextView::DeleteText(fromOffset, toOffset); + BComboBox *parent = dynamic_cast(Parent()); + if (parent) + { + if (parent->fModificationMessage) { + parent->Invoke(parent->fModificationMessage); + } + BMessage *msg; + parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage), + parent); + delete msg; + } +} + + +// ---------------------------------------------------------------------------- +// #pragma mark - + + +BComboBox::ComboBoxWindow::ComboBoxWindow(BComboBox *box) + : BWindow(BRect(0, 0, 10, 10), NULL, B_BORDERED_WINDOW_LOOK, + B_FLOATING_SUBSET_WINDOW_FEEL, B_NOT_MOVABLE | B_NOT_RESIZABLE + | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE + | B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS) +{ + fParent = box; + DoPosition(); + BWindow *parentWin = fParent->Window(); + if (parentWin) + AddToSubset(parentWin); + + BRect rect(Bounds()); + rect.right -= B_V_SCROLL_BAR_WIDTH; + fListView = new ChoiceListView(rect, fParent); + AddChild(fListView); + rect.left = rect.right; + rect.right += B_V_SCROLL_BAR_WIDTH; + fScrollBar = new BScrollBar(rect, "_popup_scroll_bar_", fListView, 0, 1000, + B_VERTICAL); + AddChild(fScrollBar); + fListView->AdjustScrollBar(); +} + + +BComboBox::ComboBoxWindow::~ComboBoxWindow() +{ + fListView->RemoveSelf(); + delete fListView; +} + + +void BComboBox::ComboBoxWindow::WindowActivated(bool /*active*/) +{ +// if (active) +// fListView->AdjustScrollBar(); +} + + +void BComboBox::ComboBoxWindow::FrameResized(float /*width*/, float /*height*/) +{ + fListView->AdjustScrollBar(); +} + + +void BComboBox::ComboBoxWindow::DoPosition() +{ + BRect winRect(fParent->fText->Frame()); + winRect = fParent->ConvertToScreen(winRect); +// winRect.left += fParent->Divider() + 5; + winRect.right -= 2; + winRect.OffsetTo(winRect.left, winRect.bottom + kTextInputMargin); + winRect.bottom = winRect.top + 100; + MoveTo(winRect.LeftTop()); + ResizeTo(winRect.IntegerWidth(), winRect.IntegerHeight()); +} + + +BComboBox::ChoiceListView *BComboBox::ComboBoxWindow::ListView() +{ + return fListView; +} + + +BScrollBar *BComboBox::ComboBoxWindow::ScrollBar() +{ + return fScrollBar; +} + + +// ---------------------------------------------------------------------------- +// #pragma mark - + + +BComboBox::BComboBox(BRect frame, const char *name, const char *label, + BMessage *message, uint32 resizeMask, uint32 flags) + : BControl(frame, name, label, message, resizeMask, + flags | B_WILL_DRAW | B_FRAME_EVENTS), + fPopupWindow(NULL), + fModificationMessage(NULL), + fChoiceList(0), + fLabelAlign(B_ALIGN_LEFT), + fAutoComplete(false), + fButtonDepressed(false), + fDepressedWhenClicked(false), + fTrackingButtonDown(false), + fFrameCache(frame) +{ + // If the user wants this control to be keyboard navigable, then we really + // want the underlying text view to be navigable, not this view. + bool navigate = ((Flags() & B_NAVIGABLE) != 0); + if (navigate) + { + fSkipSetFlags = true; + SetFlags(Flags() & ~B_NAVIGABLE); // disable navigation for this + fSkipSetFlags = false; + } + + fDivider = StringWidth(label); + + BRect rect(frame); + rect.OffsetTo(0, 0); + rect.left += fDivider + kLabelRightMargin; +// rect.right -= kButtonWidth + 1; +// rect.right; + rect.InsetBy(kTextInputMargin, kTextInputMargin); + BRect textRect(rect); + textRect.OffsetTo(0, 0); + textRect.left += 2; + textRect.right -= 2; + + fText = new TextInput(rect, textRect, B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, + B_WILL_DRAW | B_FRAME_EVENTS | (navigate ? B_NAVIGABLE : 0)); + float height = fText->LineHeight(); + rect.bottom = rect.top + height; +// fText->ResizeTo(rect.IntegerWidth(), height); + AddChild(fText); + + font_height fontInfo; + GetFontHeight(&fontInfo); + float h1 = ceil(fontInfo.ascent + fontInfo.descent + fontInfo.leading); + float h2 = fText->LineHeight(); + + // Height of main view must be the larger of h1 and h2+(TV_MARGIN*2) + float h = (h1 > h2 + (TV_MARGIN*2)) ? h1 : h2 + (TV_MARGIN*2); + BRect b = Bounds(); + ResizeTo(b.Width(), h); + b.bottom = h; + + // set height and position of text entry view + fText->ResizeTo(fText->Bounds().Width(), h2); + // vertically center this view + fText->MoveBy(0, (b.Height() - (h2+(TV_MARGIN*2))) / 2); + + rect.left = rect.right + 1; + rect.right = rect.left + kButtonWidth; + + fButtonRect = rect; + fTextEnd = 0; + fSelected = -1; + fCompletionIndex = -1; + fWinMovedFilter = new MovedMessageFilter(this); +} + + +BComboBox::~BComboBox() +{ + if (fPopupWindow && fPopupWindow->Lock()) + fPopupWindow->Quit(); + + RemoveChild(fText); + delete fText; + + if (fWinMovedFilter->Looper()) + fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter); + + delete fWinMovedFilter; + +} + + +void BComboBox::SetChoiceList(BChoiceList *list) +{ +// delete fChoiceList; + fChoiceList = list; + ChoiceListUpdated(); +} + + +BChoiceList *BComboBox::ChoiceList() +{ + return fChoiceList; +} + + +void BComboBox::ChoiceListUpdated() +{ + if (fPopupWindow && fPopupWindow->Lock()) + { + if (!fPopupWindow->IsHidden()) + { + // do an invalidate on the choice list + fPopupWindow->ListView()->Invalidate(); + fPopupWindow->ListView()->AdjustScrollBar(); + // XXX: change the selection and select the proper item, if possible + } + fPopupWindow->Unlock(); + } +} + + +//void BComboBox::AddChoice(const char *text) +//{ +// fChoiceList.AddItem((char *)text); +// if (fPopupWindow && fPopupWindow->Lock()) { +// if (!fPopupWindow->IsHidden()) { +// // do an invalidate on the new item's location +// int32 index = CountChoices() - 1; +// fPopupWindow->ListView()->InvalidateItem(index); +// fPopupWindow->ListView()->AdjustScrollBar(); +// } +// fPopupWindow->Unlock(); +// } +//} + + +//const char *BComboBox::ChoiceAt(int32 index) +//{ +// return (const char *)fChoiceList.ItemAt(index); +//} + + +//int32 BComboBox::CountChoices() +//{ +// return fChoiceList.CountItems(); +//} + + +void BComboBox::Select(int32 index, bool changeTextSelection) +{ + int32 oldIndex = fSelected; + if (index < fChoiceList->CountChoices() && index >= 0) + { + BWindow *win = Window(); + bool gotLock = (win && win->Lock()); + if (!win || gotLock) + { + fSelected = index; + if (fPopupWindow && fPopupWindow->Lock()) + { + ChoiceListView *lv = fPopupWindow->ListView(); + lv->InvalidateItem(oldIndex); + lv->InvalidateItem(fSelected); + lv->ScrollToSelection(); + fPopupWindow->Unlock(); + } + + if (changeTextSelection) + { + // Find last coma + const char *ptr = fText->Text(); + const char *end; + int32 tlength = fText->TextLength(); + + for (end = ptr+tlength-1; end>ptr; end--) + { + if (*end == ',') + { + // Find end of whitespace + for (end++; isspace(*end); end++) {} + break; + } + } + int32 soffset = end-ptr; + int32 eoffset = tlength; + if (end != 0) + fText->Delete(soffset, eoffset); + + tlength = strlen(fChoiceList->ChoiceAt(fSelected)); + fText->Insert(soffset, fChoiceList->ChoiceAt(fSelected), tlength); + eoffset = fText->TextLength(); + fText->Select(soffset, eoffset); +// fText->SetText(fChoiceList->ChoiceAt(fSelected)); +// fText->SelectAll(); + } + + if (gotLock) + win->Unlock(); + } + } + else + { + Deselect(); + return; + } +} + + +void BComboBox::Deselect() +{ + BWindow *win = Window(); + bool gotLock = (win && win->Lock()); + if (!win || gotLock) + { + int32 oldIndex = fSelected; + fSelected = -1; + // invalidate the old selected item, if needed + if (oldIndex >= 0 && fPopupWindow && fPopupWindow->Lock()) + { + fPopupWindow->ListView()->InvalidateItem(oldIndex); + fPopupWindow->Unlock(); + } + + if (gotLock) + win->Unlock(); + } +} + + +int32 BComboBox::CurrentSelection() +{ + return fSelected; +} + + +void BComboBox::SetAutoComplete(bool on) +{ + fAutoComplete = on; +} + + +bool BComboBox::GetAutoComplete() +{ + return fAutoComplete; +} + + +void BComboBox::SetLabel(const char *text) +{ + BControl::SetLabel(text); + BRect invalRect = Bounds(); + invalRect.right = fDivider; + Invalidate(invalRect); +} + + +void BComboBox::SetValue(int32 value) +{ + BControl::SetValue(value); +} + + +void BComboBox::SetText(const char *text) +{ + fText->SetText(text); + if (fText->IsFocus()) + fText->SetInitialText(); + + fText->Invalidate(); +} + + +const char *BComboBox::Text() const +{ + return fText->Text(); +} + + +BTextView *BComboBox::TextView() +{ + return fText; +} + + +void BComboBox::SetDivider(float divide) +{ + float diff = fDivider - divide; + fDivider = divide; + + fText->MoveBy(-diff, 0); + fText->ResizeBy(diff, 0); + + if (Window()) + { + fText->Invalidate(); + Invalidate(); + } +} + + +float BComboBox::Divider() const +{ + return fDivider; +} + + +void BComboBox::SetAlignment(alignment label, alignment text) +{ + fText->SetAlignment(text); + fText->AlignTextRect(); + + if (fLabelAlign != label) + { + fLabelAlign = label; + Invalidate(); + } +} + + +void BComboBox::GetAlignment(alignment *label, alignment *text) const +{ + *text = fText->Alignment(); + *label = fLabelAlign; +} + + +void BComboBox::SetModificationMessage(BMessage *message) +{ + delete fModificationMessage; + fModificationMessage = message; +} + + +BMessage *BComboBox::ModificationMessage() const +{ + return fModificationMessage; +} + + +void BComboBox::GetPreferredSize(float */*width*/, float */*height*/) +{ +// BFont font; +// GetFont(&font); +// +// *width = Bounds().IntegerWidth(); +// if (Label() != NULL) { +// float strWidth = font.StringWidth(Label()); +// *width = ceil(kTextInputMargin + strWidth + kLabelRightMargin + +// (strWidth * 1.50) + kTextInputMargin); +// } +// +// font_height finfo; +// float h1; +// float h2; +// +// font.GetHeight(&finfo); +// h1 = ceil(finfo.ascent + finfo.descent + finfo.leading); +// h2 = fText->LineHeight(); +// +// // Height of main view must be the larger of h1 and h2+(kTextInputMargin*2) +// *height = ceil((h1 > h2 + (kTextInputMargin*2)) ? h1 : h2 + (kTextInputMargin*2)); +} + + +void BComboBox::ResizeToPreferred() +{ + BControl::ResizeToPreferred(); +} + + +void BComboBox::FrameMoved(BPoint new_position) +{ + if (fPopupWindow && fPopupWindow->Lock()) + { + fPopupWindow->MoveBy(new_position.x - fFrameCache.left, + new_position.y - fFrameCache.top); + fPopupWindow->Unlock(); + } + fFrameCache.OffsetTo(new_position); +} + + +void BComboBox::FrameResized(float new_width, float new_height) +{ + // It's the cheese! + float dx = new_width - fFrameCache.Width(); + float dy = new_height - fFrameCache.Height(); + if (dx != 0 && Window()) + { +// BRect inval(fFrameCache.right, fFrameCache.top, +// fFrameCache.right+dx, fFrameCache.bottom); + BRect inval(Bounds()); + if (dx > 0) + inval.left = inval.right-dx-1; + else + inval.left = inval.right-3; +// Window()->ConvertToScreen(&inval); +// ConvertFromScreen(&inval); + Invalidate(inval); + } + + fFrameCache.right += dx; + fFrameCache.bottom += dy; +// fButtonRect.OffsetBy(dx, 0); + + if (fPopupWindow && fPopupWindow->Lock()) + { + if (!fPopupWindow->IsHidden()) + HidePopupWindow(); + + fPopupWindow->Unlock(); + } +} + + +void BComboBox::WindowActivated(bool /*active*/) +{ + if (fText->IsFocus()) + Draw(Bounds()); +} + + +void BComboBox::Draw(BRect /*updateRect*/) +{ + BRect bounds = Bounds(); + font_height fInfo; + rgb_color high = HighColor(); + rgb_color low = LowColor(); + rgb_color base = ViewColor(); + bool focused; + bool enabled; + rgb_color white = {255, 255, 255, 255}; + rgb_color black = { 0, 0, 0, 255 }; + + enabled = IsEnabled(); + focused = fText->IsFocus() && Window()->IsActive(); + + BRect fr = fText->Frame(); + + fr.InsetBy(-3, -3); + fr.bottom -= 1; + if (enabled) + SetHighColor(tint_color(base, B_DARKEN_1_TINT)); + else + SetHighColor(base); + + StrokeLine(fr.LeftBottom(), fr.LeftTop()); + StrokeLine(fr.RightTop()); + + if (enabled) + SetHighColor(white); + else + SetHighColor(tint_color(base, B_LIGHTEN_2_TINT)); + + StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom()); + StrokeLine(fr.RightTop()+BPoint(0,1)); + fr.InsetBy(1,1); + + if (focused) + { + // draw UI indication for 'active' + SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); + StrokeRect(fr); + } + else + { + if (enabled) + SetHighColor(tint_color(base, B_DARKEN_4_TINT)); + else + SetHighColor(tint_color(base, B_DARKEN_2_TINT)); + StrokeLine(fr.LeftBottom(), fr.LeftTop()); + StrokeLine(fr.RightTop()); + SetHighColor(base); + StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom()); + StrokeLine(fr.RightTop()+BPoint(0,1)); + } + + fr.InsetBy(1,1); + + if (!enabled) + SetHighColor(tint_color(base, B_DISABLED_MARK_TINT)); + else + SetHighColor(white); + + StrokeRect(fr); + SetHighColor(high); + + rgb_color oldCol = HighColor(); + + bounds.right = bounds.left + fDivider; + if ((Label()) && (fDivider > 0.0)) + { + BPoint loc; + GetFontHeight(&fInfo); + + switch (fLabelAlign) { + case B_ALIGN_LEFT: + loc.x = bounds.left + TV_MARGIN; + break; + case B_ALIGN_CENTER: + { + float width = StringWidth(Label()); + float center = (bounds.right - bounds.left) / 2; + loc.x = center - (width/2); + break; + } + case B_ALIGN_RIGHT: + { + float width = StringWidth(Label()); + loc.x = bounds.right - width - TV_MARGIN; + break; + } + } + + uint32 rmode = ResizingMode(); + if ((rmode & _rule_(0xf, 0, 0xf, 0)) == _rule_(_VIEW_TOP_, 0, _VIEW_BOTTOM_, 0)) + loc.y = fr.bottom - 2; + else + loc.y = bounds.bottom - (2 + ceil(fInfo.descent)); + + MovePenTo(loc); + SetHighColor(black); + DrawString(Label()); + SetHighColor(high); + } +} + + +void BComboBox::MessageReceived(BMessage *msg) +{ + switch (msg->what) + { + case kTextInputModifyMessage: + TryAutoComplete(); + break; + case kPopupButtonInvokeMessage: + if (fChoiceList && fChoiceList->CountChoices() && !fPopupWindow) + fPopupWindow = CreatePopupWindow(); + + if (fPopupWindow->Lock()) + { + if (fPopupWindow->IsHidden()) + ShowPopupWindow(); + else + HidePopupWindow(); + + fPopupWindow->Unlock(); + } + break; + case kWindowMovedMessage: + if (fPopupWindow && fPopupWindow->Lock()) + { + if (!fPopupWindow->IsHidden()) + HidePopupWindow(); + + fPopupWindow->Unlock(); + } + break; + default: + BControl::MessageReceived(msg); + } +} + + +void BComboBox::MouseDown(BPoint where) +{ +// printf("BComboBox::MouseDown(%f, %f)\n", where.x, where.y); + /*if (fButtonRect.Contains(where)) { // clicked in button area + fDepressedWhenClicked = fButtonDepressed; + fButtonDepressed = !fButtonDepressed; + fTrackingButtonDown = true; + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); + Invalidate(fButtonRect); + fText->MakeFocus(true); + }*/ + BControl::MouseDown(where); +} + + +void BComboBox::MouseUp(BPoint /*where*/) +{ + if (fTrackingButtonDown) + { + // send an invoke message when the button changes state + if (fButtonDepressed != fDepressedWhenClicked) + { + BMessage *msg; + Window()->PostMessage(msg = new BMessage(kPopupButtonInvokeMessage),this); + delete msg; + } + fTrackingButtonDown = false; + } +} + + +void BComboBox::MouseMoved(BPoint where, uint32 /*transit*/,const BMessage */*dragMessage*/) +{ + if (fTrackingButtonDown) + { + BRect sloppyRect = fButtonRect; + sloppyRect.InsetBy(-3, -3); + + bool oldState = fButtonDepressed; + fButtonDepressed = sloppyRect.Contains(where) ? !fDepressedWhenClicked + : fDepressedWhenClicked; + + if (oldState != fButtonDepressed) + Invalidate(fButtonRect); + } +} + + +status_t BComboBox::Invoke(BMessage *msg) +{ + return BControl::Invoke(msg); +} + + +void BComboBox::AttachedToWindow() +{ + Window()->AddFilter(fWinMovedFilter); + if (Parent()) + { + SetViewColor(Parent()->ViewColor()); + SetLowColor(ViewColor()); + } + + bool enabled = IsEnabled(); + rgb_color mc = HighColor(); + rgb_color base; + BFont textFont; + + // mc used to be base in this line + if (mc.red == 255 && mc.green == 255 && mc.blue == 255) + base = ViewColor(); + else + base = LowColor(); + + fText->GetFontAndColor(0, &textFont); + mc = enabled ? mc : disable_color(base); + + fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc); + + if (!enabled) + base = tint_color(base, B_DISABLED_MARK_TINT); + else + base.red = base.green = base.blue = 255; + + fText->SetLowColor(base); + fText->SetViewColor(base); + + fText->MakeEditable(enabled); +} + + +void BComboBox::DetachedFromWindow() +{ + fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter); +} + + +void BComboBox::SetFlags(uint32 flags) +{ + if (!fSkipSetFlags) + { + uint32 te_flags = fText->Flags(); + bool te_nav = ((te_flags & B_NAVIGABLE) != 0); + bool wants_nav = ((flags & B_NAVIGABLE) != 0); + + // the ComboBox should never be navigable + ASSERT((Flags() & B_NAVIGABLE) == 0); + + if (!te_nav && wants_nav) + { + // The combo box wants to be navigable. Pass that along to + // the text view + fText->SetFlags(te_flags | B_NAVIGABLE); + } + else if (te_nav && !wants_nav) + { + // Caller wants to end NAV on the text view; + fText->SetFlags(te_flags & ~B_NAVIGABLE); + } + + flags = flags & ~B_NAVIGABLE; // never want NAV for the combo box + } + BControl::SetFlags(flags); +} + + +void BComboBox::SetEnabled(bool enabled) +{ + if (enabled == IsEnabled()) + return; + + if (Window()) + { + fText->MakeEditable(enabled); + rgb_color mc = HighColor(); + rgb_color base = ViewColor(); + + mc = (enabled) ? mc : disable_color(base); + BFont textFont; + fText->GetFontAndColor(0, &textFont); + fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc); + + if (!enabled) + base = tint_color(base, B_DISABLED_MARK_TINT); + else + base.red = base.green = base.blue = 255; + + fText->SetLowColor(base); + fText->SetViewColor(base); + + fText->Invalidate(); + Window()->UpdateIfNeeded(); + } + + fSkipSetFlags = true; + BControl::SetEnabled(enabled); + fSkipSetFlags = false; + +//+ // Want the sub_view to be the navigable one. We always want to be able +//+ // to navigate to that view, even if disabled since Copy still works. +//+ fText->SetFlags(fText->Flags() | B_NAVIGABLE); +//+ SetFlags(Flags() & ~B_NAVIGABLE); +} + + +//void BComboBox::AllAttached() +//{ +//} + + +BComboBox::ComboBoxWindow *BComboBox::CreatePopupWindow() +{ + ComboBoxWindow *win = new ComboBoxWindow(this); + return win; +} + + +void BComboBox::CommitValue() +{ + Invoke(); +} + + +void BComboBox::TryAutoComplete() +{ + int32 from, to; + fText->GetSelection(&from, &to); + if (fAutoComplete && from == to) + { + bool autoCompleted = false; + const char *ptr = fText->Text(); + if (to > fTextEnd && from == fText->TextLength()) + { + const char *completion; + // find the first matching choice and do auto-completion + + // Find last comma + const char *end; + for (end = fText->Text()+fText->TextLength()-1; end>ptr; end--) + { + if (*end == ',') + { + // Find end of whitespace + for (end++; isspace(*end); end++) {} + if (*end == 0) + return; + break; + } + } + if (fChoiceList->GetMatch(end,0,&fCompletionIndex,&completion) == B_OK) + { + fText->Insert(completion); + fText->Select(to, to + strlen(completion)); + Select(fCompletionIndex); + autoCompleted = true; + } + else + fCompletionIndex = -1; + } + fTextEnd = to; + + if (!autoCompleted) + { + int32 sel = CurrentSelection(); + if (sel >= 0) + { + const char *selText = fChoiceList->ChoiceAt(sel); + if (selText && !strcmp(ptr, selText)) + { + // don't Deselect() if the text input matches the selection + return; + } + } + fCompletionIndex = -1; + Deselect(); + } + } +} + + +// fPopupWindow must exist and already be locked & hidden when this function +// is called +void BComboBox::ShowPopupWindow() +{ + // adjust position of the popup window + fPopupWindow->DoPosition(); + fPopupWindow->ListView()->SetEventMask(B_POINTER_EVENTS, 0); + fPopupWindow->Show(); + fPopupWindow->ListView()->MakeFocus(true); +} + + +// fPopupWindow must exist and already be locked & shown when this function +// is called +void BComboBox::HidePopupWindow() +{ + fPopupWindow->Hide(); + fPopupWindow->ListView()->SetEventMask(0, 0); + fButtonDepressed = false; + Invalidate(fButtonRect); +} + +// ---------------------------------------------------------------------------- + +BComboBox::MovedMessageFilter::MovedMessageFilter(BHandler *target) + : BMessageFilter(B_WINDOW_MOVED) +{ + fTarget = target; +} + + +filter_result BComboBox::MovedMessageFilter::Filter(BMessage *message,BHandler **target) +{ + // eliminate unused parameter warning + (void)target; + + BMessage *dup = new BMessage(*message); + dup->what = kWindowMovedMessage; + if (fTarget->Looper()) { + fTarget->Looper()->PostMessage(dup, fTarget); + } + delete dup; + return B_DISPATCH_MESSAGE; +} + + +void BComboBox::MakeFocus(bool state) +{ + fText->MakeFocus(state); + if (state) + fText->SelectAll(); +} + + +// ---------------------------------------------------------------------------- +// #pragma mark - + + +BDefaultChoiceList::BDefaultChoiceList(BComboBox *owner = NULL) +{ + fOwner = owner; + fList = new StringObjectList(); +} + + +BDefaultChoiceList::~BDefaultChoiceList() +{ + BString *string; + while ((string = fList->RemoveItemAt(0)) != NULL) + delete string; + + delete fList; +} + + +const char *BDefaultChoiceList::ChoiceAt(int32 index) +{ + BString *string = fList->ItemAt(index); + if (string) + return string->String(); + + return NULL; +} + + +status_t BDefaultChoiceList::GetMatch(const char *prefix, int32 startIndex, + int32 *matchIndex, const char **completionText) +{ + BString *str; + int32 len = strlen(prefix); + int32 choices = fList->CountItems(); + for (int32 i = startIndex; i < choices; i++) + { + str = fList->ItemAt(i); + if (!str->ICompare(prefix, len)) + { + // prefix matches + *matchIndex = i; + *completionText = str->String() + len; + return B_OK; + } + } + *matchIndex = -1; + *completionText = NULL; + return B_ERROR; +} + + +int32 BDefaultChoiceList::CountChoices() +{ + return fList->CountItems(); +} + + +status_t BDefaultChoiceList::AddChoice(const char *toAdd) +{ + BString *str = new BString(toAdd); + bool r = fList->AddItem(str); + if (fOwner) + fOwner->ChoiceListUpdated(); + + return (r) ? B_OK : B_ERROR; +} + + +status_t BDefaultChoiceList::AddChoiceAt(const char *toAdd, int32 index) +{ + BString *str = new BString(toAdd); + bool r = fList->AddItem(str, index); + if (fOwner) + fOwner->ChoiceListUpdated(); + + return (r) ? B_OK : B_ERROR; +} + + +//int BStringCompareFunction(const BString *s1, const BString *s2) +//{ +// return s1->Compare(s2); +//} + + +status_t BDefaultChoiceList::RemoveChoice(const char *toRemove) +{ + BString *string; + int32 choices = fList->CountItems(); + for (int32 i = 0; i < choices; i++) + { + string = fList->ItemAt(i); + if (!string->Compare(toRemove)) + { + fList->RemoveItemAt(i); + if (fOwner) + fOwner->ChoiceListUpdated(); + + return B_OK; + } + } + return B_ERROR; +} + + +status_t BDefaultChoiceList::RemoveChoiceAt(int32 index) +{ + BString *string = fList->RemoveItemAt(index); + if (string) + { + delete string; + if (fOwner) + fOwner->ChoiceListUpdated(); + + return B_OK; + } + return B_ERROR; +} + + +void BDefaultChoiceList::SetOwner(BComboBox *owner) +{ + fOwner = owner; +} + + +BComboBox *BDefaultChoiceList::Owner() +{ + return fOwner; +} + diff --git a/src/apps/bemail/ComboBox.h b/src/apps/bemail/ComboBox.h new file mode 100644 index 0000000000..59b2f7daec --- /dev/null +++ b/src/apps/bemail/ComboBox.h @@ -0,0 +1,224 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// +// ComboBox.h +// +// A view that is the combination of a text control and a pop-up list +// +// + +#ifndef _COMBOBOX_H +#define _COMBOBOX_H + +#include +#include + +class BButton; +class BList; +class BTextControl; +class BWindow; +class BComboBox; + +/* +// Abstract class provides an interface for BComboBox to access possible choices. +// Choices are used for auto-completion and for showing the pop-up list +class BChoiceList { +public: + // Returns the choice at index or NULL if the index is invalid + virtual const char *ChoiceAt(int32 index) = 0; + + // Looks for a match at or after startIndex which contains a choice + // that starts with prefix. If a match is found, B_OK is returned, + // matchIndex is set to the list index that should be selected, and completionText + // is set to point at the text that should be appended to the text input. + // If no match is found, a negative value is returned. + virtual status_t GetMatch(const char *prefix, int32 startIndex, + int32 *matchIndex, const char **completionText) = 0; + + // Returns the number of choices + virtual int32 CountChoices() = 0; +}; +*/ + +class StringObjectList; + +// Implementation of BChoiceList. Keeps copies of each choice added, and frees +// the memory when the choices are removed. +class BDefaultChoiceList // : public BChoiceList +{ +public: + BDefaultChoiceList(BComboBox *owner = NULL); + virtual ~BDefaultChoiceList(); + + virtual const char *ChoiceAt(int32 index); + virtual status_t GetMatch(const char *prefix, int32 startIndex, + int32 *matchIndex, const char **completionText); + virtual int32 CountChoices(); + + status_t AddChoice(const char *toAdd); + status_t AddChoiceAt(const char *toAdd, int32 index); + status_t RemoveChoice(const char *toRemove); + status_t RemoveChoiceAt(int32 index); + + void SetOwner(BComboBox *owner); + BComboBox *Owner(); + +private: + StringObjectList *fList; + BComboBox *fOwner; +}; +typedef BDefaultChoiceList BChoiceList; + + +class BComboBox : public BControl { +public: + BComboBox(BRect frame, const char *name, const char *label, + BMessage *message, uint32 resizeMask = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE); +// BComboBox(BMessage *data); + virtual ~BComboBox(); + + // BArchivable methods +// static BArchivable *Instantiate(BMessage *data); +// virtual status_t Archive(BMessage *data, bool deep = true) const; + + // SetChoiceList causes the BComboBox to delete the old BChoiceList object, + // take ownership of the new choice list, and then invalidate the pop-up list + void SetChoiceList(BChoiceList *list); + + // ChoiceList returns a pointer to the current choice list + BChoiceList *ChoiceList(); + + // ChoiceListUpdated should be called whenever an item in the choice list changes + // so that BComboBox can perform the proper updating. + virtual void ChoiceListUpdated(); + + // Select changes the list selection to the specified index, and if the + // changeTextSelection flag is true, changes the text in the TextView to + // the value at index and selects all the text in the TextView. + virtual void Select(int32 index, bool changeTextSelection = false); + + virtual void Deselect(); + + // Returns the index of the current selection, or a negative value + int32 CurrentSelection(); + + // SetAutoComplete enables or disables auto-completion + virtual void SetAutoComplete(bool on); + bool GetAutoComplete(); + + // The following methods are mostly identical to their BTextControl counterparts + virtual void SetValue(int32 value); + virtual void SetEnabled(bool enabled); + virtual void SetLabel(const char *text); + virtual void SetText(const char *text); + const char *Text() const; + BTextView *TextView(); + virtual void SetDivider(float dividing_line); + float Divider() const; + virtual void SetAlignment(alignment label, alignment text); + void GetAlignment(alignment *label, alignment *text) const; + + virtual void SetModificationMessage(BMessage *message); + BMessage *ModificationMessage() const; + + virtual void GetPreferredSize(float *width, float *height); + virtual void ResizeToPreferred(); + virtual void FrameMoved(BPoint new_position); + virtual void FrameResized(float new_width, float new_height); + virtual void WindowActivated(bool active); + virtual void MakeFocus(bool state); + + virtual void Draw(BRect update); + virtual void MessageReceived(BMessage *msg); + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage *dragMessage); +// virtual void AllAttached(); + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + virtual void SetFlags(uint32 flags); +// virtual void SetFont(const BFont *font, uint32 properties = B_FONT_ALL); + + virtual status_t Invoke(BMessage *msg = NULL); + +// virtual BHandler *ResolveSpecifier(BMessage *msg, int32 index, +// BMessage *specifier, int32 form, const char *property); +// virtual status_t GetSupportedSuites(BMessage *data); +// +// +// virtual status_t Perform(perform_code d, void *arg); + +private: + class ComboBoxWindow; + class ChoiceListView; + class TextInput; + class MovedMessageFilter; + +protected: + ComboBoxWindow *CreatePopupWindow(); + void CommitValue(); + void TryAutoComplete(); + void ShowPopupWindow(); + void HidePopupWindow(); + + BRect fButtonRect; + int32 fSelected; + int32 fCompletionIndex; + float fDivider; + TextInput *fText; + ComboBoxWindow *fPopupWindow; + BMessage *fModificationMessage; + BChoiceList *fChoiceList; + alignment fLabelAlign; + bool fAutoComplete; + bool fButtonDepressed; + bool fDepressedWhenClicked; + bool fTrackingButtonDown; + +/*----- Private or reserved -----------------------------------------*/ +private: + BRect fFrameCache; + MovedMessageFilter *fWinMovedFilter; + int32 fTextEnd; + bool fSkipSetFlags; + + friend class ChoiceListView; + friend class ComboBoxWindow; + friend class TextInput; +}; + +#endif // #ifndef _COMBOBOX_H diff --git a/src/apps/bemail/Content.cpp b/src/apps/bemail/Content.cpp new file mode 100644 index 0000000000..6a7278293e --- /dev/null +++ b/src/apps/bemail/Content.cpp @@ -0,0 +1,3255 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Content.cpp +// +//-------------------------------------------------------------------- + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "Mail.h" +#include "Content.h" +#include "Utilities.h" +#include "FieldMsg.h" +#include "Words.h" + + +#define DEBUG_SPELLCHECK 0 +#if DEBUG_SPELLCHECK +# define DSPELL(x) x +#else +# define DSPELL(x) ; +#endif + +const rgb_color kNormalTextColor = {0, 0, 0, 255}; +const rgb_color kSpellTextColor = {255, 0, 0, 255}; +const rgb_color kHyperLinkColor = {0, 0, 255, 255}; +const rgb_color kHeaderColor = {72, 72, 72, 255}; + +const rgb_color kQuoteColors[] = +{ + {0, 0, 0x80, 0}, // 3rd, 6th, ... quote level color + {0, 0x80, 0, 0}, // 1st, 4th, ... quote level color + {0x80, 0, 0, 0} // 2nd, ... +}; +const int32 kNumQuoteColors = 3; + +extern bool header_flag; +extern bool gColoredQuotes; + +void Unicode2UTF8(int32 c, char **out); + + +inline bool +IsInitialUTF8Byte(uchar b) +{ + return ((b & 0xC0) != 0x80); +} + + +void +Unicode2UTF8(int32 c, char **out) +{ + char *s = *out; + + ASSERT(c < 0x200000); + + if (c < 0x80) + *(s++) = c; + else if (c < 0x800) + { + *(s++) = 0xc0 | (c >> 6); + *(s++) = 0x80 | (c & 0x3f); + } + else if (c < 0x10000) + { + *(s++) = 0xe0 | (c >> 12); + *(s++) = 0x80 | ((c >> 6) & 0x3f); + *(s++) = 0x80 | (c & 0x3f); + } + else if (c < 0x200000) + { + *(s++) = 0xf0 | (c >> 18); + *(s++) = 0x80 | ((c >> 12) & 0x3f); + *(s++) = 0x80 | ((c >> 6) & 0x3f); + *(s++) = 0x80 | (c & 0x3f); + } + *out = s; +} + + +static bool +FilterHTMLTag(int32 *first, char **t, char *end) +{ + const char *newlineTags[] = { + "br", "/p", "/div", "/table", "/tr", + NULL}; + + char *a = *t; + + // check for some common entities (in ISO-Latin-1) + if (first[0] == '&') { + // filter out and convert decimal values + if (a[1] == '#' && sscanf(a + 1, "%ld;", first) == 1) + return false; + + const struct { char *name; int32 code; } entities[] = { + // this list is sorted alphabetically to be binary searchable + // the current implementation doesn't do this, though + + // "name" is the entity name, + // "code" is the corresponding unicode + {"AElig;", 0x00c6}, + {"Aacute;", 0x00c1}, + {"Acirc;", 0x00c2}, + {"Agrave;", 0x00c0}, + {"Aring;", 0x00c5}, + {"Atilde;", 0x00c3}, + {"Auml;", 0x00c4}, + {"Ccedil;", 0x00c7}, + {"Eacute;", 0x00c9}, + {"Ecirc;", 0x00ca}, + {"Egrave;", 0x00c8}, + {"Euml;", 0x00cb}, + {"Iacute;", 0x00cd}, + {"Icirc;", 0x00ce}, + {"Igrave;", 0x00cc}, + {"Iuml;", 0x00cf}, + {"Ntilde;", 0x00d1}, + {"Oacute;", 0x00d3}, + {"Ocirc;", 0x00d4}, + {"Ograve;", 0x00d2}, + {"Ouml;", 0x00d6}, + {"Uacute;", 0x00da}, + {"Ucirc;", 0x00db}, + {"Ugrave;", 0x00d9}, + {"Uuml;", 0x00dc}, + {"aacute;", 0x00e1}, + {"acirc;", 0x00e2}, + {"aelig;", 0x00e6}, + {"agrave;", 0x00e0}, + {"amp;", '&'}, + {"aring;", 0x00e5}, + {"atilde;", 0x00e3}, + {"auml;", 0x00e4}, + {"ccedil;", 0x00e7}, + {"copy;", 0x00a9}, + {"eacute;", 0x00e9}, + {"ecirc;", 0x00ea}, + {"egrave;", 0x00e8}, + {"euml;", 0x00eb}, + {"gt;", '>'}, + {"iacute;", 0x00ed}, + {"icirc;", 0x00ee}, + {"igrave;", 0x00ec}, + {"iuml;", 0x00ef}, + {"lt;", '<'}, + {"nbsp;", ' '}, + {"ntilde;", 0x00f1}, + {"oacute;", 0x00f3}, + {"ocirc;", 0x00f4}, + {"ograve;", 0x00f2}, + {"ouml;", 0x00f6}, + {"quot;", '"'}, + {"szlig;", 0x00f6}, + {"uacute;", 0x00fa}, + {"ucirc;", 0x00fb}, + {"ugrave;", 0x00f9}, + {"uuml;", 0x00fc}, + {NULL, 0} + }; + + for (int32 i = 0; entities[i].name; i++) { + // entities are case-sensitive + int32 length = strlen(entities[i].name); + if (!strncmp(a + 1, entities[i].name, length)) { + t[0] += length; // note that the '&' is included here + first[0] = entities[i].code; + return false; + } + } + } + + // no tag to filter + if (first[0] != '<') + return false; + + a++; + + // is the tag one of the newline tags? + + bool newline = false; + for (int i = 0; newlineTags[i]; i++) { + int length = strlen(newlineTags[i]); + if (!strncasecmp(a, (char *)newlineTags[i], length) && !isalnum(a[length])) { + newline = true; + break; + } + } + + // oh, it's not, so skip it! + + if (!strncasecmp(a, "head", 4)) { // skip "head" completely + for (; a[0] && a < end; a++) { + // Find the end of the HEAD section, or the start of the BODY, + // which happens for some malformed spam. + if (strncasecmp (a, "\"\r\n"); + + char *parenthesis = NULL; + + // filter out a trailing ')' if there is no left parenthesis before + if (string[index - 1] == ')') { + char *parenthesis = strchr(string, '('); + if (parenthesis == NULL || parenthesis > string + index) + index--; + } + + // filter out a trailing ']' if there is no left bracket before + if (parenthesis == NULL && string[index - 1] == ']') { + char *parenthesis = strchr(string, '['); + if (parenthesis == NULL || parenthesis > string + index) + index--; + } + } + else + index = strcspn(string, " \t>)\"\\,\r\n"); + + // filter out some punctuation marks if they are the last character + char suffix = string[index - 1]; + if (suffix == '.' + || suffix == ',' + || suffix == '?' + || suffix == '!' + || suffix == ':' + || suffix == ';') + index--; + + if (url != NULL) { + // copy the address to the specified string + if (type == TYPE_URL && string[0] == 'w') { + // URL starts with "www.", so add the protocol to it + url->SetTo("http://"); + url->Append(string, index); + } else if (type == TYPE_MAILTO && cistrncmp(string, "mailto:", 7)) { + // eMail address had no "mailto:" prefix + url->SetTo("mailto:"); + url->Append(string, index); + } else + url->SetTo(string, index); + } + urlLength = index; + + return type; +} + + +static void +CopyQuotes(const char *text, size_t length, char *outText, size_t &outLength) +{ + // count qoute level (to be able to wrap quotes correctly) + + char *quote = QUOTE; + int32 level = 0; + for (size_t i = 0; i < length; i++) { + if (text[i] == quote[0]) + level++; + else if (text[i] != ' ' && text[i] != '\t') + break; + } + + // if there are too much quotes, try to preserve the quote color level + if (level > 10) + level = kNumQuoteColors * 3 + (level % kNumQuoteColors); + + // copy the quotes to outText + + const int32 quoteLength = strlen(QUOTE); + outLength = 0; + while (level-- > 0) { + strcpy(outText + outLength, QUOTE); + outLength += quoteLength; + } +} + + +/** Fills the specified text_run_array with the correct values for the + * specified text. + * If "view" is NULL, it will assume that "line" lies on a line break, + * if not, it will correctly retrieve the number of quotes the current + * line already has. + */ + +void +FillInQuoteTextRuns(BTextView *view, const char *line, int32 length, const BFont &font, + text_run_array *style, int32 maxStyles) +{ + text_run *runs = style->runs; + int32 index = style->count; + bool begin; + int32 pos = 0; + int32 level = 0; + const char *quote = QUOTE; + + // get index to the beginning of the current line + + if (view != NULL) { + int32 start, end; + view->GetSelection(&end, &end); + + begin = view->TextLength() == 0 || view->ByteAt(view->TextLength() - 1) == '\n'; + + // the following line works only reliable when text wrapping is set to off; + // so the complicated version actually used here is necessary: + // start = view->OffsetAt(view->CurrentLine()); + + const char *text = view->Text(); + + if (!begin) { + // if the text is not the start of a new line, go back + // to the first character in the current line + for (start = end; start > 0; start--) { + if (text[start - 1] == '\n') + break; + } + } + + // get number of nested qoutes for current line + + if (!begin && start < end) { + begin = true; // if there was no text in this line, there may come more nested quotes + + for (int32 i = start; i < end; i++) { + if (text[i] == quote[0]) + level++; + else if (text[i] != ' ' && text[i] != '\t') { + begin = false; + break; + } + } + if (begin) // skip leading spaces (tabs & newlines aren't allowed here) + while (line[pos] == ' ') + pos++; + } + } else + begin = true; + + // set styles for all qoute levels in the text to be inserted + + for (int32 pos = 0; pos < length;) { + int32 next; + if (begin && line[pos] == quote[0]) { + while (pos < length && line[pos] != '\n') { + level++; + + bool search = true; + for (next = pos + 1; next < length; next++) { + if (search && line[next] == quote[0] + || line[next] == '\n') + break; + else if (line[next] != ' ' && line[next] != '\t') + search = false; + } + + runs[index].offset = pos; + runs[index].font = font; + runs[index].color = level > 0 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor; + + pos = next; + if (++index >= maxStyles) + break; + } + } else { + runs[index].offset = pos; + runs[index].font = font; + runs[index].color = level > 0 ? kQuoteColors[level % kNumQuoteColors] : kNormalTextColor; + index++; + + for (next = pos; next < length; next++) { + if (line[next] == '\n') + break; + } + pos = next; + } + + if (index >= maxStyles) + break; + + level = 0; + + if (pos < length && line[pos] == '\n') { + pos++; + begin = true; + + // skip leading spaces (tabs & newlines aren't allowed here) + while (pos < length && line[pos] == ' ') + pos++; + } + } + style->count = index; +} + + +// #pragma mark - + + +TextRunArray::TextRunArray(size_t entries) + : + fNumEntries(entries) +{ + fArray = (text_run_array *)malloc(sizeof(int32) + sizeof(text_run) * entries); + if (fArray != NULL) + fArray->count = 0; +} + + +TextRunArray::~TextRunArray() +{ + free(fArray); +} + + +//==================================================================== +// #pragma mark - + + +TContentView::TContentView(BRect rect, bool incoming, BEmailMessage *mail, BFont *font) + : BView(rect, "m_content", B_FOLLOW_ALL, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), + fFocus(false), + fIncoming(incoming) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + BFont v_font = *be_plain_font; + v_font.SetSize(FONT_SIZE); + fOffset = 12; + + BRect r(rect); + r.OffsetTo(0, 0); + r.right -= B_V_SCROLL_BAR_WIDTH; + r.bottom -= B_H_SCROLL_BAR_HEIGHT; + r.top += 4; + BRect text(r); + text.OffsetTo(0, 0); + text.InsetBy(5, 5); + + fTextView = new TTextView(r, text, fIncoming, mail, this, font); + BScrollView *scroll = new BScrollView("", fTextView, B_FOLLOW_ALL, 0, true, true); + AddChild(scroll); +} + + +void +TContentView::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case CHANGE_FONT: + { + BFont *font; + msg->FindPointer("font", (void **)&font); + fTextView->SetFontAndColor(0, LONG_MAX, font); + fTextView->Invalidate(Bounds()); + break; + } + + case M_QUOTE: + { + int32 start, finish; + fTextView->GetSelection(&start, &finish); + fTextView->AddQuote(start, finish); + break; + } + case M_REMOVE_QUOTE: + { + int32 start, finish; + fTextView->GetSelection(&start, &finish); + fTextView->RemoveQuote(start, finish); + break; + } + + case M_SIGNATURE: + { + entry_ref ref; + msg->FindRef("ref", &ref); + + BFile file(&ref, B_READ_ONLY); + if (file.InitCheck() == B_OK) { + int32 start, finish; + fTextView->GetSelection(&start, &finish); + + off_t size; + file.GetSize(&size); + if (size > 32768) // safety against corrupt signatures + break; + + char *signature = (char *)malloc(size); + ssize_t bytesRead = file.Read(signature, size); + if (bytesRead < B_OK) { + free (signature); + break; + } + + const char *text = fTextView->Text(); + int32 length = fTextView->TextLength(); + + if (length && text[length - 1] != '\n') { + fTextView->Select(length, length); + + char newLine = '\n'; + fTextView->Insert(&newLine, 1); + + length++; + } + + fTextView->Select(length, length); + fTextView->Insert(signature, bytesRead); + fTextView->Select(length, length + bytesRead); + fTextView->ScrollToSelection(); + + fTextView->Select(start, finish); + fTextView->ScrollToSelection(); + free (signature); + } else { + beep(); + (new BAlert("", + MDR_DIALECT_CHOICE ("An error occurred trying to open this signature.", + "この署名を開くときにエラーが発生しました"), + MDR_DIALECT_CHOICE ("Sorry", "了解")))->Go(); + } + break; + } + + case M_FIND: + FindString(msg->FindString("findthis")); + break; + + default: + BView::MessageReceived(msg); + } +} + + +void +TContentView::FindString(const char *str) +{ + int32 finish; + int32 pass = 0; + int32 start = 0; + + if (str == NULL) + return; + + // + // Start from current selection or from the beginning of the pool + // + const char *text = fTextView->Text(); + int32 count = fTextView->TextLength(); + fTextView->GetSelection(&start, &finish); + if (start != finish) + start = finish; + if (!count || text == NULL) + return; + + // + // Do the find + // + while (pass < 2) { + long found = -1; + char lc = tolower(str[0]); + char uc = toupper(str[0]); + for (long i = start; i < count; i++) { + if (text[i] == lc || text[i] == uc) { + const char *s = str; + const char *t = text + i; + while (*s && (tolower(*s) == tolower(*t))) { + s++; + t++; + } + if (*s == 0) { + found = i; + break; + } + } + } + + // + // Select the text if it worked + // + if (found != -1) { + Window()->Activate(); + fTextView->Select(found, found + strlen(str)); + fTextView->ScrollToSelection(); + fTextView->MakeFocus(true); + return; + } + else if (start) { + start = 0; + text = fTextView->Text(); + count = fTextView->TextLength(); + pass++; + } else { + beep(); + return; + } + } +} + + +void +TContentView::Focus(bool focus) +{ + if (fFocus != focus) { + fFocus = focus; + Draw(Frame()); + } +} + + +void +TContentView::FrameResized(float /* width */, float /* height */) +{ + BFont v_font = *be_plain_font; + v_font.SetSize(FONT_SIZE); + + font_height fHeight; + v_font.GetHeight(&fHeight); + + BRect r(fTextView->Bounds()); + r.OffsetTo(0, 0); + r.InsetBy(5, 5); + fTextView->SetTextRect(r); +} + + +//==================================================================== +// #pragma mark - + + +TTextView::TTextView(BRect frame, BRect text, bool incoming, BEmailMessage *mail, + TContentView *view, BFont *font) + : BTextView(frame, "", text, B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE), + fHeader(header_flag), + fReady(false), + fYankBuffer(NULL), + fLastPosition(-1), + fMail(mail), + fFont(font), + fParent(view), + fStopLoading(false), + fThread(0), + fPanel(NULL), + fIncoming(incoming), + fSpellCheck(false), + fRaw(false), + fCursor(false), + fFirstSpellMark(NULL) +{ + BFont menuFont = *be_plain_font; + menuFont.SetSize(10); + + fStopSem = create_sem(1, "reader_sem"); + SetStylable(true); + + fEnclosures = new BList(); + + // + // Enclosure pop up menu + // + fEnclosureMenu = new BPopUpMenu("Enclosure", false, false); + fEnclosureMenu->SetFont(&menuFont); + fEnclosureMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Save Enclosure", "添付ファイルを保存") B_UTF8_ELLIPSIS,new BMessage(M_SAVE))); + fEnclosureMenu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Open Enclosure", "添付ファイルを開く"), new BMessage(M_OPEN))); + + // + // Hyperlink pop up menu + // + fLinkMenu = new BPopUpMenu("Link", false, false); + fLinkMenu->SetFont(&menuFont); + fLinkMenu->AddItem(new BMenuItem( + MDR_DIALECT_CHOICE ("Open This Link", "リンク先を開く"), + new BMessage(M_OPEN))); + fLinkMenu->AddItem(new BMenuItem( + MDR_DIALECT_CHOICE ("Copy Link Location", "リンク先をコピー"), + new BMessage(M_COPY))); + + SetDoesUndo(true); + + //Undo function + fUndoBuffer.On(); + fInputMethodUndoBuffer.On(); + fUndoState.replaced = false; + fUndoState.deleted = false; + fInputMethodUndoState.active = false; + fInputMethodUndoState.replace = false; +} + + +TTextView::~TTextView() +{ + ClearList(); + delete fPanel; + + if (fYankBuffer) + free(fYankBuffer); + + delete_sem(fStopSem); +} + + +void +TTextView::AttachedToWindow() +{ + BTextView::AttachedToWindow(); + fFont.SetSpacing(B_FIXED_SPACING); + SetFontAndColor(&fFont); + + if (fMail != NULL) { + LoadMessage(fMail, false, NULL); + if (fIncoming) + MakeEditable(false); + } +} + + +void +TTextView::KeyDown(const char *key, int32 count) +{ + char raw; + int32 end; + int32 start; + uint32 mods; + BMessage *msg; + int32 textLen = TextLength(); + + msg = Window()->CurrentMessage(); + mods = msg->FindInt32("modifiers"); + + switch (key[0]) + { + case B_HOME: + if (IsSelectable()) + { + if (IsEditable()) + BTextView::KeyDown(key, count); + else + { + // scroll to the beginning + Select(0, 0); + ScrollToSelection(); + } + } + break; + + case B_END: + if (IsSelectable()) + { + if (IsEditable()) + BTextView::KeyDown(key, count); + else + { + // scroll to the end + int32 length = TextLength(); + Select(length, length); + ScrollToSelection(); + } + } + break; + + case 0x02: // ^b - back 1 char + if (IsSelectable()) + { + GetSelection(&start, &end); + while (!IsInitialUTF8Byte(ByteAt(--start))) + { + if (start < 0) + { + start = 0; + break; + } + } + if (start >= 0) + { + Select(start, start); + ScrollToSelection(); + } + } + break; + + case B_DELETE: + if (IsSelectable()) + { + if ((key[0] == B_DELETE) || (mods & B_CONTROL_KEY)) // ^d + { + if (IsEditable()) + { + GetSelection(&start, &end); + if (start != end) + Delete(); + else + { + for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) + { + if (end > textLen) + { + end = textLen; + break; + } + } + Select(start, end); + Delete(); + } + } + } + else + Select(textLen, textLen); + ScrollToSelection(); + } + break; + + case 0x05: // ^e - end of line + if ((IsSelectable()) && (mods & B_CONTROL_KEY)) + { + if (CurrentLine() == CountLines() - 1) + Select(TextLength(), TextLength()); + else + { + GoToLine(CurrentLine() + 1); + GetSelection(&start, &end); + Select(start - 1, start - 1); + } + } + break; + + case 0x06: // ^f - forward 1 char + if (IsSelectable()) + { + GetSelection(&start, &end); + if (end > start) + start = end; + else + { + for (end = start + 1; !IsInitialUTF8Byte(ByteAt(end)); end++) + { + if (end > textLen) + { + end = textLen; + break; + } + } + start = end; + } + Select(start, start); + ScrollToSelection(); + } + break; + + case 0x0e: // ^n - next line + if (IsSelectable()) + { + raw = B_DOWN_ARROW; + BTextView::KeyDown(&raw, 1); + } + break; + + case 0x0f: // ^o - open line + if (IsEditable()) + { + GetSelection(&start, &end); + Delete(); + + char newLine = '\n'; + Insert(&newLine, 1); + Select(start, start); + ScrollToSelection(); + } + break; + + case B_PAGE_UP: + if (mods & B_CONTROL_KEY) { // ^k kill text from cursor to e-o-line + if (IsEditable()) { + GetSelection(&start, &end); + if ((start != fLastPosition) && (fYankBuffer)) { + free(fYankBuffer); + fYankBuffer = NULL; + } + fLastPosition = start; + if (CurrentLine() < (CountLines() - 1)) { + GoToLine(CurrentLine() + 1); + GetSelection(&end, &end); + end--; + } + else + end = TextLength(); + if (end < start) + break; + if (start == end) + end++; + Select(start, end); + if (fYankBuffer) { + fYankBuffer = (char *)realloc(fYankBuffer, + strlen(fYankBuffer) + (end - start) + 1); + GetText(start, end - start, + &fYankBuffer[strlen(fYankBuffer)]); + } else { + fYankBuffer = (char *)malloc(end - start + 1); + GetText(start, end - start, fYankBuffer); + } + Delete(); + ScrollToSelection(); + } + break; + } + + BTextView::KeyDown(key, count); + break; + + case 0x10: // ^p goto previous line + if (IsSelectable()) { + raw = B_UP_ARROW; + BTextView::KeyDown(&raw, 1); + } + break; + + case 0x19: // ^y yank text + if ((IsEditable()) && (fYankBuffer)) { + Delete(); + Insert(fYankBuffer); + ScrollToSelection(); + } + break; + + default: + BTextView::KeyDown(key, count); + } +} + + +void +TTextView::MakeFocus(bool focus) +{ + if (!focus) { + // ToDo: can someone please translate this? Otherwise I will remove it - axeld. + // MakeFocus(false) は、IM も Inactive になり、そのまま確定される。 + // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) + // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 + fInputMethodUndoState.active = false; + // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 + if (fInputMethodUndoBuffer.CountItems() > 0) { + KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); + if (item->History == K_INSERTED) { + fUndoBuffer.MakeNewUndoItem(); + fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, item->History, item->CursorPos); + fUndoBuffer.MakeNewUndoItem(); + } + fInputMethodUndoBuffer.MakeEmpty(); + } + } + BTextView::MakeFocus(focus); + + fParent->Focus(focus); +} + + +void +TTextView::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case B_SIMPLE_DATA: + { + if (fIncoming) + break; + + BMessage message(REFS_RECEIVED); + bool isEnclosure = false; + bool inserted = false; + + off_t len = 0; + int32 end; + int32 start; + + int32 index = 0; + entry_ref ref; + while (msg->FindRef("refs", index++, &ref) == B_OK) { + BFile file(&ref, B_READ_ONLY); + if (file.InitCheck() == B_OK) { + BNodeInfo node(&file); + char type[B_FILE_NAME_LENGTH]; + node.GetType(type); + + off_t size = 0; + file.GetSize(&size); + + if (!strncasecmp(type, "text/", 5) && size > 0) { + len += size; + char *text = (char *)malloc(size); + if (text == NULL) { + puts("no memory!"); + return; + } + if (file.Read(text, size) < B_OK) { + puts("could not read from file"); + continue; + } + if (!inserted) { + GetSelection(&start, &end); + Delete(); + inserted = true; + } + + int32 offset = 0; + for (int32 loop = 0; loop < size; loop++) { + if (text[loop] == '\n') { + Insert(&text[offset], loop - offset + 1); + offset = loop + 1; + } else if (text[loop] == '\r') { + text[loop] = '\n'; + Insert(&text[offset], loop - offset + 1); + if ((loop + 1 < size) + && (text[loop + 1] == '\n')) + loop++; + offset = loop + 1; + } + } + free(text); + } else { + isEnclosure = true; + message.AddRef("refs", &ref); + } + } + } + + if (index == 1) { + // message doesn't contain any refs - maybe the parent class likes it + BTextView::MessageReceived(msg); + break; + } + + if (inserted) + Select(start, start + len); + if (isEnclosure) + Window()->PostMessage(&message, Window()); + break; + } + + case M_HEADER: + msg->FindBool("header", &fHeader); + SetText(NULL); + LoadMessage(fMail, false, NULL); + break; + + case M_RAW: + StopLoad(); + + msg->FindBool("raw", &fRaw); + SetText(NULL); + LoadMessage(fMail, false, NULL); + break; + + case M_SELECT: + if (IsSelectable()) + Select(0, TextLength()); + break; + + case M_SAVE: + Save(msg); + break; + + case B_NODE_MONITOR: + { + int32 opcode; + if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) { + dev_t device; + if (msg->FindInt32("device", &device) < B_OK) + break; + ino_t inode; + if (msg->FindInt64("node", &inode) < B_OK) + break; + + hyper_text *enclosure; + for (int32 index = 0; + (enclosure = (hyper_text *)fEnclosures->ItemAt(index++)) != NULL;) { + if (device == enclosure->node.device + && inode == enclosure->node.node) { + if (opcode == B_ENTRY_REMOVED) { + enclosure->saved = false; + enclosure->have_ref = false; + } else if (opcode == B_ENTRY_MOVED) { + enclosure->ref.device = device; + msg->FindInt64("to directory", &enclosure->ref.directory); + + const char *name; + msg->FindString("name", &name); + enclosure->ref.set_name(name); + } + break; + } + } + } + break; + } + + // + // Tracker has responded to a BMessage that was dragged out of + // this email message. It has created a file for us, we just have to + // put the stuff in it. + // + case B_COPY_TARGET: + { + BMessage data; + if (msg->FindMessage("be:originator-data", &data) == B_OK) { + entry_ref directory; + const char *name; + hyper_text *enclosure; + + if (data.FindPointer("enclosure", (void **)&enclosure) == B_OK + && msg->FindString("name", &name) == B_OK + && msg->FindRef("directory", &directory) == B_OK) { + switch (enclosure->type) { + case TYPE_ENCLOSURE: + case TYPE_BE_ENCLOSURE: + { + // + // Enclosure. Decode the data and write it out. + // + BMessage saveMsg(M_SAVE); + saveMsg.AddString("name", name); + saveMsg.AddRef("directory", &directory); + saveMsg.AddPointer("enclosure", enclosure); + Save(&saveMsg, false); + break; + } + + case TYPE_URL: + { + const char *replyType; + if (msg->FindString("be:filetypes", &replyType) != B_OK) + // drag recipient didn't ask for any specific type, + // create a bookmark file as default + replyType = "application/x-vnd.Be-bookmark"; + + BDirectory dir(&directory); + BFile file(&dir, name, B_READ_WRITE); + if (file.InitCheck() == B_OK) { + if (strcmp(replyType, "application/x-vnd.Be-bookmark") == 0) { + // we got a request to create a bookmark, stuff + // it with the url attribute + file.WriteAttr("META:url", B_STRING_TYPE, 0, + enclosure->name, strlen(enclosure->name) + 1); + } else if (strcasecmp(replyType, "text/plain") == 0) { + // create a plain text file, stuff it with + // the url as text + file.Write(enclosure->name, strlen(enclosure->name)); + } + + BNodeInfo fileInfo(&file); + fileInfo.SetType(replyType); + } + break; + } + + case TYPE_MAILTO: + { + // + // Add some attributes to the already created + // person file. Strip out the 'mailto:' if + // possible. + // + char *addrStart = enclosure->name; + while (true) { + if (*addrStart == ':') { + addrStart++; + break; + } + + if (*addrStart == '\0') { + addrStart = enclosure->name; + break; + } + + addrStart++; + } + + const char *replyType; + if (msg->FindString("be:filetypes", &replyType) != B_OK) + // drag recipient didn't ask for any specific type, + // create a bookmark file as default + replyType = "application/x-vnd.Be-bookmark"; + + BDirectory dir(&directory); + BFile file(&dir, name, B_READ_WRITE); + if (file.InitCheck() == B_OK) { + if (!strcmp(replyType, "application/x-person")) { + // we got a request to create a bookmark, stuff + // it with the address attribute + file.WriteAttr("META:email", B_STRING_TYPE, 0, + addrStart, strlen(enclosure->name) + 1); + } else if (!strcasecmp(replyType, "text/plain")) { + // create a plain text file, stuff it with the + // email as text + file.Write(addrStart, strlen(addrStart)); + } + + BNodeInfo fileInfo(&file); + fileInfo.SetType(replyType); + } + break; + } + } + } else { + // + // Assume this is handled by BTextView... + // (Probably drag clipping.) + // + BTextView::MessageReceived(msg); + } + } + break; + } + + case B_INPUT_METHOD_EVENT: + { + int32 im_op; + if (msg->FindInt32("be:opcode", &im_op) == B_OK){ + switch (im_op) { + case B_INPUT_METHOD_STARTED: + fInputMethodUndoState.replace = true; + fInputMethodUndoState.active = true; + break; + case B_INPUT_METHOD_STOPPED: + fInputMethodUndoState.active = false; + if (fInputMethodUndoBuffer.CountItems() > 0) { + KUndoItem *undo = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); + if (undo->History == K_INSERTED){ + fUndoBuffer.MakeNewUndoItem(); + fUndoBuffer.AddUndo(undo->RedoText, undo->Length, + undo->Offset, undo->History, undo->CursorPos); + fUndoBuffer.MakeNewUndoItem(); + } + fInputMethodUndoBuffer.MakeEmpty(); + } + break; + case B_INPUT_METHOD_CHANGED: + fInputMethodUndoState.active = true; + break; + case B_INPUT_METHOD_LOCATION_REQUEST: + fInputMethodUndoState.active = true; + break; + } + } + BTextView::MessageReceived(msg); + break; + } + + case M_REDO: + Redo(); + break; + + default: + BTextView::MessageReceived(msg); + } +} + + +void +TTextView::MouseDown(BPoint where) +{ + if (IsEditable()) { + BPoint point; + uint32 buttons; + GetMouse(&point, &buttons); + if (gDictCount && (buttons == B_SECONDARY_MOUSE_BUTTON)) { + int32 offset, start, end, length; + const char *text = Text(); + offset = OffsetAt(where); + if (isalpha(text[offset])) { + length = TextLength(); + + //Find start and end of word + //FindSpellBoundry(length, offset, &start, &end); + + char c; + bool isAlpha, isApost, isCap; + int32 first; + + for (first = offset; + (first >= 0) && (((c = text[first]) == '\'') || isalpha(c)); + first--) {} + isCap = isupper(text[++first]); + + for (start = offset, c = text[start], isAlpha = isalpha(c), isApost = (c=='\''); + (start >= 0) && (isAlpha || (isApost + && (((c = text[start+1]) != 's') || !isCap) && isalpha(c) + && isalpha(text[start-1]))); + start--, c = text[start], isAlpha = isalpha(c), isApost = (c == '\'')) {} + start++; + + for (end = offset, c = text[end], isAlpha = isalpha(c), isApost = (c == '\''); + (end < length) && (isAlpha || (isApost + && (((c = text[end + 1]) != 's') || !isCap) && isalpha(c))); + end++, c = text[end], isAlpha = isalpha(c), isApost = (c == '\'')) {} + + length = end - start; + BString srcWord; + srcWord.SetTo(text + start, length); + + bool foundWord = false; + BList matches; + BString *string; + + BMenuItem *menuItem; + BPopUpMenu menu("Words", false, false); + + int32 matchCount; + for (int32 i = 0; i < gDictCount; i++) + matchCount = gWords[i]->FindBestMatches(&matches, + srcWord.String()); + + if (matches.CountItems()) { + sort_word_list(&matches, srcWord.String()); + for (int32 i = 0; (string = (BString *)matches.ItemAt(i)) != NULL; i++) { + menu.AddItem((menuItem = new BMenuItem(string->String(), NULL))); + if (!strcasecmp(string->String(), srcWord.String())) { + menuItem->SetEnabled(false); + foundWord = true; + } + delete string; + } + } else { + (menuItem = new BMenuItem("No Matches", NULL))->SetEnabled(false); + menu.AddItem(menuItem); + } + + BMenuItem *addItem = NULL; + if (!foundWord && gUserDict >= 0) { + menu.AddSeparatorItem(); + addItem = new BMenuItem(MDR_DIALECT_CHOICE ("Add", "追加"), NULL); + menu.AddItem(addItem); + } + + point = ConvertToScreen(where); + if ((menuItem = menu.Go(point, false, false)) != NULL) { + if (menuItem == addItem) { + BString newItem(srcWord.String()); + newItem << "\n"; + gWords[gUserDict]->InitIndex(); + gExactWords[gUserDict]->InitIndex(); + gUserDictFile->Write(newItem.String(), newItem.Length()); + gWords[gUserDict]->BuildIndex(); + gExactWords[gUserDict]->BuildIndex(); + + if (fSpellCheck) + CheckSpelling(0, TextLength()); + } else { + int32 len = strlen(menuItem->Label()); + Select(start, start); + Delete(start, end); + Insert(start, menuItem->Label(), len); + Select(start+len, start+len); + } + } + } + return; + } else if (fSpellCheck && IsEditable()) { + int32 start, end; + + GetSelection(&start, &end); + FindSpellBoundry(1, start, &start, &end); + CheckSpelling(start, end); + } + } else { + // is not editable, look for enclosures/links + + int32 clickOffset = OffsetAt(where); + int32 items = fEnclosures->CountItems(); + for (int32 loop = 0; loop < items; loop++) { + hyper_text *enclosure = (hyper_text*) fEnclosures->ItemAt(loop); + if (clickOffset < enclosure->text_start || clickOffset >= enclosure->text_end) + continue; + + // + // The user is clicking on this attachment + // + + int32 start; + int32 finish; + Select(enclosure->text_start, enclosure->text_end); + GetSelection(&start, &finish); + Window()->UpdateIfNeeded(); + + bool drag = false; + bool held = false; + uint32 buttons = 0; + if (Window()->CurrentMessage()) { + Window()->CurrentMessage()->FindInt32("buttons", + (int32 *) &buttons); + } + + // + // If this is the primary button, wait to see if the user is going + // to single click, hold, or drag. + // + if (buttons != B_SECONDARY_MOUSE_BUTTON) { + BPoint point = where; + bigtime_t popupDelay; + get_click_speed(&popupDelay); + popupDelay *= 2; + popupDelay += system_time(); + while (buttons && abs((int)(point.x - where.x)) < 4 + && abs((int)(point.y - where.y)) < 4 + && system_time() < popupDelay) { + snooze(10000); + GetMouse(&point, &buttons); + } + + if (system_time() < popupDelay) { + // + // The user either dragged this or released the button. + // check if it was dragged. + // + if (!(abs((int)(point.x - where.x)) < 4 + && abs((int)(point.y - where.y)) < 4) && buttons) + drag = true; + } else { + // + // The user held the button down. + // + held = true; + } + } + + // + // If the user has right clicked on this menu, + // or held the button down on it for a while, + // pop up a context menu. + // + if (buttons == B_SECONDARY_MOUSE_BUTTON || held) { + // + // Right mouse click... Display a menu + // + BPoint point = where; + ConvertToScreen(&point); + + BMenuItem *item; + if ((enclosure->type != TYPE_ENCLOSURE) + && (enclosure->type != TYPE_BE_ENCLOSURE)) + item = fLinkMenu->Go(point, true); + else + item = fEnclosureMenu->Go(point, true); + + BMessage *msg; + if (item && (msg = item->Message()) != NULL) { + if (msg->what == M_SAVE) { + if (fPanel) + fPanel->SetEnclosure(enclosure); + else { + fPanel = new TSavePanel(enclosure, this); + fPanel->Window()->Show(); + } + } else if (msg->what == M_COPY) { + // copy link location to clipboard + + if (be_clipboard->Lock()) { + be_clipboard->Clear(); + + BMessage *clip; + if ((clip = be_clipboard->Data()) != NULL) { + clip->AddData("text/plain", B_MIME_TYPE, + enclosure->name, strlen(enclosure->name)); + be_clipboard->Commit(); + } + be_clipboard->Unlock(); + } + } else + Open(enclosure); + } + } else { + // + // Left button. If the user single clicks, open this link. + // Otherwise, initiate a drag. + // + if (drag) { + BMessage dragMessage(B_SIMPLE_DATA); + dragMessage.AddInt32("be:actions", B_COPY_TARGET); + dragMessage.AddString("be:types", B_FILE_MIME_TYPE); + switch (enclosure->type) { + case TYPE_BE_ENCLOSURE: + case TYPE_ENCLOSURE: + // + // Attachment. The type is specified in the message. + // + dragMessage.AddString("be:types", B_FILE_MIME_TYPE); + dragMessage.AddString("be:filetypes", + enclosure->content_type ? enclosure->content_type : ""); + dragMessage.AddString("be:clip_name", enclosure->name); + break; + + case TYPE_URL: + // + // URL. The user can drag it into the tracker to + // create a bookmark file. + // + dragMessage.AddString("be:types", B_FILE_MIME_TYPE); + dragMessage.AddString("be:filetypes", + "application/x-vnd.Be-bookmark"); + dragMessage.AddString("be:filetypes", "text/plain"); + dragMessage.AddString("be:clip_name", "Bookmark"); + + dragMessage.AddString("be:url", enclosure->name); + break; + + case TYPE_MAILTO: + // + // Mailto address. The user can drag it into the + // tracker to create a people file. + // + dragMessage.AddString("be:types", B_FILE_MIME_TYPE); + dragMessage.AddString("be:filetypes", + "application/x-person"); + dragMessage.AddString("be:filetypes", "text/plain"); + dragMessage.AddString("be:clip_name", "Person"); + + dragMessage.AddString("be:email", enclosure->name); + break; + + default: + // + // Otherwise it doesn't have a type that I know how + // to save. It won't have any types and if any + // program wants to accept it, more power to them. + // (tracker won't.) + // + dragMessage.AddString("be:clip_name", "Hyperlink"); + } + + BMessage data; + data.AddPointer("enclosure", enclosure); + dragMessage.AddMessage("be:originator-data", &data); + + BRegion selectRegion; + GetTextRegion(start, finish, &selectRegion); + DragMessage(&dragMessage, selectRegion.Frame(), this); + } else { + // + // User Single clicked on the attachment. Open it. + // + Open(enclosure); + } + } + return; + } + } + BTextView::MouseDown(where); +} + + +void +TTextView::MouseMoved(BPoint where, uint32 code, const BMessage *msg) +{ + int32 start = OffsetAt(where); + + for (int32 loop = fEnclosures->CountItems(); loop-- > 0;) { + hyper_text *enclosure = (hyper_text *)fEnclosures->ItemAt(loop); + if ((start >= enclosure->text_start) && (start < enclosure->text_end)) { + if (!fCursor) + SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); + fCursor = true; + return; + } + } + + if (fCursor) { + SetViewCursor(B_CURSOR_I_BEAM); + fCursor = false; + } + + BTextView::MouseMoved(where, code, msg); +} + + +void +TTextView::ClearList() +{ + hyper_text *enclosure; + while ((enclosure = (hyper_text *)fEnclosures->FirstItem()) != NULL) { + fEnclosures->RemoveItem(enclosure); + + if (enclosure->name) + free(enclosure->name); + if (enclosure->content_type) + free(enclosure->content_type); + if (enclosure->encoding) + free(enclosure->encoding); + if (enclosure->have_ref && !enclosure->saved) { + BEntry entry(&enclosure->ref); + entry.Remove(); + } + + watch_node(&enclosure->node, B_STOP_WATCHING, this); + free(enclosure); + } +} + + +void +TTextView::LoadMessage(BEmailMessage *mail, bool quoteIt, const char *text) +{ + StopLoad(); + + fMail = mail; + + ClearList(); + + MakeSelectable(true); + MakeEditable(false); + if (text) + Insert(text, strlen(text)); + + //attr_info attrInfo; + TTextView::Reader *reader = new TTextView::Reader(fHeader, fRaw, quoteIt, fIncoming, + text != NULL, true, + // I removed the following, because I absolutely can't imagine why it's + // there (the mail kit should be able to deal with non-compliant mails) + // -- axeld. + // fFile->GetAttrInfo(B_MAIL_ATTR_MIME, &attrInfo) == B_OK, + this, mail, fEnclosures, fStopSem); + + resume_thread(fThread = spawn_thread(Reader::Run, "reader", B_NORMAL_PRIORITY, reader)); +} + + +void +TTextView::Open(hyper_text *enclosure) +{ + switch (enclosure->type) { + case TYPE_URL: + { + const struct {const char *urlType, *handler; } handlerTable[] = { + {"http", B_URL_HTTP}, + {"https", B_URL_HTTPS}, + {"ftp", B_URL_FTP}, + {"gopher", B_URL_GOPHER}, + {"mailto", B_URL_MAILTO}, + {"news", B_URL_NEWS}, + {"nntp", B_URL_NNTP}, + {"telnet", B_URL_TELNET}, + {"rlogin", B_URL_RLOGIN}, + {"tn3270", B_URL_TN3270}, + {"wais", B_URL_WAIS}, + {"file", B_URL_FILE}, + {NULL, NULL} + }; + const char *handlerToLaunch = NULL; + + const char *colonPos = strchr(enclosure->name, ':'); + if (colonPos) { + int urlTypeLength = colonPos - enclosure->name; + + for (int32 index = 0; handlerTable[index].urlType; index++) { + if (!strncasecmp(enclosure->name, + handlerTable[index].urlType, urlTypeLength)) { + handlerToLaunch = handlerTable[index].handler; + break; + } + } + } + if (handlerToLaunch) { + entry_ref appRef; + if (be_roster->FindApp(handlerToLaunch, &appRef) != B_OK) + handlerToLaunch = NULL; + } + if (!handlerToLaunch) + handlerToLaunch = "application/x-vnd.Be-Bookmark"; + + status_t result = be_roster->Launch(handlerToLaunch, 1, &enclosure->name); + if (result != B_NO_ERROR && result != B_ALREADY_RUNNING) { + beep(); + (new BAlert("", + MDR_DIALECT_CHOICE("There is no installed handler for URL links.", + "このURLリンクを扱えるアプリケーションが存在しません"), + "Sorry"))->Go(); + } + break; + } + + case TYPE_MAILTO: + if (be_roster->Launch(B_MAIL_TYPE, 1, &enclosure->name) < B_OK) { + char *argv[] = {"BeMail", enclosure->name}; + be_app->ArgvReceived(2, argv); + } + break; + + case TYPE_ENCLOSURE: + case TYPE_BE_ENCLOSURE: + if (!enclosure->have_ref) { + BPath path; + if (find_directory(B_COMMON_TEMP_DIRECTORY, &path) == B_NO_ERROR) { + BDirectory dir(path.Path()); + if (dir.InitCheck() == B_NO_ERROR) { + char name[B_FILE_NAME_LENGTH]; + char baseName[B_FILE_NAME_LENGTH]; + strcpy(baseName, enclosure->name ? enclosure->name : "enclosure"); + strcpy(name, baseName); + for (int32 index = 0; dir.Contains(name); index++) + sprintf(name, "%s_%ld", baseName, index); + + BEntry entry(path.Path()); + entry_ref ref; + entry.GetRef(&ref); + + BMessage save(M_SAVE); + save.AddRef("directory", &ref); + save.AddString("name", name); + save.AddPointer("enclosure", enclosure); + if (Save(&save) != B_NO_ERROR) + break; + enclosure->saved = false; + } + } + } + + BMessenger tracker("application/x-vnd.Be-TRAK"); + if (tracker.IsValid()) { + BMessage openMsg(B_REFS_RECEIVED); + openMsg.AddRef("refs", &enclosure->ref); + tracker.SendMessage(&openMsg); + } + break; + } +} + + +status_t +TTextView::Save(BMessage *msg, bool makeNewFile) +{ + const char *name; + entry_ref ref; + BFile file; + BPath path; + hyper_text *enclosure; + status_t result = B_NO_ERROR; + char entry_name[B_FILE_NAME_LENGTH]; + + msg->FindString("name", &name); + msg->FindRef("directory", &ref); + msg->FindPointer("enclosure", (void **)&enclosure); + + BDirectory dir; + dir.SetTo(&ref); + result = dir.InitCheck(); + + if (result == B_OK) { + if (makeNewFile) { + // + // Search for the file and delete it if it already exists. + // (It may not, that's ok.) + // + BEntry entry; + if (dir.FindEntry(name, &entry) == B_NO_ERROR) + entry.Remove(); + + if ((enclosure->have_ref) && (!enclosure->saved)) { + entry.SetTo(&enclosure->ref); + + // + // Added true arg and entry_name so MoveTo clobbers as + // before. This may not be the correct behaviour, but + // it's the preserved behaviour. + // + entry.GetName(entry_name); + result = entry.MoveTo(&dir, entry_name, true); + if (result == B_NO_ERROR) { + entry.Rename(name); + entry.GetRef(&enclosure->ref); + entry.GetNodeRef(&enclosure->node); + enclosure->saved = true; + return result; + } + } + + if (result == B_NO_ERROR) { + result = dir.CreateFile(name, &file); + if (result == B_NO_ERROR && enclosure->content_type) { + char type[B_MIME_TYPE_LENGTH]; + + if (!strcasecmp(enclosure->content_type, "message/rfc822")) + strcpy(type, "text/x-email"); + else if (!strcasecmp(enclosure->content_type, "message/delivery-status")) + strcpy(type, "text/plain"); + else + strcpy(type, enclosure->content_type); + + BNodeInfo info(&file); + info.SetType(type); + } + } + } else { + // + // This file was dragged into the tracker or desktop. The file + // already exists. + // + result = file.SetTo(&dir, name, B_WRITE_ONLY); + } + } + + if (enclosure->component == NULL) + result = B_ERROR; + + if (result == B_NO_ERROR) { + // + // Write the data + // + enclosure->component->GetDecodedData(&file); + + BEntry entry; + dir.FindEntry(name, &entry); + entry.GetRef(&enclosure->ref); + enclosure->have_ref = true; + enclosure->saved = true; + entry.GetPath(&path); + update_mime_info(path.Path(), false, true, + !cistrcmp("application/octet-stream", enclosure->content_type ? enclosure->content_type : B_EMPTY_STRING)); + entry.GetNodeRef(&enclosure->node); + watch_node(&enclosure->node, B_WATCH_NAME, this); + } + + if (result != B_NO_ERROR) { + beep(); + MDR_DIALECT_CHOICE( + (new BAlert("", "An error occurred trying to save the enclosure.", "Sorry"))->Go();, + (new BAlert("", "添付ファイルを保存するときにエラーが発生しました", "了解"))->Go(); + ) + } + + return result; +} + + +void +TTextView::StopLoad() +{ + Window()->Unlock(); + + thread_info info; + if (fThread != 0 && get_thread_info(fThread, &info) == B_NO_ERROR) { + fStopLoading = true; + acquire_sem(fStopSem); + int32 result; + wait_for_thread(fThread, &result); + fThread = 0; + release_sem(fStopSem); + fStopLoading = false; + } + + Window()->Lock(); +} + + +void +TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding) +{ + if (mail == NULL) + return; + + int32 textLength = TextLength(); + const char *text = Text(); + + BTextMailComponent *body = mail->Body(); + if (body == NULL) { + if (mail->SetBody(body = new BTextMailComponent()) < B_OK) + return; + } + body->SetEncoding(encoding, charset); + + // Just add the text as a whole if we can, or ... + if (!wrap) { + body->AppendText(text); + return; + } + + // ... do word wrapping. + + BWindow *window = Window(); + char *saveText = strdup(text); + BRect saveTextRect = TextRect(); + + // do this before we start messing with the fonts + // the user will never know... + window->DisableUpdates(); + Hide(); + BScrollBar *vScroller = ScrollBar(B_VERTICAL); + BScrollBar *hScroller = ScrollBar(B_HORIZONTAL); + if (vScroller != NULL) + vScroller->SetTarget((BView *)NULL); + if (hScroller != NULL) + hScroller->SetTarget((BView *)NULL); + + // Temporarily set the font to a fixed width font for line wrapping + // calculations. If the font doesn't have as many of the symbols as + // the preferred font, go back to using the user's preferred font. + + bool *boolArray; + int missingCharactersFixedWidth = 0; + int missingCharactersPreferredFont = 0; + int32 numberOfCharacters; + + numberOfCharacters = BString(text).CountChars(); + if (numberOfCharacters > 0 + && (boolArray = (bool *)malloc(sizeof(bool) * numberOfCharacters)) != NULL) { + memset(boolArray, 0, sizeof (bool) * numberOfCharacters); + be_fixed_font->GetHasGlyphs(text, numberOfCharacters, boolArray); + for (int i = 0; i < numberOfCharacters; i++) { + if (!boolArray[i]) + missingCharactersFixedWidth += 1; + } + + memset(boolArray, 0, sizeof (bool) * numberOfCharacters); + fFont.GetHasGlyphs(text, numberOfCharacters, boolArray); + for (int i = 0; i < numberOfCharacters; i++) { + if (!boolArray[i]) + missingCharactersPreferredFont += 1; + } + + free(boolArray); + } + + if (missingCharactersFixedWidth > missingCharactersPreferredFont) + SetFontAndColor(0, textLength, &fFont); + else // All things being equal, the fixed font is better for wrapping. + SetFontAndColor(0, textLength, be_fixed_font); + + // calculate a text rect that is 72 columns wide + BRect newTextRect = saveTextRect; + newTextRect.right = newTextRect.left + be_fixed_font->StringWidth("m") * 72; + SetTextRect(newTextRect); + + // hard-wrap, based on TextView's soft-wrapping + int32 numLines = CountLines(); + bool spaceMoved = false; + char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need + if (content != NULL) { + int32 contentLength = 0; + + for (int32 i = 0; i < numLines; i++) { + int32 startOffset = OffsetAt(i); + if (spaceMoved) { + startOffset++; + spaceMoved = false; + } + int32 endOffset = OffsetAt(i + 1); + int32 lineLength = endOffset - startOffset; + + // quick hack to not break URLs into several parts + for (int32 pos = startOffset; pos < endOffset; pos++) { + size_t urlLength; + uint8 type = CheckForURL(text + pos, urlLength); + if (type != 0) + pos += urlLength; + + if (pos > endOffset) { + // find first break character after the URL + for (; text[pos]; pos++) { + if (isalnum(text[pos]) || isspace(text[pos])) + break; + } + if (text[pos] && isspace(text[pos]) && text[pos] != '\n') + pos++; + + endOffset += pos - endOffset; + lineLength = endOffset - startOffset; + + // insert a newline (and the same number of quotes) after the + // URL to make sure the rest of the text is properly wrapped + + char buffer[64]; + if (text[pos] == '\n') + buffer[0] = '\0'; + else + strcpy(buffer, "\n"); + + size_t quoteLength; + CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength); + + Insert(pos, buffer, strlen(buffer)); + numLines = CountLines(); + text = Text(); + i++; + } + } + if (text[endOffset - 1] != ' ' + && text[endOffset - 1] != '\n' + && text[endOffset] == ' ') { + // make sure spaces will be part of this line + endOffset++; + lineLength++; + spaceMoved = true; + } + + memcpy(content + contentLength, text + startOffset, lineLength); + contentLength += lineLength; + + // add a newline to every line except for the ones + // that already end in newlines, and the last line + if ((text[endOffset - 1] != '\n') && (i < (numLines - 1))) { + content[contentLength++] = '\n'; + + // copy quote level of the first line + size_t quoteLength; + CopyQuotes(text + startOffset, lineLength, content + contentLength, quoteLength); + contentLength += quoteLength; + } + } + content[contentLength] = '\0'; + + body->AppendText(content); + free(content); + } + + // reset the text rect and font + SetTextRect(saveTextRect); + SetText(saveText); + free(saveText); + SetFontAndColor(0, textLength, &fFont); + + // should be OK to hook these back up now + if (vScroller != NULL) + vScroller->SetTarget(this); + if (hScroller != NULL) + hScroller->SetTarget(this); + + Show(); + window->EnableUpdates(); +} + + +//-------------------------------------------------------------------- +// #pragma mark - + + +TTextView::Reader::Reader(bool header, bool raw, bool quote, bool incoming, bool stripHeader, + bool mime, TTextView *view, BEmailMessage *mail, BList *list, sem_id sem) + : + fHeader(header), + fRaw(raw), + fQuote(quote), + fIncoming(incoming), + fStripHeader(stripHeader), + fMime(mime), + fView(view), + fMail(mail), + fEnclosures(list), + fStopSem(sem) +{ +} + + +bool +TTextView::Reader::ParseMail(BMailContainer *container, BTextMailComponent *ignore) +{ + int32 count = 0; + for (int32 i = 0; i < container->CountComponents(); i++) { + if (fView->fStopLoading) + return false; + + BMailComponent *component; + if ((component = container->GetComponent(i)) == NULL) { + if (fView->fStopLoading) + return false; + + hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); + memset(enclosure, 0, sizeof(hyper_text)); + + enclosure->type = TYPE_ENCLOSURE; + + char *name = "\n\n"; + + fView->GetSelection(&enclosure->text_start, &enclosure->text_end); + enclosure->text_start++; + enclosure->text_end += strlen(name) - 1; + + Insert(name, strlen(name), true); + fEnclosures->AddItem(enclosure); + continue; + } + + count++; + if (component == ignore) + continue; + + if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) { + BMIMEMultipartMailContainer *c = dynamic_cast(container->GetComponent(i)); + ASSERT(c != NULL); + + if (!ParseMail(c, ignore)) + count--; + } else if (fIncoming) { + hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); + memset(enclosure, 0, sizeof(hyper_text)); + + enclosure->type = TYPE_ENCLOSURE; + enclosure->component = component; + + BString name; + char fileName[B_FILE_NAME_LENGTH]; + strcpy(fileName, "untitled"); + if (BMailAttachment *attachment = dynamic_cast (component)) + attachment->FileName(fileName); + + BPath path(fileName); + enclosure->name = strdup(path.Leaf()); + + BMimeType type; + component->MIMEType(&type); + enclosure->content_type = strdup(type.Type()); + + char typeDescription[B_MIME_TYPE_LENGTH]; + if (type.GetShortDescription(typeDescription) != B_OK) + strcpy(typeDescription, type.Type() ? type.Type() : B_EMPTY_STRING); + + name = "\nname << " (Type: " << typeDescription << ")>\n"; + + fView->GetSelection(&enclosure->text_start, &enclosure->text_end); + enclosure->text_start++; + enclosure->text_end += strlen(name.String()) - 1; + + Insert(name.String(), name.Length(), true); + fEnclosures->AddItem(enclosure); + } +// default: +// { +// PlainTextBodyComponent *body = dynamic_cast(container->GetComponent(i)); +// const char *text; +// if (body && (text = body->Text()) != NULL) +// Insert(text, strlen(text), false); +// } + } + return count > 0; +} + + +bool +TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader) +{ + char line[522]; + int32 count = 0; + + for (int32 loop = 0; loop < data_len; loop++) { + if (fView->fStopLoading) + return false; + + if (fQuote && (!loop || (loop && data[loop - 1] == '\n'))) { + strcpy(&line[count], QUOTE); + count += strlen(QUOTE); + } + if (!fRaw && fIncoming && (loop < data_len - 7)) { + size_t urlLength; + BString url; + uint8 type = CheckForURL(data + loop, urlLength, &url); + + if (type) { + if (!Insert(line, count, false, isHeader)) + return false; + count = 0; + + hyper_text *enclosure = (hyper_text *)malloc(sizeof(hyper_text)); + memset(enclosure, 0, sizeof(hyper_text)); + fView->GetSelection(&enclosure->text_start, + &enclosure->text_end); + enclosure->type = type; + enclosure->name = strdup(url.String()); + if (enclosure->name == NULL) + return false; + + Insert(&data[loop], urlLength, true, isHeader); + enclosure->text_end += urlLength; + loop += urlLength - 1; + + fEnclosures->AddItem(enclosure); + continue; + } + } + if (!fRaw && fMime && data[loop] == '=') { + if ((loop) && (loop < data_len - 1) && (data[loop + 1] == '\r')) + loop += 2; + else + line[count++] = data[loop]; + } else if (data[loop] != '\r') + line[count++] = data[loop]; + + if (count > 511 || (count && loop == data_len - 1)) { + if (!Insert(line, count, false, isHeader)) + return false; + count = 0; + } + } + return true; +} + + +bool +TTextView::Reader::Insert(const char *line, int32 count, bool isHyperLink, bool isHeader) +{ + if (!count) + return true; + + BFont font(fView->Font()); + TextRunArray style(count / 8 + 8); + + if (gColoredQuotes && !isHeader && !isHyperLink) + FillInQuoteTextRuns(fView, line, count, font, &style.Array(), style.MaxEntries()); + else { + text_run_array &array = style.Array(); + array.count = 1; + array.runs[0].offset = 0; + if (isHeader) { + array.runs[0].color = isHyperLink ? kHyperLinkColor : kHeaderColor; + font.SetSize(font.Size() * 0.9); + } else + array.runs[0].color = isHyperLink ? kHyperLinkColor : kNormalTextColor; + array.runs[0].font = font; + } + + if (!fView->Window()->Lock()) + return false; + + fView->Insert(line, count, &style.Array()); + + fView->Window()->Unlock(); + return true; +} + + +status_t +TTextView::Reader::Run(void *_this) +{ + Reader *reader = (Reader *)_this; + TTextView *view = reader->fView; + char *msg = NULL; + off_t size = 0; + int32 len = 0; + + if (!reader->Lock()) + return B_INTERRUPTED; + + BFile *file = dynamic_cast(reader->fMail->Data()); + if (file != NULL) { + len = header_len(file); + + if (reader->fHeader) + size = len; + if (reader->fRaw || !reader->fMime) + file->GetSize(&size); + + if (size != 0 && (msg = (char *)malloc(size)) == NULL) + goto done; + file->Seek(0, 0); + + if (msg) + size = file->Read(msg, size); + } + + // show the header? + if (reader->fHeader && len) { + // strip all headers except "From", "To", "Reply-To", "Subject", and "Date" + if (reader->fStripHeader) { + const char *header = msg; + char *buffer = NULL; + + while (strncmp(header, "\r\n", 2)) { + const char *eol = header; + while ((eol = strstr(eol, "\r\n")) != NULL && isspace(eol[2])) + eol += 2; + if (eol == NULL) + break; + + eol += 2; // CR+LF belong to the line + size_t length = eol - header; + + buffer = (char *)realloc(buffer, length + 1); + if (buffer == NULL) + goto done; + + memcpy(buffer, header, length); + + length = rfc2047_to_utf8(&buffer, &length, length); + + if (!strncasecmp(header, "Reply-To: ", 10) + || !strncasecmp(header, "To: ", 4) + || !strncasecmp(header, "From: ", 6) + || !strncasecmp(header, "Subject: ", 8) + || !strncasecmp(header, "Date: ", 6)) + reader->Process(buffer, length, true); + + header = eol; + } + if (buffer) + free(buffer); + reader->Process("\r\n", 2, true); + } + else if (!reader->Process(msg, len, true)) + goto done; + } + + if (reader->fRaw) { + if (!reader->Process((const char *)msg + len, size - len)) + goto done; + } else { + //reader->fFile->Seek(0, 0); + //BEmailMessage *mail = new BEmailMessage(reader->fFile); + BEmailMessage *mail = reader->fMail; + + // at first, insert the mail body + BTextMailComponent *body = NULL; + if (mail->BodyText() && !view->fStopLoading) { + char *bodyText = const_cast(mail->BodyText()); + int32 bodyLength = strlen(bodyText); + body = mail->Body(); + bool isHTML = false; + + BMimeType type; + if (body->MIMEType(&type) == B_OK && type == "text/html") { + // strip out HTML tags + char *t = bodyText, *a, *end = bodyText + bodyLength; + bodyText = (char *) malloc (bodyLength + 1); + isHTML = true; + + for (a = bodyText; t < end; t++) { + int32 c = *t; + + // compact newlines and spaces + bool space = false; + while (c && isspace(c)) { + c = *(++t); + space = true; + } + if (space) { + c = ' '; + t--; + } + else if (FilterHTMLTag(&c, &t, end)) // the tag filter + continue; + + Unicode2UTF8(c, &a); + } + + *a = 0; + bodyLength = strlen(bodyText); + body = NULL; // to add the HTML text as enclosure + } + if (!reader->Process(bodyText, bodyLength)) + goto done; + + if (isHTML) + free(bodyText); + } + + if (!reader->ParseMail(mail, body)) + goto done; + + //reader->fView->fMail = mail; + } + + if (!view->fStopLoading && view->Window()->Lock()) { + view->Select(0, 0); + view->MakeSelectable(true); + if (!reader->fIncoming) + view->MakeEditable(true); + + view->Window()->Unlock(); + } + +done: + reader->Unlock(); + + delete reader; + if (msg) + free(msg); + + return B_NO_ERROR; +} + + +status_t +TTextView::Reader::Unlock() +{ + return release_sem(fStopSem); +} + + +bool +TTextView::Reader::Lock() +{ + if (acquire_sem_etc(fStopSem, 1, B_TIMEOUT, 0) != B_NO_ERROR) + return false; + + return true; +} + + +//==================================================================== +// #pragma mark - + + +TSavePanel::TSavePanel(hyper_text *enclosure, TTextView *view) + : BFilePanel(B_SAVE_PANEL) +{ + fEnclosure = enclosure; + fView = view; + if (enclosure->name) + SetSaveText(enclosure->name); +} + + +void +TSavePanel::SendMessage(const BMessenger * /* messenger */, BMessage *msg) +{ + const char *name = NULL; + BMessage save(M_SAVE); + entry_ref ref; + + if ((!msg->FindRef("directory", &ref)) && (!msg->FindString("name", &name))) { + save.AddPointer("enclosure", fEnclosure); + save.AddString("name", name); + save.AddRef("directory", &ref); + fView->Window()->PostMessage(&save, fView); + } +} + + +void +TSavePanel::SetEnclosure(hyper_text *enclosure) +{ + fEnclosure = enclosure; + if (enclosure->name) + SetSaveText(enclosure->name); + else + SetSaveText(""); + + if (!IsShowing()) + Show(); + Window()->Activate(); +} + + +//-------------------------------------------------------------------- +// #pragma mark - + + +void +TTextView::InsertText(const char *insertText, int32 length, int32 offset, + const text_run_array *runs) +{ + ContentChanged(); + + // Undo function + + int32 cursorPos, dummy; + GetSelection(&cursorPos, &dummy); + + if (fInputMethodUndoState.active) { + // IMアクティブ時は、一旦別のバッファへ記憶 + fInputMethodUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); + fInputMethodUndoState.replace = false; + } else { + if (fUndoState.replaced) { + fUndoBuffer.AddUndo(insertText, length, offset, K_REPLACED, cursorPos); + } else { + if (length == 1 && insertText[0] == 0x0a) + fUndoBuffer.MakeNewUndoItem(); + + fUndoBuffer.AddUndo(insertText, length, offset, K_INSERTED, cursorPos); + + if (length == 1 && insertText[0] == 0x0a) + fUndoBuffer.MakeNewUndoItem(); + } + } + + fUndoState.replaced = false; + fUndoState.deleted = false; + + struct text_runs : text_run_array { text_run _runs[1]; } style; + if (runs == NULL && IsEditable()) { + style.count = 1; + style.runs[0].offset = 0; + style.runs[0].font = fFont; + style.runs[0].color = kNormalTextColor; + runs = &style; + } + + BTextView::InsertText(insertText, length, offset, runs); + + if (fSpellCheck && IsEditable()) + { + UpdateSpellMarks(offset, length); + + rgb_color color; + GetFontAndColor(offset - 1, NULL, &color); + const char *text = Text(); + + if (length > 1 + || isalpha(text[offset + 1]) + || (!isalpha(text[offset]) && text[offset] != '\'') + || (color.red == kSpellTextColor.red + && color.green == kSpellTextColor.green + && color.blue == kSpellTextColor.blue)) + { + int32 start, end; + FindSpellBoundry(length, offset, &start, &end); + + DSPELL(printf("Offset %ld, start %ld, end %ld\n", offset, start, end)); + DSPELL(printf("\t\"%10.10s...\"\n", text + start)); + + CheckSpelling(start, end); + } + } +} + + +void +TTextView::DeleteText(int32 start, int32 finish) +{ + ContentChanged(); + + // Undo function + int32 cursorPos, dummy; + GetSelection(&cursorPos, &dummy); + if (fInputMethodUndoState.active) { + if (fInputMethodUndoState.replace) { + fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); + fInputMethodUndoState.replace = false; + } else { + fInputMethodUndoBuffer.AddUndo(&Text()[start], finish - start, start, + K_DELETED, cursorPos); + } + } else + fUndoBuffer.AddUndo(&Text()[start], finish - start, start, K_DELETED, cursorPos); + + fUndoState.deleted = true; + fUndoState.replaced = true; + + BTextView::DeleteText(start, finish); + if (fSpellCheck && IsEditable()) { + UpdateSpellMarks(start, start - finish); + + int32 s, e; + FindSpellBoundry(1, start, &s, &e); + CheckSpelling(s, e); + } +} + + +void +TTextView::ContentChanged(void) +{ + BLooper *looper = Looper(); + if (looper == NULL) + return; + + BMessage msg(FIELD_CHANGED); + msg.AddInt32("bitmask", FIELD_BODY); + msg.AddPointer("source", this); + looper->PostMessage(&msg); +} + + +void +TTextView::CheckSpelling(int32 start, int32 end, int32 flags) +{ + const char *text = Text(); + const char *next, *endPtr, *word = NULL; + int32 wordLength = 0, wordOffset; + int32 nextHighlight = start; + BString testWord; + bool isCap = false; + bool isAlpha; + bool isApost; + + for (next = text + start, endPtr = text + end; next <= endPtr; next++) { + //printf("next=%c\n", *next); + // ToDo: this has to be refined to other languages... + // Alpha signifies the start of a word + isAlpha = isalpha(*next); + isApost = (*next == '\''); + if (!word && isAlpha) { + //printf("Found word start\n"); + word = next; + wordLength++; + isCap = isupper(*word); + } else if (word && (isAlpha || isApost) && !(isApost && !isalpha(next[1])) + && !(isCap && isApost && (next[1] == 's'))) { + // Word continues check + wordLength++; + //printf("Word continues...\n"); + } else if (word) { + // End of word reached + + //printf("Word End\n"); + // Don't check single characters + if (wordLength > 1) { + bool isUpper = true; + + // Look for all uppercase + for (int32 i = 0; i < wordLength; i++) { + if (word[i] == '\'') + break; + + if (islower(word[i])) { + isUpper = false; + break; + } + } + + // Don't check all uppercase words + if (!isUpper) { + bool foundMatch = false; + wordOffset = word - text; + testWord.SetTo(word, wordLength); + + testWord = testWord.ToLower(); + DSPELL(printf("Testing: \"%s\"\n", testWord.String())); + + int32 key = -1; + if (gDictCount) + key = gExactWords[0]->GetKey(testWord.String()); + + // Search all dictionaries + for (int32 i = 0; i < gDictCount; i++) { + if (gExactWords[i]->Lookup(key) >= 0) { + foundMatch = true; + break; + } + } + + if (!foundMatch) { + if (flags & S_CLEAR_ERRORS) + RemoveSpellMark(nextHighlight, wordOffset); + + if (flags & S_SHOW_ERRORS) + AddSpellMark(wordOffset, wordOffset + wordLength); + } else if (flags & S_CLEAR_ERRORS) + RemoveSpellMark(nextHighlight, wordOffset + wordLength); + + nextHighlight = wordOffset + wordLength; + } + } + // Reset state to looking for word + word = NULL; + wordLength = 0; + } + } + + if (nextHighlight <= end + && (flags & S_CLEAR_ERRORS) != 0 + && nextHighlight < TextLength()) + SetFontAndColor(nextHighlight, end, NULL, B_FONT_ALL, &kNormalTextColor); +} + + +void +TTextView::FindSpellBoundry(int32 length, int32 offset, int32 *_start, int32 *_end) +{ + int32 start, end, textLength; + const char *text = Text(); + textLength = TextLength(); + + for (start = offset - 1; start >= 0 + && (isalpha(text[start]) || text[start] == '\''); start--) {} + + start++; + + for (end = offset + length; end < textLength + && (isalpha(text[end]) || text[end] == '\''); end++) {} + + *_start = start; + *_end = end; +} + + +TTextView::spell_mark * +TTextView::FindSpellMark(int32 start, int32 end, spell_mark **_previousMark) +{ + spell_mark *lastMark = NULL; + + for (spell_mark *spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { + if (spellMark->start < end && spellMark->end > start) { + if (_previousMark) + *_previousMark = lastMark; + return spellMark; + } + + lastMark = spellMark; + } + return NULL; +} + + +void +TTextView::UpdateSpellMarks(int32 offset, int32 length) +{ + DSPELL(printf("UpdateSpellMarks: offset = %ld, length = %ld\n", offset, length)); + + spell_mark *spellMark; + for (spellMark = fFirstSpellMark; spellMark; spellMark = spellMark->next) { + DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); + + if (spellMark->end < offset) + continue; + + if (spellMark->start > offset) + spellMark->start += length; + + spellMark->end += length; + + DSPELL(printf("\t-> reset: %ld - %ld\n", spellMark->start, spellMark->end)); + } +} + + +status_t +TTextView::AddSpellMark(int32 start, int32 end) +{ + DSPELL(printf("AddSpellMark: start = %ld, end = %ld\n", start, end)); + + // check if there is already a mark for this passage + spell_mark *spellMark = FindSpellMark(start, end); + if (spellMark) { + if (spellMark->start == start && spellMark->end == end) { + DSPELL(printf("\tfound one\n")); + return B_OK; + } + + DSPELL(printf("\tremove old one\n")); + RemoveSpellMark(start, end); + } + + spellMark = (spell_mark *)malloc(sizeof(spell_mark)); + if (spellMark == NULL) + return B_NO_MEMORY; + + spellMark->start = start; + spellMark->end = end; + spellMark->style = RunArray(start, end); + + // set the spell marks appearance + BFont font(fFont); + font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); + SetFontAndColor(start, end, &font, B_FONT_ALL, &kSpellTextColor); + + // add it to the queue + spellMark->next = fFirstSpellMark; + fFirstSpellMark = spellMark; + + return B_OK; +} + + +bool +TTextView::RemoveSpellMark(int32 start, int32 end) +{ + DSPELL(printf("RemoveSpellMark: start = %ld, end = %ld\n", start, end)); + + // find spell mark + spell_mark *lastMark = NULL; + spell_mark *spellMark = FindSpellMark(start, end, &lastMark); + if (spellMark == NULL) { + DSPELL(printf("\tnot found!\n")); + return false; + } + + DSPELL(printf("\tfound: %ld - %ld\n", spellMark->start, spellMark->end)); + + // dequeue the spell mark + if (lastMark) + lastMark->next = spellMark->next; + else + fFirstSpellMark = spellMark->next; + + if (spellMark->start < start) + start = spellMark->start; + if (spellMark->end > end) + end = spellMark->end; + + // reset old text run array + SetRunArray(start, end, spellMark->style); + + free(spellMark->style); + free(spellMark); + + return true; +} + + +void +TTextView::RemoveSpellMarks() +{ + spell_mark *spellMark, *nextMark; + + for (spellMark = fFirstSpellMark; spellMark; spellMark = nextMark) { + nextMark = spellMark->next; + + // reset old text run array + SetRunArray(spellMark->start, spellMark->end, spellMark->style); + + free(spellMark->style); + free(spellMark); + } + + fFirstSpellMark = NULL; +} + + +void +TTextView::EnableSpellCheck(bool enable) +{ + if (fSpellCheck == enable) + return; + + fSpellCheck = enable; + int32 textLength = TextLength(); + if (fSpellCheck) { + // work-around for a bug in the BTextView class + // which causes lots of flicker + int32 start, end; + GetSelection(&start, &end); + if (start != end) + Select(start, start); + + CheckSpelling(0, textLength); + + if (start != end) + Select(start, end); + } + else + RemoveSpellMarks(); +} + + +void +TTextView::WindowActivated(bool flag) +{ + if (!flag) { + // WindowActivated(false) は、IM も Inactive になり、そのまま確定される。 + // しかしこの場合、input_server が B_INPUT_METHOD_EVENT(B_INPUT_METHOD_STOPPED) + // を送ってこないまま矛盾してしまうので、やむを得ずここでつじつまあわせ処理している。 + // OpenBeOSで修正されることを願って暫定処置としている。 + fInputMethodUndoState.active = false; + // fInputMethodUndoBufferに溜まっている最後のデータがK_INSERTEDなら(確定)正規のバッファへ追加 + if (fInputMethodUndoBuffer.CountItems() > 0) { + KUndoItem *item = fInputMethodUndoBuffer.ItemAt(fInputMethodUndoBuffer.CountItems() - 1); + if (item->History == K_INSERTED) { + fUndoBuffer.MakeNewUndoItem(); + fUndoBuffer.AddUndo(item->RedoText, item->Length, item->Offset, + item->History, item->CursorPos); + fUndoBuffer.MakeNewUndoItem(); + } + fInputMethodUndoBuffer.MakeEmpty(); + } + } + BTextView::WindowActivated(flag); +} + + +void +TTextView::AddQuote(int32 start, int32 finish) +{ + BRect rect = Bounds(); + + int32 lineStart; + GoToLine(CurrentLine()); + GetSelection(&lineStart, &lineStart); + + // make sure that we're changing the whole last line, too + int32 lineEnd = finish > lineStart ? finish - 1 : finish; + { + const char *text = Text(); + while (text[lineEnd] && text[lineEnd] != '\n') + lineEnd++; + } + Select(lineStart, lineEnd); + + int32 textLength = lineEnd - lineStart; + char *text = (char *)malloc(textLength + 1); + if (text == NULL) + return; + + GetText(lineStart, textLength, text); + + int32 quoteLength = strlen(QUOTE); + int32 targetLength = 0; + char *target = NULL; + int32 lastLine = 0; + + for (int32 index = 0; index < textLength; index++) { + if (text[index] == '\n' || index == textLength - 1) { + // add quote to this line + int32 lineLength = index - lastLine + 1; + + target = (char *)realloc(target, targetLength + lineLength + quoteLength); + if (target == NULL) + // free the old buffer? + return; + + // copy the quote sign + memcpy(&target[targetLength], QUOTE, quoteLength); + targetLength += quoteLength; + + // copy the rest of the line + memcpy(&target[targetLength], &text[lastLine], lineLength); + targetLength += lineLength; + + lastLine = index + 1; + } + } + + // replace with quoted text + free(text); + Delete(); + + if (gColoredQuotes) { + const BFont *font = Font(); + TextRunArray style(targetLength / 8 + 8); + + FillInQuoteTextRuns(NULL, target, targetLength, font, &style.Array(), style.MaxEntries()); + Insert(target, targetLength, &style.Array()); + } else + Insert(target, targetLength); + + free(target); + + // redo the old selection (compute the new start if necessary) + Select(start + quoteLength, finish + (targetLength - textLength)); + + ScrollTo(rect.LeftTop()); +} + + +void +TTextView::RemoveQuote(int32 start, int32 finish) +{ + BRect rect = Bounds(); + + GoToLine(CurrentLine()); + int32 lineStart; + GetSelection(&lineStart, &lineStart); + + // make sure that we're changing the whole last line, too + int32 lineEnd = finish > lineStart ? finish - 1 : finish; + const char *text = Text(); + while (text[lineEnd] && text[lineEnd] != '\n') + lineEnd++; + + Select(lineStart, lineEnd); + + int32 length = lineEnd - lineStart; + char *target = (char *)malloc(length + 1); + if (target == NULL) + return; + + int32 quoteLength = strlen(QUOTE); + int32 removed = 0; + text += lineStart; + + for (int32 index = 0; index < length;) { + // find out the length of the current line + int32 lineLength = 0; + while (index + lineLength < length && text[lineLength] != '\n') + lineLength++; + + // include the newline to be part of this line + if (text[lineLength] == '\n' && index + lineLength + 1 < length) + lineLength++; + + if (!strncmp(text, QUOTE, quoteLength)) { + // remove quote + length -= quoteLength; + removed += quoteLength; + + lineLength -= quoteLength; + text += quoteLength; + } + + if (lineLength == 0) { + target[index] = '\0'; + break; + } + + memcpy(&target[index], text, lineLength); + + text += lineLength; + index += lineLength; + } + + if (removed) { + Delete(); + + if (gColoredQuotes) { + const BFont *font = Font(); + TextRunArray style(length / 8 + 8); + + FillInQuoteTextRuns(NULL, target, length, font, &style.Array(), style.MaxEntries()); + Insert(target, length, &style.Array()); + } else + Insert(target, length); + + // redo old selection + bool noSelection = start == finish; + + if (start > lineStart + quoteLength) + start -= quoteLength; + else + start = lineStart; + + if (noSelection) + finish = start; + else + finish -= removed; + } + + free(target); + + Select(start, finish); + ScrollTo(rect.LeftTop()); +} + + +void +TTextView::Undo(BClipboard */*clipboard*/) +{ + if (fInputMethodUndoState.active) + return; + + int32 length, offset, cursorPos; + undo_type history; + char *text; + status_t status; + + status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); + if (status == B_OK) { + fUndoBuffer.Off(); + + switch (history) { + case K_INSERTED: + BTextView::Delete(offset, offset + length); + Select(offset, offset); + break; + + case K_DELETED: + BTextView::Insert(offset, text, length); + Select(offset, offset + length); + break; + + case K_REPLACED: + BTextView::Delete(offset, offset + length); + status = fUndoBuffer.Undo(&text, &length, &offset, &history, &cursorPos); + if (status == B_OK && history == K_DELETED) { + BTextView::Insert(offset, text, length); + Select(offset, offset + length); + } else { + ::beep(); + (new BAlert("", + MDR_DIALECT_CHOICE("Inconsistency occurred in the Undo/Redo buffer.", + "Undo/Redoバッファに矛盾が発生しました!"), "OK"))->Go(); + } + break; + } + ScrollToSelection(); + ContentChanged(); + fUndoBuffer.On(); + } +} + + +void +TTextView::Redo() +{ + if (fInputMethodUndoState.active) + return; + + int32 length, offset, cursorPos; + undo_type history; + char *text; + status_t status; + bool replaced; + + status = fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); + if (status == B_OK) { + fUndoBuffer.Off(); + + switch (history) { + case K_INSERTED: + BTextView::Insert(offset, text, length); + Select(offset, offset + length); + break; + + case K_DELETED: + BTextView::Delete(offset, offset + length); + if (replaced) { + fUndoBuffer.Redo(&text, &length, &offset, &history, &cursorPos, &replaced); + BTextView::Insert(offset, text, length); + } + Select(offset, offset + length); + break; + + case K_REPLACED: + ::beep(); + (new BAlert("", + MDR_DIALECT_CHOICE("Inconsistency occurred in the Undo/Redo buffer.", + "Undo/Redoバッファに矛盾が発生しました!"), "OK"))->Go(); + break; + } + ScrollToSelection(); + ContentChanged(); + fUndoBuffer.On(); + } +} diff --git a/src/apps/bemail/Content.h b/src/apps/bemail/Content.h new file mode 100644 index 0000000000..eb426a9285 --- /dev/null +++ b/src/apps/bemail/Content.h @@ -0,0 +1,291 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Content.h +// +//-------------------------------------------------------------------- + +#ifndef _CONTENT_H +#define _CONTENT_H + +#include +#include +#include +#include +#include +#include +#include + +#include "KUndoBuffer.h" + +#define MESSAGE_TEXT "Message:" +#define MESSAGE_TEXT_H 16 +#define MESSAGE_TEXT_V 5 +#define MESSAGE_FIELD_H 59 +#define MESSAGE_FIELD_V 11 + +#define CONTENT_TYPE "content-type: " +#define CONTENT_ENCODING "content-transfer-encoding: " +#define CONTENT_DISPOSITION "Content-Disposition: " +#define MIME_TEXT "text/" +#define MIME_MULTIPART "multipart/" + +class TMailWindow; +class TScrollView; +class TTextView; +class BFile; +class BList; +class BPopupMenu; + +struct text_run_array; + +typedef struct +{ + bool header; + bool raw; + bool quote; + bool incoming; + bool close; + bool mime; + TTextView *view; + BEmailMessage *mail; + BList *enclosures; + sem_id *stop_sem; +} reader_info; + +enum ENCLOSURE_TYPE +{ + TYPE_ENCLOSURE = 100, + TYPE_BE_ENCLOSURE, + TYPE_URL, + TYPE_MAILTO +}; + +struct hyper_text { + int32 type; + char *name; + char *content_type; + char *encoding; + int32 text_start; + int32 text_end; + BMailComponent *component; + bool saved; + bool have_ref; + entry_ref ref; + node_ref node; +}; + +class TSavePanel; + + +//==================================================================== + +class TContentView : public BView +{ + public: + TContentView(BRect, bool incoming, BEmailMessage *mail, BFont *); + virtual void MessageReceived(BMessage *); + void FindString(const char *); + void Focus(bool); + void FrameResized(float, float); + + TTextView *fTextView; + + private: + bool fFocus; + bool fIncoming; + float fOffset; +}; + +//==================================================================== + +enum { + S_CLEAR_ERRORS = 1, + S_SHOW_ERRORS = 2 +}; + +class TTextView : public BTextView +{ + public: + TTextView(BRect, BRect, bool incoming, BEmailMessage *mail, TContentView *,BFont *); + ~TTextView(); + + virtual void AttachedToWindow(); + virtual void KeyDown(const char*, int32); + virtual void MakeFocus(bool); + virtual void MessageReceived(BMessage*); + virtual void MouseDown(BPoint); + virtual void MouseMoved(BPoint, uint32, const BMessage*); + virtual void InsertText(const char *text, int32 length, int32 offset, + const text_run_array *runs); + virtual void DeleteText(int32 start, int32 finish); + + void ClearList(); + void LoadMessage(BEmailMessage *mail, bool quoteIt, const char *insertText); + void Open(hyper_text*); + status_t Save(BMessage *, bool makeNewFile = true); + void StopLoad(); + void AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_encoding encoding); + void CheckSpelling(int32 start, int32 end, + int32 flags = S_CLEAR_ERRORS | S_SHOW_ERRORS); + void FindSpellBoundry(int32 length, int32 offset, int32 *start, + int32 *end); + void EnableSpellCheck(bool enable); + + void AddQuote(int32 start, int32 finish); + void RemoveQuote(int32 start, int32 finish); + + void WindowActivated(bool flag); + void Undo(BClipboard *clipboard); + void Redo(); + + const BFont *Font() const { return &fFont; } + + bool fHeader; + bool fReady; + + private: + struct { bool replaced, deleted; } fUndoState; + KUndoBuffer fUndoBuffer; + + struct { bool active, replace; } fInputMethodUndoState; + KUndoBuffer fInputMethodUndoBuffer; + // For handling Input Method changes in undo. + + struct spell_mark; + + spell_mark *FindSpellMark(int32 start, int32 end, spell_mark **_previousMark = NULL); + void UpdateSpellMarks(int32 offset, int32 length); + status_t AddSpellMark(int32 start, int32 end); + bool RemoveSpellMark(int32 start, int32 end); + void RemoveSpellMarks(); + + void ContentChanged(void); + + class Reader; + friend TTextView::Reader; + + char *fYankBuffer; + int32 fLastPosition; + BFile *fFile; + BEmailMessage *fMail; + // for incoming/replied/forwarded mails only + BFont fFont; + TContentView *fParent; + sem_id fStopSem; + bool fStopLoading; + thread_id fThread; + BList *fEnclosures; + BPopUpMenu *fEnclosureMenu; + BPopUpMenu *fLinkMenu; + TSavePanel *fPanel; + bool fIncoming; + bool fSpellCheck; + bool fRaw; + bool fCursor; + + struct spell_mark + { + spell_mark *next; + int32 start; + int32 end; + struct text_run_array *style; + }; + + spell_mark *fFirstSpellMark; + + class Reader + { + public: + Reader(bool header,bool raw,bool quote,bool incoming,bool stripHeaders,bool mime, + TTextView *view,BEmailMessage *mail,BList *list,sem_id sem); + + static status_t Run(void *); + + private: + bool ParseMail(BMailContainer *container,BTextMailComponent *ignore); + bool Process(const char *data, int32 len, bool isHeader = false); + bool Insert(const char *line, int32 count, bool isHyperLink, bool isHeader = false); + + bool Lock(); + status_t Unlock(); + + bool fHeader; + bool fRaw; + bool fQuote; + bool fIncoming; + bool fStripHeader; + bool fMime; + TTextView *fView; + BEmailMessage *fMail; + BList *fEnclosures; + sem_id fStopSem; + }; +}; + + +//==================================================================== + +class TSavePanel : public BFilePanel { + public: + TSavePanel(hyper_text*, TTextView*); + virtual void SendMessage(const BMessenger*, BMessage*); + void SetEnclosure(hyper_text*); + + private: + hyper_text *fEnclosure; + TTextView *fView; +}; + +//==================================================================== + +class TextRunArray { + public: + TextRunArray(size_t entries); + ~TextRunArray(); + + text_run_array &Array() { return *fArray; } + size_t MaxEntries() const { return fNumEntries; } + + private: + text_run_array *fArray; + size_t fNumEntries; +}; + +extern void FillInQuoteTextRuns(BTextView *view, const char *line, int32 length, + const BFont &font, text_run_array *style, int32 maxStyles = 5); + +#endif /* #ifndef _CONTENT_H */ diff --git a/src/apps/bemail/Enclosures.cpp b/src/apps/bemail/Enclosures.cpp new file mode 100644 index 0000000000..98a321ae3d --- /dev/null +++ b/src/apps/bemail/Enclosures.cpp @@ -0,0 +1,556 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Enclosures.cpp +// The enclosures list view (TListView), the list items (TListItem), +// and the view containing the list and handling the messages (TEnclosuresView). +//-------------------------------------------------------------------- + +#include "Mail.h" +#include "Enclosures.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + + +//==================================================================== + + +static status_t +GetTrackerIcon(BMimeType &type, BBitmap *icon, icon_size iconSize) +{ + // set some icon size related variables + status_t error = B_OK; + BRect bounds; + switch (iconSize) { + case B_MINI_ICON: + bounds.Set(0, 0, 15, 15); + break; + case B_LARGE_ICON: + bounds.Set(0, 0, 31, 31); + break; + default: + error = B_BAD_VALUE; + break; + } + // check parameters and initialization + if (error == B_OK + && (!icon || icon->InitCheck() != B_OK || icon->Bounds() != bounds)) + return B_BAD_VALUE; + + bool success = false; + + // Ask the MIME database for the preferred application for the file type + // and whether this application has a special icon for the type. + char signature[B_MIME_TYPE_LENGTH]; + if (type.GetPreferredApp(signature) == B_OK) { + BMimeType type(signature); + success = (type.GetIconForType(type.Type(), icon, iconSize) == B_OK); + } + + // Ask the MIME database whether there is an icon for the node's file type. + if (error == B_OK && !success) + success = (type.GetIcon(icon, iconSize) == B_OK); + + // Ask the MIME database for the super type and start all over + if (error == B_OK && !success) { + BMimeType super; + if (type.GetSupertype(&super) == B_OK) + return GetTrackerIcon(super, icon, iconSize); + } + + // Return the icon for "application/octet-stream" from the MIME database. + if (error == B_OK && !success) { + // get the "application/octet-stream" icon + BMimeType type("application/octet-stream"); + error = type.GetIcon(icon, iconSize); + } + + return error; +} + + +// #pragma mark - + + +TEnclosuresView::TEnclosuresView(BRect rect, BRect wind_rect) + : BView(rect, "m_enclosures", B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW), + fFocus(false) +{ + rgb_color c; + c.red = c.green = c.blue = VIEW_COLOR; + SetViewColor(c); + + BFont font = *be_plain_font; + font.SetSize(FONT_SIZE); + font_height fHeight; + font.GetHeight(&fHeight); + fOffset = 12; + + BRect r; + r.left = ENCLOSE_TEXT_H + font.StringWidth( + MDR_DIALECT_CHOICE ("Enclosures: ","添付ファイル")) + 5; + r.top = ENCLOSE_FIELD_V; + r.right = wind_rect.right - wind_rect.left - B_V_SCROLL_BAR_WIDTH - 9; + r.bottom = Frame().Height() - 8; + fList = new TListView(r, this); + fList->SetInvocationMessage(new BMessage(LIST_INVOKED)); + + BScrollView *scroll = new BScrollView("", fList, B_FOLLOW_LEFT_RIGHT | + B_FOLLOW_TOP, 0, false, true); + AddChild(scroll); + scroll->ScrollBar(B_VERTICAL)->SetRange(0, 0); +} + + +TEnclosuresView::~TEnclosuresView() +{ + for (int32 index = fList->CountItems();index-- > 0;) + { + TListItem *item = static_cast(fList->ItemAt(index)); + fList->RemoveItem(index); + + if (item->Component() == NULL) + watch_node(item->NodeRef(), B_STOP_WATCHING, this); + delete item; + } +} + + +void +TEnclosuresView::Draw(BRect where) +{ + float offset; + BFont font = *be_plain_font; + + BView::Draw(where); + font.SetSize(FONT_SIZE); + SetFont(&font); + SetHighColor(0, 0, 0); + SetLowColor(VIEW_COLOR, VIEW_COLOR, VIEW_COLOR); + + offset = 12; + + font_height fHeight; + font.GetHeight(&fHeight); + + MovePenTo(ENCLOSE_TEXT_H, ENCLOSE_TEXT_V + fHeight.ascent); + DrawString(ENCLOSE_TEXT); +} + + +void +TEnclosuresView::MessageReceived(BMessage *msg) +{ + switch (msg->what) + { + case LIST_INVOKED: + { + BListView *list; + msg->FindPointer("source", (void **)&list); + if (list) + { + TListItem *item = (TListItem *) (list->ItemAt(msg->FindInt32("index"))); + if (item) + { + BMessenger tracker("application/x-vnd.Be-TRAK"); + if (tracker.IsValid()) + { + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", item->Ref()); + + tracker.SendMessage(&message); + } + } + } + break; + } + + case M_REMOVE: + { + int32 index; + while ((index = fList->CurrentSelection()) >= 0) + { + TListItem *item = (TListItem *) fList->ItemAt(index); + fList->RemoveItem(index); + + if (item->Component()) + { + // remove the component from the mail + TMailWindow *window = dynamic_cast(Window()); + if (window && window->Mail()) + window->Mail()->RemoveComponent(item->Component()); + + (new BAlert("", MDR_DIALECT_CHOICE ( + "Removing enclosures from a forwarded mail is not yet implemented!\n" + "It will not yet work correctly.", + "転送メールから添付ファイルを削除する機能はまだ実装されていません。"), + MDR_DIALECT_CHOICE ("OK","了解")))->Go(); + } + else + watch_node(item->NodeRef(), B_STOP_WATCHING, this); + delete item; + } + break; + } + + case M_SELECT: + fList->Select(0, fList->CountItems() - 1, true); + break; + + case B_SIMPLE_DATA: + case B_REFS_RECEIVED: + case REFS_RECEIVED: + if (msg->HasRef("refs")) + { + bool badType = false; + + int32 index = 0; + entry_ref ref; + while (msg->FindRef("refs", index++, &ref) == B_NO_ERROR) + { + BFile file(&ref, O_RDONLY); + if (file.InitCheck() == B_OK && file.IsFile()) + { + TListItem *item; + for (int16 loop = 0; loop < fList->CountItems(); loop++) + { + item = (TListItem *) fList->ItemAt(loop); + if (ref == *(item->Ref())) + { + fList->Select(loop); + fList->ScrollToSelection(); + continue; + } + } + fList->AddItem(item = new TListItem(&ref)); + fList->Select(fList->CountItems() - 1); + fList->ScrollToSelection(); + + watch_node(item->NodeRef(), B_WATCH_NAME, this); + } + else + badType = true; + } + if (badType) + { + beep(); + (new BAlert("", MDR_DIALECT_CHOICE ( + "Only files can be added as enclosures.", + "添付できるのは、ファイルのみです。"), + MDR_DIALECT_CHOICE ("Ok","了解")))->Go(); + } + } + break; + + case B_NODE_MONITOR: + { + int32 opcode; + if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) + { + dev_t device; + if (msg->FindInt32("device", &device) < B_OK) + break; + ino_t inode; + if (msg->FindInt64("node", &inode) < B_OK) + break; + + for (int32 index = fList->CountItems();index-- > 0;) + { + TListItem *item = static_cast(fList->ItemAt(index)); + + if (device == item->NodeRef()->device + && inode == item->NodeRef()->node) + { + if (opcode == B_ENTRY_REMOVED) + { + // don't hide the item + + //fList->RemoveItem(index); + // + //watch_node(item->NodeRef(), B_STOP_WATCHING, this); + //delete item; + } + else if (opcode == B_ENTRY_MOVED) + { + item->Ref()->device = device; + msg->FindInt64("to directory", &item->Ref()->directory); + + const char *name; + msg->FindString("name", &name); + item->Ref()->set_name(name); + } + + fList->InvalidateItem(index); + break; + } + } + } + break; + } + + default: + BView::MessageReceived(msg); + } +} + + +void +TEnclosuresView::Focus(bool focus) +{ + if (fFocus != focus) + { + fFocus = focus; + Draw(Frame()); + } +} + + +void +TEnclosuresView::AddEnclosuresFromMail(BEmailMessage *mail) +{ + for (int32 i = 0; i < mail->CountComponents(); i++) + { + BMailComponent *component = mail->GetComponent(i); + if (component == mail->Body()) + continue; + + BMailAttachment *attachment = dynamic_cast(component); + if (attachment == NULL) + continue; + + fList->AddItem(new TListItem(component)); + } +} + + +//==================================================================== +// #pragma mark - + + +TListView::TListView(BRect rect, TEnclosuresView *view) + : BListView(rect, "", B_MULTIPLE_SELECTION_LIST, B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT), + fParent(view) +{ +} + + +void +TListView::AttachedToWindow() +{ + BListView::AttachedToWindow(); + + BFont font = *be_plain_font; + font.SetSize(FONT_SIZE); + SetFont(&font); +} + + +void +TListView::MakeFocus(bool focus) +{ + BListView::MakeFocus(focus); + fParent->Focus(focus); +} + + +void +TListView::MouseDown(BPoint point) +{ + int32 buttons; + Looper()->CurrentMessage()->FindInt32("buttons",&buttons); + + if (buttons & B_SECONDARY_MOUSE_BUTTON) + { + BFont font = *be_plain_font; + font.SetSize(10); + + BPopUpMenu menu("enclosure", false, false); + menu.SetFont(&font); + menu.AddItem(new BMenuItem( + MDR_DIALECT_CHOICE ("Open Enclosure","添付ファイルを開く"), + new BMessage(LIST_INVOKED))); + menu.AddItem(new BMenuItem( + MDR_DIALECT_CHOICE ("Remove Enclosure","添付ファイルを削除"), + new BMessage(M_REMOVE))); + + BPoint menuStart = ConvertToScreen(point); + + BMenuItem *item; + if ((item = menu.Go(menuStart)) != NULL) + { + if (item->Command() == LIST_INVOKED) + { + BMessage msg(LIST_INVOKED); + msg.AddPointer("source",this); + msg.AddInt32("index",IndexOf(point)); + Window()->PostMessage(&msg,fParent); + } + else + { + Select(IndexOf(point)); + Window()->PostMessage(item->Command(),fParent); + } + } + } + else + BListView::MouseDown(point); +} + + +void +TListView::KeyDown(const char *bytes, int32 numBytes) +{ + BListView::KeyDown(bytes,numBytes); + + if (numBytes == 1 && *bytes == B_DELETE) + Window()->PostMessage(M_REMOVE, fParent); +} + + +//==================================================================== +// #pragma mark - + + +TListItem::TListItem(entry_ref *ref) +{ + fComponent = NULL; + fRef = *ref; + + BEntry entry(ref); + entry.GetNodeRef(&fNodeRef); +} + + +TListItem::TListItem(BMailComponent *component) + : + fComponent(component) +{ +} + + +void +TListItem::Update(BView *owner, const BFont *font) +{ + BListItem::Update(owner, font); + + if (Height() < 17) // mini icon height + 1 + SetHeight(17); +} + + +void +TListItem::DrawItem(BView *owner, BRect r, bool /* complete */) +{ + if (IsSelected()) { + owner->SetHighColor(180, 180, 180); + owner->SetLowColor(180, 180, 180); + } else { + owner->SetHighColor(255, 255, 255); + owner->SetLowColor(255, 255, 255); + } + owner->FillRect(r); + owner->SetHighColor(0, 0, 0); + + BFont font = *be_plain_font; + font.SetSize(FONT_SIZE); + owner->SetFont(&font); + owner->MovePenTo(r.left + 24, r.bottom - 4); + + if (fComponent) { + // if it's already a mail component, we don't have an icon to + // draw, and the entry_ref is invalid + BMailAttachment *attachment = static_cast(fComponent); + + char name[B_FILE_NAME_LENGTH * 2]; + if (attachment->FileName(name) < B_OK) + strcpy(name, "unnamed"); + + BMimeType type; + if (attachment->MIMEType(&type) == B_OK) + sprintf(name + strlen(name), ", Type: %s", type.Type()); + + owner->DrawString(name); + + BRect iconRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1); + + BBitmap bitmap(iconRect, B_COLOR_8_BIT); + if (GetTrackerIcon(type, &bitmap, B_MINI_ICON) == B_NO_ERROR) { + BRect rect(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15); + owner->SetDrawingMode(B_OP_OVER); + owner->DrawBitmap(&bitmap, iconRect, rect); + owner->SetDrawingMode(B_OP_COPY); + } else { + // ToDo: find some nicer image for this :-) + owner->SetHighColor(150, 150, 150); + owner->FillEllipse(BRect(r.left + 8, r.top + 4, r.left + 16, r.top + 13)); + } + return; + } + + BFile file(&fRef, O_RDONLY); + BEntry entry(&fRef); + BPath path; + if (entry.GetPath(&path) == B_OK && file.InitCheck() == B_OK) { + owner->DrawString(path.Path()); + + BNodeInfo info(&file); + BRect sr(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1); + + BBitmap bitmap(sr, B_COLOR_8_BIT); + if (info.GetTrackerIcon(&bitmap, B_MINI_ICON) == B_NO_ERROR) { + BRect dr(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15); + owner->SetDrawingMode(B_OP_OVER); + owner->DrawBitmap(&bitmap, sr, dr); + owner->SetDrawingMode(B_OP_COPY); + } + } else + owner->DrawString(""); +} + diff --git a/src/apps/bemail/Enclosures.h b/src/apps/bemail/Enclosures.h new file mode 100644 index 0000000000..be54a5d32e --- /dev/null +++ b/src/apps/bemail/Enclosures.h @@ -0,0 +1,129 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Enclosures.h +// +//-------------------------------------------------------------------- + +#ifndef _ENCLOSURES_H +#define _ENCLOSURES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ENCLOSURES_HEIGHT 65 + +#define ENCLOSE_TEXT "Enclosures:" +#define ENCLOSE_TEXT_H 7 +#define ENCLOSE_TEXT_V 3 +#define ENCLOSE_FIELD_V 3 + +class TListView; +class TMailWindow; +class TScrollView; + + +//==================================================================== + +class TEnclosuresView : public BView +{ + public: + TEnclosuresView(BRect, BRect); + ~TEnclosuresView(); + + virtual void Draw(BRect); + virtual void MessageReceived(BMessage*); + void Focus(bool); + void AddEnclosuresFromMail(BEmailMessage *mail); + + TListView *fList; + + private: + bool fFocus; + float fOffset; + TMailWindow *fWindow; +}; + + +//==================================================================== + +class TListView : public BListView +{ + public: + TListView(BRect, TEnclosuresView *); + + virtual void AttachedToWindow(); + virtual void MakeFocus(bool); + virtual void MouseDown(BPoint point); + virtual void KeyDown(const char *bytes,int32 numBytes); + + private: + TEnclosuresView *fParent; +}; + + +//==================================================================== + +class TListItem : public BListItem +{ + public: + TListItem(entry_ref *); + TListItem(BMailComponent *); + + virtual void DrawItem(BView *, BRect, bool); + virtual void Update(BView *, const BFont *); + + BMailComponent *Component() { return fComponent; }; + entry_ref *Ref() { return &fRef; } + node_ref *NodeRef() { return &fNodeRef; } + + private: + BMailComponent *fComponent; + entry_ref fRef; + node_ref fNodeRef; +}; + +#endif // #ifndef _ENCLOSURES_H diff --git a/src/apps/bemail/FieldMsg.h b/src/apps/bemail/FieldMsg.h new file mode 100644 index 0000000000..07ed40edd6 --- /dev/null +++ b/src/apps/bemail/FieldMsg.h @@ -0,0 +1,50 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _FIELD_MSG_H +#define _FIELD_MSG_H + +enum { + FIELD_CHANGED = 0x0800 +}; + +enum { + FIELD_TO = 0x01, + FIELD_SUBJECT = 0x02, + FIELD_CC = 0x04, + FIELD_BCC = 0x08, + FIELD_BODY = 0x10 +}; + +#endif // #ifndef _FIELD_MSG_H diff --git a/src/apps/bemail/FindWindow.cpp b/src/apps/bemail/FindWindow.cpp new file mode 100644 index 0000000000..86c241e3c6 --- /dev/null +++ b/src/apps/bemail/FindWindow.cpp @@ -0,0 +1,295 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// =========================================================================== +// FindWindow.cpp +// Copyright 1996 by Peter Barrett, All rights reserved. +// =========================================================================== + +#include "FindWindow.h" +#include "Mail.h" + +#include +#include +#include +#include +#include + +#include + +void TextBevel(BView& view, BRect r); + +// ============================================================================ +void TextBevel(BView& view, BRect r) +{ + r.InsetBy(-1,-1); + view.SetHighColor(96,96,96); + view.MovePenTo(r.left,r.bottom); + view.StrokeLine(BPoint(r.left,r.top)); + view.StrokeLine(BPoint(r.right,r.top)); + view.SetHighColor(216,216,216); + view.StrokeLine(BPoint(r.right,r.bottom)); + view.StrokeLine(BPoint(r.left,r.bottom)); + r.InsetBy(-1,-1); + view.SetHighColor(192,192,192); + view.MovePenTo(r.left,r.bottom); + view.StrokeLine(BPoint(r.left,r.top)); + view.StrokeLine(BPoint(r.right,r.top)); + view.SetHighColor(255,255,255); + view.StrokeLine(BPoint(r.right,r.bottom)); + view.StrokeLine(BPoint(r.left,r.bottom)); + view.SetHighColor(0,0,0); +} + +// FindWindow is modeless... + +#define FINDBUTTON 'find' + +static BString sPreviousFind = ""; + +FindWindow* FindWindow::mFindWindow = NULL; +BRect FindWindow::mLastPosition(BRect(100,300,300,374)); + +void FindWindow::DoFind(BWindow *window, const char *text) +{ + if (window == NULL) { + long i=0; + while ((bool)(window = be_app->WindowAt(i++))) { // Send the text to a waiting window + if (window != mFindWindow) + if (dynamic_cast(window) != NULL) + break; // Found a window + } + } + + /* ask that window who is in the front */ + window = dynamic_cast(window)->FrontmostWindow(); + if (window == NULL) + return; + +// Found a window, send a find message + + if (!window->Lock()) + return; + BView *focus = window->FindView("m_content"); + window->Unlock(); + + if (focus) + { + BMessage msg(M_FIND); + msg.AddString("findthis",text); + window->PostMessage(&msg, focus); + } +} + +FindPanel::FindPanel(BRect rect) + : BBox(rect, "FindPanel", B_FOLLOW_LEFT_RIGHT, + B_WILL_DRAW) +{ + BRect r = Bounds(); + r.InsetBy(8,8); + r.bottom -= 44; + BRect text = r; + text.OffsetTo(B_ORIGIN); + text.InsetBy(2,2); + + mBTextView = new DialogTextView(r,"BTextView",text,B_FOLLOW_ALL,B_WILL_DRAW); + mBTextView->DisallowChar('\n'); + mBTextView->SetText(sPreviousFind.String()); + mBTextView->MakeFocus(); + AddChild(mBTextView); + + mFindButton = new BButton(BRect(0,0,90,20),"FINDBUTTON", + MDR_DIALECT_CHOICE ("Find","検索"), + new BMessage(FINDBUTTON),B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); + AddChild(mFindButton); + r = mFindButton->Bounds(); + + mFindButton->MoveTo(Bounds().right - r.Width() - 8, + Bounds().bottom - r.Height() - 8); + mFindButton->SetEnabled(sPreviousFind.Length()); +} + +FindPanel::~FindPanel() +{ + sPreviousFind = mBTextView->Text(); +} + +void FindPanel::AttachedToWindow() +{ + BView::AttachedToWindow(); + SetViewColor(216,216,216); + Window()->SetDefaultButton(mFindButton); + mFindButton->SetTarget(this); + mBTextView->MakeFocus(true); + mBTextView->SelectAll(); +} + +void FindPanel::MouseDown(BPoint point) +{ + Window()->Activate(); + BView::MouseDown(point); +} + +void FindPanel::Draw(BRect) +{ + TextBevel(*this,mBTextView->Frame()); +} + +void FindPanel::KeyDown(const char *, int32) +{ + int32 length = mBTextView->TextLength(); + bool enabled = mFindButton->IsEnabled(); + + if (length > 0 && !enabled) + mFindButton->SetEnabled(true); + else if (length == 0 && enabled) + mFindButton->SetEnabled(false); +} + +void FindPanel::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case FINDBUTTON: + Find(); + break; + default: + BView::MessageReceived(msg); + } +} + +void FindPanel::Find() +{ + mBTextView->SelectAll(); + const char *text = mBTextView->Text(); + if (text == NULL || text[0] == 0) return; + + BWindow *window = NULL; + long i=0; + while ((bool)(window = be_app->WindowAt(i++))) { // Send the text to a waiting window + if (window != FindWindow::mFindWindow) + break; // Found a window + } + + if (window) + FindWindow::DoFind(window, text); +} + +// ============================================================================ + + +FindWindow::FindWindow() + : BWindow(FindWindow::mLastPosition, + MDR_DIALECT_CHOICE ("Find","検索"), + B_FLOATING_WINDOW, + B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_WILL_ACCEPT_FIRST_CLICK) +{ + mFindPanel = new FindPanel(Bounds()); + AddChild(mFindPanel); + mFindWindow = this; + Show(); +} + +FindWindow::~FindWindow() +{ + FindWindow::mLastPosition = Frame(); + mFindWindow = NULL; +} + +void FindWindow::Find(BWindow *window) +{ + // eliminate unused parameter warning + (void)window; + + if (mFindWindow == NULL) { + mFindWindow = new FindWindow(); + } else + mFindWindow->Activate(); +} + +void FindWindow::FindAgain(BWindow *window) +{ + if (mFindWindow) { + mFindWindow->Lock(); + mFindWindow->mFindPanel->Find(); + mFindWindow->Unlock(); + } else if (sPreviousFind.Length() != 0) + DoFind(window, sPreviousFind.String()); + else + Find(window); +} + +void FindWindow::SetFindString(const char *string) +{ + sPreviousFind = string; +} + +const char *FindWindow::GetFindString() +{ + return sPreviousFind.String(); +} + + +DialogTextView::DialogTextView(BRect frame, const char *name, BRect textRect, + uint32 resizingMode, uint32 flags) + : BTextView(frame, name, textRect, resizingMode, flags) +{ +} + +void DialogTextView::KeyDown(const char *bytes, int32 numBytes) +{ + BTextView::KeyDown(bytes, numBytes); + if (Parent()) + Parent()->KeyDown(bytes, numBytes); +} + +void DialogTextView::MouseDown(BPoint point) +{ + Window()->Activate(); + BTextView::MouseDown(point); +} + +void DialogTextView::InsertText(const char *inText, int32 inLength, int32 inOffset, + const text_run_array *inRuns) +{ + BTextView::InsertText(inText, inLength, inOffset, inRuns); + if (Parent()) + Parent()->KeyDown(NULL, 0); +} + +void DialogTextView::DeleteText(int32 fromOffset, int32 toOffset) +{ + BTextView::DeleteText(fromOffset, toOffset); + if (Parent()) + Parent()->KeyDown(NULL, 0); +} diff --git a/src/apps/bemail/FindWindow.h b/src/apps/bemail/FindWindow.h new file mode 100644 index 0000000000..b918c9b4e8 --- /dev/null +++ b/src/apps/bemail/FindWindow.h @@ -0,0 +1,105 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// =========================================================================== +// FindWindow.h +// Copyright 1996 by Peter Barrett, All rights reserved. +// =========================================================================== + +#ifndef _FINDWINDOW_H +#define _FINDWINDOW_H + +#include +#include +#include +#include + +class FindPanel; + +// ============================================================================ +// Floating find window, just one of them..... + +class FindWindow : public BWindow { +friend class FindPanel; +public: + FindWindow(); + virtual ~FindWindow(); + + static void FindAgain(BWindow *window); + static void Find(BWindow *window); + static bool IsFindWindowOpen() {return mFindWindow;} + static void Close() + { if (mFindWindow) mFindWindow->PostMessage(B_QUIT_REQUESTED); } + static void SetFindString(const char *string); + static const char* GetFindString(); + +protected: + static void DoFind(BWindow *window, const char *text); + + static FindWindow* mFindWindow; + FindPanel* mFindPanel; + static BRect mLastPosition; +}; + +class FindPanel : public BBox { +public: + FindPanel(BRect rect); + virtual ~FindPanel(); + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage *msg); + virtual void Draw(BRect updateRect); + virtual void KeyDown(const char *bytes, int32 numBytes); + void Find(); + virtual void MouseDown(BPoint point); + +protected: + BButton* mFindButton; + BTextView* mBTextView; +}; + + +class DialogTextView : public BTextView { +public: + DialogTextView(BRect frame, const char *name, BRect textRect, + uint32 resizingMode, uint32 flags); + virtual void KeyDown(const char *bytes, int32 numBytes); + virtual void MouseDown(BPoint point); + virtual void InsertText(const char *inText, int32 inLength, + int32 inOffset, const text_run_array *inRuns); + virtual void DeleteText(int32 fromOffset, int32 toOffset); +}; + +// ============================================================================ + +#endif // #ifndef _FINDWINDOW_H diff --git a/src/apps/bemail/Header.cpp b/src/apps/bemail/Header.cpp new file mode 100644 index 0000000000..b67463d11d --- /dev/null +++ b/src/apps/bemail/Header.cpp @@ -0,0 +1,1037 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Header.cpp +// +//-------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Mail.h" +#include "Header.h" +#include "Utilities.h" +#include "QueryMenu.h" +#include "FieldMsg.h" +#include "Prefs.h" + +#include + +extern uint32 gDefaultChain; + +const char *kDateLabel = "Date:"; +const uint32 kMsgFrom = 'hFrm'; +const uint32 kMsgEncoding = 'encd'; + + +class QPopupMenu : public QueryMenu { + public: + QPopupMenu(const char *title); + + private: + void AddPersonItem(const entry_ref *ref, ino_t node, BString &name, BString &email, + const char *attr, BMenu *groupMenu, BMenuItem *superItem); + + protected: + virtual void EntryCreated(const entry_ref &ref, ino_t node); + virtual void EntryRemoved(ino_t node); + + int32 fGroups; // Current number of "group" submenus. Includes All People if present. +}; + +//==================================================================== + +struct CompareBStrings +{ + bool operator()(const BString *s1, const BString *s2) const + { + return (s1->Compare(*s2) < 0); + } +}; + +//==================================================================== + + +THeaderView::THeaderView ( + BRect rect, + BRect windowRect, + bool incoming, + BEmailMessage *mail, + bool resending, + uint32 defaultCharacterSet + ) : BBox(rect, "m_header", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW, B_NO_BORDER), + fAccountMenu(NULL), + fEncodingMenu(NULL), + fChain(gDefaultChain), + fAccountTo(NULL), + fAccount(NULL), + fBcc(NULL), + fCc(NULL), + fSubject(NULL), + fTo(NULL), + fDate(NULL), + fIncoming(incoming), + fCharacterSetUserSees(defaultCharacterSet), + fResending(resending), + fBccMenu(NULL), + fCcMenu(NULL), + fToMenu(NULL) +{ + BMenuField *field; + BMessage *msg; + + BFont font = *be_plain_font; + font.SetSize(FONT_SIZE); + SetFont(&font); + float x = font.StringWidth( /* The longest title string in the header area */ + MDR_DIALECT_CHOICE ("Enclosures: ","添付ファイル:")) + 9; + float y = TO_FIELD_V; + + if (!fIncoming) + { + InitEmailCompletion(); + InitGroupCompletion(); + } + + // Prepare the character set selection pop-up menu (we tell the user that + // it is the Encoding menu, even though it is really the character set). + // It may appear in the first line, to the right of the From box if the + // user is reading an e-mail. It appears on the second line, to the right + // of the e-mail account menu, if the user is composing a message. It lets + // the user quickly select a character set different from the application + // wide default one, and also shows them which character set is active. If + // you are reading a message, you also see an item that says "Automatic" + // for automatic decoding character set choice. It can slide around as the + // window is resized when viewing a message, but not when composing + // (because the adjacent pop-up menu can't resize dynamically due to a BeOS + // bug). + + bool marked; + float widestCharacterSet; + + fEncodingMenu = new BPopUpMenu (B_EMPTY_STRING); + marked = false; + widestCharacterSet = 0; + for (int32 i = 0; true; i++) { + if (kEncodings[i].flavor == B_MAIL_NULL_CONVERSION && (resending || !fIncoming)) + break; // Composing a new message, don't display last "Automatic" item. + msg = new BMessage(kMsgEncoding); + msg->AddInt32 ("charset", kEncodings[i].flavor); + BMenuItem *item = new BMenuItem (kEncodings[i].name, msg); + if (kEncodings[i].flavor == fCharacterSetUserSees && !marked) { + item->SetMarked (true); + marked = true; + } + fEncodingMenu->AddItem (item); + if (font.StringWidth (kEncodings[i].name) > widestCharacterSet) + widestCharacterSet = font.StringWidth (kEncodings[i].name); + if (kEncodings[i].flavor == B_MAIL_NULL_CONVERSION) + break; // No more character set choices after this one, stop. + } + + // First line of the header, From for reading e-mails (includes the + // character set choice at the right), To when composing (nothing else in + // the row). + + BRect r; + char string[20]; + if (fIncoming && !resending) + { + // Set up the character set pop-up menu on the right of "To" box. + r.Set (windowRect.Width() - widestCharacterSet - + font.StringWidth (DECODING_TEXT) - 2 * SEPARATOR_MARGIN, + y - 2, windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT + 2); + field = new BMenuField (r, "decoding", DECODING_TEXT, fEncodingMenu, + true /* fixedSize */, + B_FOLLOW_TOP | B_FOLLOW_RIGHT, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + field->SetFont (&font); + field->SetDivider (font.StringWidth(DECODING_TEXT) + 5); + AddChild(field); + r.Set(x - font.StringWidth(FROM_TEXT) - 11, y, + field->Frame().left - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); + sprintf(string, FROM_TEXT); + } + else + { + r.Set(x - 11, y, windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); + string[0] = 0; + } + y += FIELD_HEIGHT; + fTo = new TTextControl(r, string, new BMessage(TO_FIELD), fIncoming, resending, + B_FOLLOW_LEFT_RIGHT); + + if (!fIncoming || resending) + { + fTo->SetChoiceList(&fEmailList); + fTo->SetAutoComplete(true); + } + AddChild(fTo); + msg = new BMessage(FIELD_CHANGED); + msg->AddInt32("bitmask", FIELD_TO); + fTo->SetModificationMessage(msg); + + if (!fIncoming || resending) + { + r.right = r.left + 8; + r.left = r.right - be_plain_font->StringWidth(TO_TEXT) - 30; + r.top -= 1; + fToMenu = new QPopupMenu(TO_TEXT); + field = new BMenuField(r, "", "", fToMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); + field->SetDivider(0.0); + field->SetEnabled(true); + AddChild(field); + } + + // "From:" accounts Menu and Encoding Menu. + if (!fIncoming || resending) + { + // Put the character set box on the right of the From field. + r.Set (windowRect.Width() - widestCharacterSet - + font.StringWidth (ENCODING_TEXT) - 2 * SEPARATOR_MARGIN, + y - 2, windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT + 2); + field = new BMenuField (r, "encoding", ENCODING_TEXT, fEncodingMenu, + true /* fixedSize */, + B_FOLLOW_TOP | B_FOLLOW_LEFT, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + field->SetFont (&font); + field->SetDivider (font.StringWidth(ENCODING_TEXT) + 5); + AddChild(field); + + // And now the "from account" pop-up menu, on the left side, taking the + // remaining space. + + fAccountMenu = new BPopUpMenu(B_EMPTY_STRING); + + BList chains; + if (GetOutboundMailChains(&chains) >= B_OK) + { + marked = false; + for (int32 i = 0;i < chains.CountItems();i++) + { + BMailChain *chain = (BMailChain *)chains.ItemAt(i); + BString name = chain->Name(); + if ((msg = chain->MetaData()) != NULL) + { + name << ": " << msg->FindString("real_name") + << " <" << msg->FindString("reply_to") << ">"; + } + BMenuItem *item = new BMenuItem(name.String(),msg = new BMessage(kMsgFrom)); + + msg->AddInt32("id",chain->ID()); + + if (gDefaultChain == chain->ID()) + { + item->SetMarked(true); + marked = true; + } + fAccountMenu->AddItem(item); + delete chain; + } + if (!marked) + { + BMenuItem *item = fAccountMenu->ItemAt(0); + if (item != NULL) + { + item->SetMarked(true); + fChain = item->Message()->FindInt32("id"); + } + else + { + fAccountMenu->AddItem(item = new BMenuItem("",NULL)); + item->SetEnabled(false); + fChain = ~0UL; + } + // default chain is invalid, set to marked + gDefaultChain = fChain; + } + } + r.Set(x - font.StringWidth(FROM_TEXT) - 11, y - 2, + field->Frame().left - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT + 2); + field = new BMenuField(r, "account", FROM_TEXT, fAccountMenu, + true /* fixedSize */, + B_FOLLOW_TOP | B_FOLLOW_LEFT, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + field->SetFont(&font); + field->SetDivider(font.StringWidth(FROM_TEXT) + 11); + AddChild(field); + + y += FIELD_HEIGHT; + } + else // To: account + { + bool account = count_pop_accounts() > 0; + + r.Set(x - font.StringWidth(TO_TEXT) - 11, y, + windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); + if (account) + r.right -= SEPARATOR_MARGIN + ACCOUNT_FIELD_WIDTH; + fAccountTo = new TTextControl(r, TO_TEXT, NULL, fIncoming, false, B_FOLLOW_LEFT_RIGHT); + fAccountTo->SetEnabled(false); + AddChild(fAccountTo); + + if (account) + { + r.left = r.right + 6; r.right = windowRect.Width() - SEPARATOR_MARGIN; + fAccount = new TTextControl(r, ACCOUNT_TEXT, NULL, fIncoming, false, B_FOLLOW_RIGHT | B_FOLLOW_TOP); + fAccount->SetEnabled(false); + AddChild(fAccount); + } + y += FIELD_HEIGHT; + } + + --y; + r.Set(x - font.StringWidth(SUBJECT_TEXT) - 11, y, + windowRect.Width() - SEPARATOR_MARGIN, y + TO_FIELD_HEIGHT); + y += FIELD_HEIGHT; + fSubject = new TTextControl(r, SUBJECT_TEXT, new BMessage(SUBJECT_FIELD), + fIncoming, false, B_FOLLOW_LEFT_RIGHT); + AddChild(fSubject); + (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_SUBJECT); + fSubject->SetModificationMessage(msg); + + if (fResending) + fSubject->SetEnabled(false); + + --y; + if (!fIncoming) + { + r.Set(x - 11, y, CC_FIELD_H + CC_FIELD_WIDTH, y + CC_FIELD_HEIGHT); + fCc = new TTextControl(r, "", new BMessage(CC_FIELD), fIncoming, false); + fCc->SetChoiceList(&fEmailList); + fCc->SetAutoComplete(true); + AddChild(fCc); + (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_CC); + fCc->SetModificationMessage(msg); + + r.right = r.left + 9; + r.left = r.right - be_plain_font->StringWidth(CC_TEXT) - 30; + r.top -= 1; + fCcMenu = new QPopupMenu(CC_TEXT); + field = new BMenuField(r, "", "", fCcMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); + + field->SetDivider(0.0); + field->SetEnabled(true); + AddChild(field); + + r.Set(BCC_FIELD_H + be_plain_font->StringWidth(BCC_TEXT), y, + windowRect.Width() - SEPARATOR_MARGIN, y + BCC_FIELD_HEIGHT); + y += FIELD_HEIGHT; + fBcc = new TTextControl(r, "", new BMessage(BCC_FIELD), + fIncoming, false, B_FOLLOW_LEFT_RIGHT); + fBcc->SetChoiceList(&fEmailList); + fBcc->SetAutoComplete(true); + AddChild(fBcc); + (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_BCC); + fBcc->SetModificationMessage(msg); + + r.right = r.left + 9; + r.left = r.right - be_plain_font->StringWidth(BCC_TEXT) - 30; + r.top -= 1; + fBccMenu = new QPopupMenu(BCC_TEXT); + field = new BMenuField(r, "", "", fBccMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); + field->SetDivider(0.0); + field->SetEnabled(true); + AddChild(field); + } + else + { + r.Set(x - font.StringWidth(kDateLabel) - 10, y + 4, + windowRect.Width(), y + TO_FIELD_HEIGHT + 1); + y += TO_FIELD_HEIGHT + 5; + fDate = new BStringView(r, "", ""); + AddChild(fDate); + fDate->SetFont(&font); + fDate->SetHighColor(0, 0, 0); + + LoadMessage(mail); + } + ResizeTo(Bounds().Width(),y); +} + + +void +THeaderView::InitEmailCompletion() +{ + // get boot volume + BVolume volume; + BVolumeRoster().GetBootVolume(&volume); + + BQuery query; + query.SetVolume(&volume); + query.SetPredicate("META:email=**"); + // Due to R5 BFS bugs, you need two stars, META:email=** for the query. + // META:email="*" will just return one entry and stop, same with + // META:email=* and a few other variations. Grumble. + query.Fetch(); + entry_ref ref; + + while (query.GetNextRef (&ref) == B_OK) + { + BNode file; + if (file.SetTo(&ref) == B_OK) + { + // Add the e-mail address as an auto-complete string. + BString email; + if (file.ReadAttrString("META:email", &email) >= B_OK) + fEmailList.AddChoice(email.String()); + + // Also add the quoted full name as an auto-complete string. Can't + // do unquoted since auto-complete isn't that smart, so the user + // will have to type a quote mark if he wants to select someone by + // name. + BString fullName; + if (file.ReadAttrString("META:name", &fullName) >= B_OK) { + if (email.FindFirst('<') < 0) { + email.ReplaceAll('>', '_'); + email.Prepend("<"); + email.Append(">"); + } + fullName.ReplaceAll('\"', '_'); + fullName.Prepend("\""); + fullName << "\" " << email; + fEmailList.AddChoice(fullName.String()); + } + + // support for 3rd-party People apps. Looks like a job for + // multiple keyword (so you can have several e-mail addresses in + // one attribute, perhaps comma separated) indices! Which aren't + // yet in BFS. + for (int16 i = 2;i < 6;i++) + { + char attr[16]; + sprintf(attr,"META:email%d",i); + if (file.ReadAttrString(attr,&email) >= B_OK) + fEmailList.AddChoice(email.String()); + } + } + } +} + + +void +THeaderView::InitGroupCompletion() +{ + // get boot volume + BVolume volume; + BVolumeRoster().GetBootVolume(&volume); + + // Build a list of all unique groups and the addresses they expand to. + BQuery query; + query.SetVolume(&volume); + query.SetPredicate("META:group=**"); + query.Fetch(); + + map group_map; + entry_ref ref; + BNode file; + while (query.GetNextRef(&ref) == B_OK) + { + if (file.SetTo(&ref) != B_OK) + continue; + + BString groups; + if (ReadAttrString(&file, "META:group", &groups) < B_OK || groups.Length() == 0) + continue; + + BString address; + ReadAttrString(&file, "META:email", &address); + + // avoid adding an empty address + if (address.Length() == 0) + continue; + + char *grp = groups.LockBuffer(groups.Length()); + char *next = strchr(grp, ','); + + for (;;) + { + if (next) *next = 0; + while (*grp && *grp == ' ') grp++; + + BString *group = new BString(grp); + BString *addressListString = NULL; + + // nobody is in this group yet, start it off + if (group_map[group] == NULL) + { + addressListString = new BString(*group); + addressListString->Append(" "); + group_map[group] = addressListString; + } + else + { + addressListString = group_map[group]; + addressListString->Append(", "); + delete group; + } + + // Append the user's address to the end of the string with the + // comma separated list of addresses. If not present, add the + // < and > brackets around the address. + + if (address.FindFirst ('<') < 0) { + address.ReplaceAll ('>', '_'); + address.Prepend ("<"); + address.Append(">"); + } + addressListString->Append(address); + + if (!next) + break; + + grp = next+1; + next = strchr(grp, ','); + } + } + + map::iterator iter; + for (iter = group_map.begin(); iter != group_map.end();) + { + BString *grp = iter->first; + BString *addr = iter->second; + fEmailList.AddChoice(addr->String()); + ++iter; + group_map.erase(grp); + delete grp; + delete addr; + } +} + + +void +THeaderView::MessageReceived(BMessage *msg) +{ + switch (msg->what) + { + case B_SIMPLE_DATA: + { + BTextView *textView = dynamic_cast(Window()->CurrentFocus()); + if (dynamic_cast(textView->Parent()) != NULL) + textView->Parent()->MessageReceived(msg); + else + { + BMessage message(*msg); + message.what = REFS_RECEIVED; + Window()->PostMessage(&message, Window()); + } + break; + } + + case kMsgFrom: + { + BMenuItem *item; + if (msg->FindPointer("source", (void **)&item) >= B_OK) + item->SetMarked(true); + + uint32 chain; + if (msg->FindInt32("id",(int32 *)&chain) >= B_OK) + fChain = chain; + break; + } + + case kMsgEncoding: + { + BMessage message(*msg); + int32 tempInt; + + if (msg->FindInt32("charset", &tempInt) == B_OK) + fCharacterSetUserSees = tempInt; + + message.what = CHARSET_CHOICE_MADE; + message.AddInt32 ("charset", fCharacterSetUserSees); + Window()->PostMessage (&message, Window()); + break; + } + } +} + + +void +THeaderView::AttachedToWindow(void) +{ + if (fToMenu) + { + fToMenu->SetTargetForItems(fTo); + fToMenu->SetPredicate("META:email=**"); + } + if (fCcMenu) + { + fCcMenu->SetTargetForItems(fCc); + fCcMenu->SetPredicate("META:email=**"); + } + if (fBccMenu) + { + fBccMenu->SetTargetForItems(fBcc); + fBccMenu->SetPredicate("META:email=**"); + } + if (fTo) + fTo->SetTarget(Looper()); + if (fSubject) + fSubject->SetTarget(Looper()); + if (fCc) + fCc->SetTarget(Looper()); + if (fBcc) + fBcc->SetTarget(Looper()); + if (fAccount) + fAccount->SetTarget(Looper()); + if (fAccountMenu) + fAccountMenu->SetTargetForItems(this); + if (fEncodingMenu) + fEncodingMenu->SetTargetForItems(this); + + BBox::AttachedToWindow(); +} + + +status_t +THeaderView::LoadMessage(BEmailMessage *mail) +{ + // + // Set the date on this message + // + const char *dateField = mail->Date(); + char string[256]; + sprintf(string, "%s %s", kDateLabel, dateField != NULL ? dateField : "Unknown"); + fDate->SetText(string); + + // + // Set contents of header fields + // + if (fIncoming && !fResending) + { + if (fBcc != NULL) + fBcc->SetEnabled(false); + + if (fCc != NULL) + fCc->SetEnabled(false); + + if (fAccount != NULL) + fAccount->SetEnabled(false); + + if (fAccountTo != NULL) + fAccountTo->SetEnabled(false); + + fSubject->SetEnabled(false); + fTo->SetEnabled(false); + } + + // Set Subject: & From: fields + fSubject->SetText(mail->Subject()); + fTo->SetText(mail->From()); + + // Set Account/To Field + if (fAccountTo != NULL) + fAccountTo->SetText(mail->To()); + + if (fAccount != NULL && mail->GetAccountName(string,sizeof(string)) == B_OK) + fAccount->SetText(string); + + return B_OK; +} + + +//==================================================================== +// #pragma mark - + + +TTextControl::TTextControl(BRect rect, char *label, BMessage *msg, + bool incoming, bool resending, int32 resizingMode) + : BComboBox(rect, "happy", label, msg, resizingMode) + //:BTextControl(rect, "happy", label, "", msg, resizingMode) +{ + strcpy(fLabel, label); + fCommand = msg != NULL ? msg->what : 0UL; + fIncoming = incoming; + fResending = resending; +} + + +void +TTextControl::AttachedToWindow() +{ + BFont font = *be_plain_font; + BTextView *text; + + SetHighColor(0, 0, 0); + // BTextControl::AttachedToWindow(); + BComboBox::AttachedToWindow(); + font.SetSize(FONT_SIZE); + SetFont(&font); + + SetDivider(StringWidth(fLabel) + 6); + text = (BTextView *)ChildAt(0); + text->SetFont(&font); +} + + +void +TTextControl::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case B_SIMPLE_DATA: + if (!fIncoming || fResending) { + BMessage message(REFS_RECEIVED); + bool enclosure = false; + BString addressList; + // Batch up the addresses to be added, since we can only + // insert a few times before deadlocking since inserting + // sends a notification message to the window BLooper, + // which is busy doing this insert. BeOS message queues + // are annoyingly limited in their design. + + entry_ref ref; + for (int32 index = 0;msg->FindRef("refs", index, &ref) == B_OK; index++) { + BFile file(&ref, B_READ_ONLY); + if (file.InitCheck() == B_NO_ERROR) { + BNodeInfo node(&file); + char type[B_FILE_NAME_LENGTH]; + node.GetType(type); + + if (fCommand != SUBJECT_FIELD && + !strcmp(type,"application/x-person")) { + // add person's E-mail address to the To: field + + const char *attr; + if (msg->FindString("attr", &attr) < B_OK) + attr = "META:email"; // If not META:email3 etc. + + BString email; + ReadAttrString(&file,attr,&email); + + /* we got something... */ + if (email.Length() > 0) { + /* see if we can get a username as well */ + BString name; + ReadAttrString(&file,"META:name",&name); + + BString address; + /* if we have no Name, just use the email address */ + if (name.Length() == 0) + address = email; + else { + /* otherwise, pretty-format it */ + address << "\"" << name << "\" <" << email << ">"; + } + if (addressList.Length() > 0) + addressList << ", "; + addressList << address; + } + } else { + enclosure = true; + message.AddRef("refs", &ref); + } + } + } + + if (addressList.Length() > 0) { + BTextView *textView = TextView(); + int end = textView->TextLength(); + if (end != 0) { + textView->Select(end, end); + textView->Insert(", "); + } + textView->Insert(addressList.String()); + } + + if (enclosure) + Window()->PostMessage(&message, Window()); + } + break; + + case M_SELECT: + { + BTextView *textView = (BTextView *)ChildAt(0); + if (textView != NULL) + textView->Select(0, textView->TextLength()); + break; + } + + default: + // BTextControl::MessageReceived(msg); + BComboBox::MessageReceived(msg); + } +} + + +bool +TTextControl::HasFocus() +{ + BTextView *textView = TextView(); + + return textView->IsFocus(); +} + + +//==================================================================== +// QPopupMenu (definition at the beginning of this file) +// #pragma mark - + + +QPopupMenu::QPopupMenu(const char *title) + : QueryMenu(title, true), + fGroups(0) +{ +} + + +void +QPopupMenu::AddPersonItem(const entry_ref *ref, ino_t node, BString &name, + BString &email, const char *attr, BMenu *groupMenu, BMenuItem *superItem) +{ + BString label; + BString sortKey; + // For alphabetical order sorting, usually last name. + + // if we have no Name, just use the email address + if (name.Length() == 0) { + label = email; + sortKey = email; + } else { + // otherwise, pretty-format it + label << name << " (" << email << ")"; + + // Extract the last name (last word in the name), + // removing trailing and leading spaces. + const char *nameStart = name.String(); + const char *string = nameStart + strlen(nameStart) - 1; + const char *wordEnd; + + while (string >= nameStart && isspace(*string)) + string--; + wordEnd = string + 1; // Points to just after last word. + while (string >= nameStart && !isspace(*string)) + string--; + string++; // Point to first letter in the word. + if (wordEnd > string) + sortKey.SetTo(string, wordEnd - string); + else // Blank name, pretend that the last name is after it. + string = nameStart + strlen(nameStart); + + // Append the first names to the end, so that people with the same last + // name get sorted by first name. Note no space between the end of the + // last name and the start of the first names, but that shouldn't + // matter for sorting. + sortKey.Append(nameStart, string - nameStart); + } + + // The target (a TTextControl) will examine all the People files specified + // and add the emails and names to the string it is displaying (same code + // is used for drag and drop of People files). + BMessage *msg = new BMessage(B_SIMPLE_DATA); + msg->AddRef("refs", ref); + msg->AddInt64("node", node); + if (attr) // For nonstandard e-mail attributes, like META:email3 + msg->AddString("attr", attr); + msg->AddString("sortkey", sortKey); + + BMenuItem *newItem = new BMenuItem(label.String(), msg); + if (fTargetHandler) + newItem->SetTarget(fTargetHandler); + + // If no group, just add it to ourself; else add it to group menu + BMenu *parentMenu = groupMenu ? groupMenu : this; + if (groupMenu) { + // Add ref to group super item. + BMessage *superMsg = superItem->Message(); + superMsg->AddRef("refs", ref); + } + + // Add it to the appropriate menu. Use alphabetical order by sortKey to + // insert it in the right spot (a dumb linear search so this will be slow). + // Start searching from the end of the menu, since the main menu includes + // all the groups at the top and we don't want to mix it in with them. + // Thus the search starts at the bottom and ends when we hit a separator + // line or the top of the menu. + + int32 index = parentMenu->CountItems(); + while (index-- > 0) { + BMenuItem *item = parentMenu->ItemAt(index); + if (item == NULL || dynamic_cast(item) != NULL) + break; + + BMessage *message = item->Message(); + BString key; + + // Stop when testKey < sortKey. + if (message != NULL + && message->FindString("sortkey", &key) == B_OK + && ICompare(key, sortKey) < 0) + break; + } + + if (!parentMenu->AddItem(newItem, index + 1)) { + fprintf (stderr, "QPopupMenu::AddPersonItem: Unable to add menu " + "item \"%s\" at index %ld.\n", sortKey.String(), index + 1); + delete newItem; + } +} + + +void +QPopupMenu::EntryCreated(const entry_ref &ref, ino_t node) +{ + BNode file; + if (file.SetTo(&ref) < B_OK) + return; + + // Make sure the pop-up menu is ready for additions. Need a bunch of + // groups at the top, a divider line, and miscellaneous people added below + // the line. + + int32 items = CountItems(); + if (!items) + AddSeparatorItem(); + + // Does the file have a group attribute? OK to have none. + BString groups; + const char *kNoGroup = "NoGroup!"; + ReadAttrString(&file, "META:group", &groups); + if (groups.Length() <= 0) + groups = kNoGroup; + + // Add the e-mail address to the all people group. Then add it to all the + // group menus that it exists in (based on the comma separated list of + // groups from the People file), optionally making the group menu if it + // doesn't exist. If it's in the special NoGroup! list, then add it below + // the groups. + + bool allPeopleGroupDone = false; + BMenu *groupMenu; + do { + BString group; + + if (!allPeopleGroupDone) { + // Create the default group for all people, if it doesn't exist yet. + group = "All People"; + allPeopleGroupDone = true; + } else { + // Break out the next group from the comma separated string. + int32 comma; + if ((comma = groups.FindFirst(',')) > 0) { + groups.MoveInto(group, 0, comma); + groups.Remove(0, 1); + } else + group.Adopt(groups); + } + + // trim white spaces + int32 i = 0; + for (i = 0; isspace(group.ByteAt(i)); i++) {} + if (i) + group.Remove(0, i); + for (i = group.Length() - 1; isspace(group.ByteAt(i)); i--) {} + group.Truncate(i + 1); + + groupMenu = NULL; + BMenuItem *superItem = NULL; // Corresponding item for group menu. + + if (group.Length() > 0 && group != kNoGroup) { + BMenu *sub; + + // Look for submenu with label == group name + for (int32 i = 0; i < items; i++) { + if ((sub = SubmenuAt(i)) != NULL) { + superItem = sub->Superitem(); + if (!strcmp(superItem->Label(), group.String())) { + groupMenu = sub; + i++; + break; + } + } + } + + // If no submenu, create one + if (!groupMenu) { + // Find where it should go (alphabetical) + int32 mindex = 0; + for (; mindex < fGroups; mindex++) { + if (strcmp(ItemAt(mindex)->Label(), group.String()) > 0) + break; + } + + groupMenu = new BMenu(group.String()); + groupMenu->SetFont(be_plain_font); + AddItem(groupMenu, mindex); + + superItem = groupMenu->Superitem(); + superItem->SetMessage(new BMessage(B_SIMPLE_DATA)); + if (fTargetHandler) + superItem->SetTarget(fTargetHandler); + + fGroups++; + } + } + + BString name; + ReadAttrString(&file, "META:name", &name); + + BString email; + ReadAttrString(&file, "META:email", &email); + + if (email.Length() != 0 || name.Length() != 0) + AddPersonItem(&ref, node, name, email, NULL, groupMenu, superItem); + + // support for 3rd-party People apps + for (int16 i = 2; i < 6; i++) { + char attr[16]; + sprintf(attr, "META:email%d", i); + if (ReadAttrString(&file, attr, &email) >= B_OK && email.Length() > 0) + AddPersonItem(&ref, node, name, email, attr, groupMenu, superItem); + } + } while (groups.Length() > 0); +} + + +void +QPopupMenu::EntryRemoved(ino_t /*node*/) +{ +} diff --git a/src/apps/bemail/Header.h b/src/apps/bemail/Header.h new file mode 100644 index 0000000000..2a0035c1c2 --- /dev/null +++ b/src/apps/bemail/Header.h @@ -0,0 +1,149 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Header.h +// +//-------------------------------------------------------------------- + +#ifndef _HEADER_H +#define _HEADER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "ComboBox.h" + +#define TO_TEXT "To:" +#define FROM_TEXT "From:" +#define ENCODING_TEXT "Encoding:" +#define DECODING_TEXT "Decoding:" +#define TO_FIELD_H 39 +#define FROM_FIELD_H 31 +#define TO_FIELD_V 7 +#define TO_FIELD_WIDTH 270 +#define FROM_FIELD_WIDTH 280 +#define TO_FIELD_HEIGHT 16 +#define FIELD_HEIGHT 24 + +#define ACCOUNT_TEXT "Account:" +#define ACCOUNT_FIELD_WIDTH 165 + +#define SUBJECT_TEXT "Subject:" +#define SUBJECT_FIELD_H 18 +#define SUBJECT_FIELD_V 33 +#define SUBJECT_FIELD_WIDTH 270 +#define SUBJECT_FIELD_HEIGHT 16 + +#define CC_TEXT "CC:" +#define CC_FIELD_H 40 +#define CC_FIELD_V 58 +#define CC_FIELD_WIDTH 192 +#define CC_FIELD_HEIGHT 16 + +#define BCC_TEXT "BCC:" +#define BCC_FIELD_H 268 +#define BCC_FIELD_V 58 +#define BCC_FIELD_WIDTH 197 +#define BCC_FIELD_HEIGHT 16 + +class TTextControl; +class BFile; +class BMenuField; +class BMenuItem; +class BPopupMenu; +class QPopupMenu; + +//==================================================================== + +class THeaderView : public BBox +{ + public: + THeaderView(BRect, BRect, bool incoming, BEmailMessage *mail, bool resending, uint32 defaultCharacterSet); + + virtual void MessageReceived(BMessage *); + virtual void AttachedToWindow(void); + status_t LoadMessage(BEmailMessage *); + + BPopUpMenu *fAccountMenu; + BPopUpMenu *fEncodingMenu; + int32 fChain; + TTextControl *fAccountTo; + TTextControl *fAccount; + TTextControl *fBcc; + TTextControl *fCc; + TTextControl *fSubject; + TTextControl *fTo; + BStringView *fDate; + bool fIncoming; + uint32 fCharacterSetUserSees; + + private: + void InitEmailCompletion(); + void InitGroupCompletion(); + + bool fResending; + QPopupMenu *fBccMenu; + QPopupMenu *fCcMenu; + QPopupMenu *fToMenu; + BDefaultChoiceList fEmailList; +}; + +//==================================================================== + +class TTextControl : public BComboBox +{ + public: + TTextControl(BRect, char*, BMessage*, bool, bool, int32 resizingMode = B_FOLLOW_NONE); + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage*); + + // returns focus for the text view + bool HasFocus(); + + private: + bool fIncoming; + bool fResending; + char fLabel[100]; + int32 fCommand; +}; + +#endif /* _HEADER_H */ diff --git a/src/apps/bemail/Jamfile b/src/apps/bemail/Jamfile new file mode 100644 index 0000000000..ef4359c6bd --- /dev/null +++ b/src/apps/bemail/Jamfile @@ -0,0 +1,24 @@ +SubDir OBOS_TOP src apps bemail ; + +UsePrivateHeaders mail ; + +AddResources BeMail : BeMail.rsrc ; +App BeMail : + BmapButton.cpp + ButtonBar.cpp + ComboBox.cpp + Content.cpp + Enclosures.cpp + FindWindow.cpp + Header.cpp + Mail.cpp + Prefs.cpp + QueryMenu.cpp + Signature.cpp + Status.cpp + Utilities.cpp + WIndex.cpp + Words.cpp + KUndoBuffer.cpp ; + +LinkSharedOSLibs BeMail : be mail tracker stdc++.r4 ; diff --git a/src/apps/bemail/KUndoBuffer.cpp b/src/apps/bemail/KUndoBuffer.cpp new file mode 100644 index 0000000000..28bb7152d4 --- /dev/null +++ b/src/apps/bemail/KUndoBuffer.cpp @@ -0,0 +1,321 @@ +#include +#include +#include +#include "KUndoBuffer.h" + + +/* + KUndoItem +KUndoBuffer に格納される Undo/Redo 情報を記憶するクラス +(The class which remembers the Undo/Redo information which is housed in the KUndoBuffer) +*/ +KUndoItem::KUndoItem(const char *redo_text, + int32 length, + int32 offset, + undo_type history, + int32 cursor_pos) +{ + Offset = offset; + Length = length; + History = history; + CursorPos = cursor_pos; + + if (redo_text!=NULL) { + RedoText = (char *)malloc(length); + memcpy(RedoText, redo_text, length); + if (RedoText!=NULL) { + Status = B_OK; + } else { + Status = B_ERROR; + } + } +} + +KUndoItem::~KUndoItem() +{ + free(RedoText); +} + +status_t +KUndoItem::InitCheck() +{ + return Status; +} + +void +KUndoItem::Merge(const char *text, int32 length) +{ + RedoText = (char *)realloc(RedoText, Length + length); + memcpy(&RedoText[Length], text, length); + Length += length; +} + + +/* + KUndoBuffer +Undo/Redo情報リストを管理するクラス。 +(The class which manages the Undo/Redo information list.) +このクラス自体は、BTextViewに影響を及ぼすものではない。 +(This class itself is not something which exerts influence on the BTextView.) +あくまで、アプリケーションがUndo/Redoを実装するための補助にすぎない。 +(To the last, it is no more than an assistance because application mounts the Undo/Redo.) +*/ +KUndoBuffer::KUndoBuffer():BList(1024) +{ + FIndex = 0; + Off(); + FNewItem = true; +} + +KUndoBuffer::~KUndoBuffer() +{ + MakeEmpty(); +} + +/* +Undo情報を追加する。 +(Undo information is added.) +リスト途中に追加したら、以降はRedoの必要がなくなるため、 +追加位置以降のUndo情報があれば削除する。 +(When it adds on the list middle, if later because necessity of the Redo is +gone, there is Undo information after the additional position, it deletes.) +*/ +bool +KUndoBuffer::AddItem(KUndoItem *item, int32 index) +{ + for (int32 i=CountItems()-1; i>=index; i--) { + RemoveItem(i); + } + return AddItem(item); +} + +bool +KUndoBuffer::AddItem(KUndoItem *item) +{ + return BList::AddItem(item); +} + +void +KUndoBuffer::MakeEmpty(void) +{ + for(int32 i=CountItems()-1; i>=0;i--) { + RemoveItem(i); + } +} + +KUndoItem * +KUndoBuffer::RemoveItem(int32 index) +{ + if (FIndex>=CountItems()) FIndex--; + delete this->ItemAt(index); + return (KUndoItem *)BList::RemoveItem(index); +} + +KUndoItem * +KUndoBuffer::ItemAt(int32 index) const +{ + return (KUndoItem *)BList::ItemAt(index); +} + +/* + Off() 呼び出し後は、On()を実行しないとUndo情報追加を行わない。 + (Unless OFF () it calls after, ON () executes, Undo information addition is not done.) +*/ +void +KUndoBuffer::On() +{ + FNoTouch = false; +} + +void +KUndoBuffer::Off() +{ + FNoTouch = true; +} + +status_t +KUndoBuffer::NewUndo(const char *text, int32 length, int32 offset, undo_type history, int32 cursor_pos) +{ + KUndoItem *NewUndoItem = new KUndoItem(text, length, offset, history, cursor_pos); + status_t status = NewUndoItem->InitCheck(); + if ( status != B_OK) { + delete NewUndoItem; + return status; + } + AddItem(NewUndoItem, FIndex); + FIndex++; + return status; +} + +/* +Undo情報をリストに追加する。 +(Undo information is added to the list.) +*/ +status_t +KUndoBuffer::AddUndo(const char *text, int32 length, int32 offset, undo_type history, int32 cursor_pos) +{ + if (FNoTouch) return B_OK; + + status_t status; + + // 新たな追加予約がある(FNewItem)か、現在位置(FIndex)が最後尾でないか、Undoリストが空であれば + // 新たにUndo情報を追加する。 + // (There is new additional reservation, if (the FNewItem), 現在位置 (the + // FIndex) is not the last tail or and the Undo list is the sky, Undo + // information is added anew.) + // さもなくば、最後尾Undo情報に結合すべきなら結合し、そうでなければ + // やはり新たにUndo情報を追加する。 + // (Without, if it should connect to last tail Undo information, if it + // connects and so is not Undo information is added after all anew.) + if (FNewItem || (FIndex < CountItems()) || (CountItems()==0)) { + status = NewUndo(text, length, offset, history, cursor_pos); + FNewItem = false; + } else { + KUndoItem *CurrentUndoItem; + CurrentUndoItem = ItemAt(FIndex-1); + if (CurrentUndoItem!=NULL) { + int32 c_length = CurrentUndoItem->Length; + int32 c_offset = CurrentUndoItem->Offset; + undo_type c_history = CurrentUndoItem->History; + if (c_history == history) { + switch(c_history) { + case K_INSERTED: + case K_REPLACED: + if ((c_offset + c_length) == offset) { + CurrentUndoItem->Merge(text, length); + } else { + status = NewUndo(text, length, offset, history, cursor_pos); + } + break; + case K_DELETED: + status = NewUndo(text, length, offset, history, cursor_pos); + break; + } + } else { + status = NewUndo(text, length, offset, history, cursor_pos); + } + } + } + + return B_OK; +} + +// Enterを押した、矢印キーを押した等、Undoを一区切り起きたい場合に +// MakeNewUndoItem()を呼び出す。 +// (The Enter was pushed, one you divide the Undo and the MakeNewUndoItem () +// you call when we would like to occur e.g., the arrow key was pushed.) +status_t +KUndoBuffer::MakeNewUndoItem() +{ + if (FIndex >= CountItems()) { + FNewItem = true; + return B_OK; + } + return B_ERROR; +} + +// Undo() Redo() は、FIndexをいったりきたりしながら +// 挿入位置、文字長、復元テキスト等の情報を返す。 +// (The Undo () the Redo (), the FIndex, while going back and forth, it +// returns the information of insertion position, letter length and the +// restoration text et cetera.) +status_t +KUndoBuffer::Undo(char **text, + int32 *length, + int32 *offset, + undo_type *history, + int32 *cursor_pos) +{ + KUndoItem *undoItem; + status_t status; + + if (FIndex>0) { + undoItem = ItemAt(FIndex-1); + if (undoItem!=NULL) { + *text = undoItem->RedoText; + *length = undoItem->Length; + *offset = undoItem->Offset; + *history = undoItem->History; + *cursor_pos = undoItem->CursorPos + undoItem->Length; + status = B_OK; + } else { + status = B_ERROR; + } + FIndex--; + } else { + status = B_ERROR; + } + return status; +} + +status_t +KUndoBuffer::Redo(char **text, + int32 *length, + int32 *offset, + undo_type *history, + int32 *cursor_pos, + bool *replaced) +{ + KUndoItem *undoItem; + status_t status; + + if (FIndex < CountItems()) { + undoItem = ItemAt(FIndex); + if (undoItem!=NULL) { + *text = undoItem->RedoText; + *length = undoItem->Length; + *offset = undoItem->Offset; + *history = undoItem->History; + *cursor_pos = undoItem->CursorPos; + if ((FIndex+1) < CountItems()) { + *replaced = ItemAt(FIndex+1)->History==K_REPLACED; + } else { + *replaced = false; + } + status = B_OK; + } else { + status = B_ERROR; + } + FIndex++; + } else { + status = B_ERROR; + } + return status; +} + +// Undo/Redo 情報を標準出力に書きだす。 +// (It starts writing Undo/Redo information on standard output.) +// デバッグ時のみ。 +// (Only when debugging.) +void +KUndoBuffer::PrintToStream() +{ + for(int32 i=0; iHistory) { + case K_INSERTED: + printf("INSERTED "); + break; + case K_DELETED: + printf("DELETED "); + break; + case K_REPLACED: + printf("REPLACED "); + break; + } + printf("Offset = %d ", (int)item->Offset); + printf("Length = %d ", (int)item->Length); + printf("CursorPos = %d ", (int)item->CursorPos); + printf("RedoText = '"); + for(int32 j=0;jLength;j++) { + uchar c = (uchar)item->RedoText[j]; + if (c >= 0x20) { + printf("%c", c); + } else { + printf("?"); + } + } + printf("'\n"); + } +} diff --git a/src/apps/bemail/KUndoBuffer.h b/src/apps/bemail/KUndoBuffer.h new file mode 100644 index 0000000000..28bf6e5594 --- /dev/null +++ b/src/apps/bemail/KUndoBuffer.h @@ -0,0 +1,77 @@ +#include + +enum undo_type{ + K_INSERTED, // 文字挿入時 (At the time of character insertion) + K_DELETED, // 削除時 (When deleting) + K_REPLACED // 置換、つまり削除→挿入の連続動作時 (Substitution, in other words deletion -> at the time of continuous action of insertion) +}; + +class KUndoItem +{ +public: + KUndoItem(const char *text, + int32 length, + int32 offset, + undo_type history, + int32 cursor_pos); + ~KUndoItem(); + void Merge(const char *text, int32 length); + status_t InitCheck(); + + int32 Offset; // 開始位置 (Start position) + int32 Length; // 文字長 (Letter length) + char *RedoText; // 復元すべきテキスト (The text which it should reconstruct) + undo_type History; // 操作区分 (Operation division) + int32 CursorPos; // カーソル位置 (Cursor position) + +private: + status_t Status; +}; + +class KUndoBuffer:public BList +{ +public: + KUndoBuffer(); + ~KUndoBuffer(); + + bool AddItem(KUndoItem *item, int32 index); + bool AddItem(KUndoItem *item); + void MakeEmpty(void); + KUndoItem *RemoveItem(int32 index); + KUndoItem *ItemAt(int32 index) const; + + + status_t AddUndo(const char *redo_text, + int32 length, + int32 offset, + undo_type history, + int32 cursor_pos); + status_t MakeNewUndoItem(); + status_t Undo(char **text, + int32 *length, + int32 *offset, + undo_type *history, + int32 *cursor_pos); + status_t Redo(char **text, + int32 *length, + int32 *offset, + undo_type *history, + int32 *cursor_pos, + bool *replaced); + + void PrintToStream(); + + void On(); + void Off(); + +private: + int32 FIndex; + bool FNewItem; + bool FNoTouch; + + status_t NewUndo(const char *text, + int32 length, + int32 offset, + undo_type history, + int32 cursor_pos); +}; diff --git a/src/apps/bemail/LICENSE b/src/apps/bemail/LICENSE new file mode 100644 index 0000000000..bbe4d1c9c1 --- /dev/null +++ b/src/apps/bemail/LICENSE @@ -0,0 +1,31 @@ +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. diff --git a/src/apps/bemail/Mail.cpp b/src/apps/bemail/Mail.cpp new file mode 100644 index 0000000000..93eedc5f67 --- /dev/null +++ b/src/apps/bemail/Mail.cpp @@ -0,0 +1,3968 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Mail.cpp +// +//-------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#ifndef BONE +# include +#endif + +#include "Mail.h" +#include "Header.h" +#include "Content.h" +#include "Enclosures.h" +#include "Prefs.h" +#include "Signature.h" +#include "Status.h" +#include "String.h" +#include "FindWindow.h" +#include "Utilities.h" +#include "ButtonBar.h" +#include "QueryMenu.h" +#include "FieldMsg.h" +#include "Words.h" + + +const char *kUndoStrings[] = { + MDR_DIALECT_CHOICE ("Undo","Z) 取り消し"), + MDR_DIALECT_CHOICE ("Undo Typing","Z) 取り消し(入力)"), + MDR_DIALECT_CHOICE ("Undo Cut","Z) 取り消し(切り取り)"), + MDR_DIALECT_CHOICE ("Undo Paste","Z) 取り消し(貼り付け)"), + MDR_DIALECT_CHOICE ("Undo Clear","Z) 取り消し(消去)"), + MDR_DIALECT_CHOICE ("Undo Drop","Z) 取り消し(ドロップ)") +}; + +const char *kRedoStrings[] = { + MDR_DIALECT_CHOICE ("Redo", "Z) やり直し"), + MDR_DIALECT_CHOICE ("Redo Typing", "Z) やり直し(入力)"), + MDR_DIALECT_CHOICE ("Redo Cut", "Z) やり直し(切り取り)"), + MDR_DIALECT_CHOICE ("Redo Paste", "Z) やり直し(貼り付け)"), + MDR_DIALECT_CHOICE ("Redo Clear", "Z) やり直し(消去)"), + MDR_DIALECT_CHOICE ("Redo Drop", "Z) やり直し(ドロップ)") +}; + +// Spam related globals. +static bool gShowSpamGUI = true; +static BMessenger gMessengerToSpamServer; +static const char *kSpamServerSignature = "application/x-vnd.agmsmith.AGMSBayesianSpamServer"; + +static const char *kDraftPath = "mail/draft"; +static const char *kDraftType = "text/x-vnd.Be-MailDraft"; +static const char *kMailFolder = "mail"; +static const char *kMailboxFolder = "mail/mailbox"; + +static const char *kDictDirectory = "word_dictionary"; +static const char *kIndexDirectory = "word_index"; +static const char *kWordsPath = "/boot/optional/goodies/words"; +static const char *kExact = ".exact"; +static const char *kMetaphone = ".metaphone"; + +// Text for both the main menu and the pop-up menu. +static const char *kSpamMenuItemTextArray[] = { + "Train as Spam, then Move to Trash", // M_TRAIN_SPAM_AND_DELETE + "Train as Spam", // M_TRAIN_SPAM + "Untrain this Message", // M_UNTRAIN + "Train as Genuine" // M_TRAIN_GENUINE +}; + +// global variables +bool gHelpOnly = false; +bool header_flag = false; +static bool sWrapMode = true; +bool attachAttributes_mode = true; +bool gColoredQuotes = true; +bool show_buttonbar = true; +char *gReplyPreamble; +char *signature; +int32 level = L_BEGINNER; +entry_ref open_dir; +BMessage *print_settings = NULL; +BPoint prefs_window; +BRect signature_window; +BRect mail_window; +BRect last_window; +uint32 gMailCharacterSet = B_MS_WINDOWS_CONVERSION; +bool gWarnAboutUnencodableCharacters = true; +Words *gWords[MAX_DICTIONARIES], *gExactWords[MAX_DICTIONARIES]; +int32 gUserDict; +BFile *gUserDictFile; +int32 gDictCount = 0; +bool gStartWithSpellCheckOn = false; +uint32 gDefaultChain; +int32 gUseAccountFrom; + +// static list for tracking of Windows +BList TMailWindow::sWindowList; +BLocker TMailWindow::sWindowListLock; + + +//==================================================================== + +int +main() +{ + TMailApp().Run(); + return B_NO_ERROR; +} + + +int32 +header_len(BFile *file) +{ + char *buffer; + int32 len; + int32 result = 0; + off_t size; + + if (file->ReadAttr(B_MAIL_ATTR_HEADER, B_INT32_TYPE, 0, &result, sizeof(int32)) != sizeof(int32)) + { + file->GetSize(&size); + buffer = (char *)malloc(size); + if (buffer) + { + file->Seek(0, 0); + if (file->Read(buffer, size) == size) + { + while ((len = linelen(buffer + result, size - result, true)) > 2) + result += len; + + result += len; + } + free(buffer); + file->WriteAttr(B_MAIL_ATTR_HEADER, B_INT32_TYPE, 0, &result, sizeof(int32)); + } + } + return result; +} + + +//-------------------------------------------------------------------- +// #pragma mark - + + +TMailApp::TMailApp() + : BApplication("application/x-vnd.Be-MAIL"), + fFont(*be_plain_font), + fWindowCount(0), + fPrefsWindow(NULL), + fSigWindow(NULL) +{ + // set default values + fFont.SetSize(FONT_SIZE); + signature = (char *)malloc(strlen(SIG_NONE) + 1); + strcpy(signature, SIG_NONE); + gReplyPreamble = (char *)malloc(1); + gReplyPreamble[0] = '\0'; + + mail_window.Set(0, 0, 0, 0); + signature_window.Set(6, TITLE_BAR_HEIGHT, 6 + kSigWidth, TITLE_BAR_HEIGHT + kSigHeight); + prefs_window.Set(6, TITLE_BAR_HEIGHT); + + // Find and read preferences file. + LoadSavePrefs (true /* TRUE to load them */); + + CheckForSpamFilterExistence(); + fFont.SetSpacing(B_BITMAP_SPACING); + last_window = mail_window; +} + + +TMailApp::~TMailApp() +{ +} + + +void +TMailApp::AboutRequested() +{ + (new BAlert("", + "BeMail\nBy Robert Polic\n\n" + "Enhanced by Axel Dörfler and the Dr. Zoidberg crew\n\n" + "Mail.cpp $Revision: 1.1 $\n" + "Compiled on " __DATE__ " at " __TIME__ ".", + "Close"))->Go(); +} + + +void +TMailApp::ArgvReceived(int32 argc, char **argv) +{ + BEntry entry; + BString names; + BString ccNames; + BString bccNames; + BString subject; + BString body; + BMessage enclosure(B_REFS_RECEIVED); + // a "mailto:" with no name should open an empty window + // so remember if we got a "mailto:" even if there isn't a name + // that goes along with it (this allows deskbar replicant to open + // an empty message even when BeMail is already running) + bool gotmailto = false; + + for (int32 loop = 1; loop < argc; loop++) + { + if (strcmp(argv[loop], "-h") == 0 + || strcmp(argv[loop], "--help") == 0) + { + printf(" usage: %s [ mailto:
] [ -subject \"\" ] [ ccto:
] [ bccto:
] " + "[ -body \" ] [ ...] \n", + argv[0]); + gHelpOnly = true; + be_app->PostMessage(B_QUIT_REQUESTED); + return; + } + else if (strncmp(argv[loop], "mailto:", 7) == 0) + { + if (names.Length()) + names += ", "; + char *options; + if ((options = strchr(argv[loop],'?')) != NULL) + { + names.Append(argv[loop] + 7, options - argv[loop] - 7); + if (!strncmp(++options,"subject=",8)) + subject = options + 8; + } + else + names += argv[loop] + 7; + gotmailto = true; + } + else if (strncmp(argv[loop], "ccto:", 5) == 0) + { + if (ccNames.Length()) + ccNames += ", "; + ccNames += argv[loop] + 5; + } + else if (strncmp(argv[loop], "bccto:", 6) == 0) + { + if (bccNames.Length()) + bccNames += ", "; + bccNames += argv[loop] + 6; + } + else if (strcmp(argv[loop], "-subject") == 0) + subject = argv[++loop]; + else if (strcmp(argv[loop], "-body") == 0 && argv[loop + 1]) + body = argv[++loop]; + else if (strncmp(argv[loop], "enclosure:", 10) == 0) + { + BEntry tmp(argv[loop] + 10, true); + if (tmp.InitCheck() == B_OK && tmp.Exists()) + { + entry_ref ref; + tmp.GetRef(&ref); + enclosure.AddRef("refs", &ref); + } + } + else if (entry.SetTo(argv[loop]) == B_NO_ERROR) + { + BMessage msg(B_REFS_RECEIVED); + entry_ref ref; + entry.GetRef(&ref); + msg.AddRef("refs", &ref); + RefsReceived(&msg); + } + } + + if (gotmailto || names.Length() || ccNames.Length() || bccNames.Length() || subject.Length() + || body.Length() || enclosure.HasRef("refs")) + { + TMailWindow *window = NewWindow(NULL, names.String()); + window->SetTo(names.String(), subject.String(), ccNames.String(), bccNames.String(), + &body, &enclosure); + window->Show(); + } +} + + +void +TMailApp::MessageReceived(BMessage *msg) +{ + TMailWindow *window = NULL; + entry_ref ref; + + switch (msg->what) + { + case M_NEW: + { + int32 type; + msg->FindInt32("type", &type); + switch (type) + { + case M_NEW: + window = NewWindow(); + break; + + case M_RESEND: + { + msg->FindRef("ref", &ref); + BNode file(&ref); + BString string = ""; + + if (file.InitCheck() == B_OK) + ReadAttrString(&file, B_MAIL_ATTR_TO, &string); + + window = NewWindow(&ref, string.String(), true); + break; + } + case M_FORWARD: + case M_FORWARD_WITHOUT_ATTACHMENTS: + { + TMailWindow *sourceWindow; + if (msg->FindPointer("window", (void **)&sourceWindow) < B_OK + || !sourceWindow->Lock()) + break; + + msg->FindRef("ref", &ref); + window = NewWindow(); + if (window->Lock()) { + window->Forward(&ref, sourceWindow, type == M_FORWARD); + window->Unlock(); + } + sourceWindow->Unlock(); + break; + } + + case M_REPLY: + case M_REPLY_TO_SENDER: + case M_REPLY_ALL: + case M_COPY_TO_NEW: + { + TMailWindow *sourceWindow; + if (msg->FindPointer("window", (void **)&sourceWindow) < B_OK + || !sourceWindow->Lock()) + break; + msg->FindRef("ref", &ref); + window = NewWindow(); + if (window->Lock()) { + if (type == M_COPY_TO_NEW) + window->CopyMessage(&ref, sourceWindow); + else + window->Reply(&ref, sourceWindow, type); + window->Unlock(); + } + sourceWindow->Unlock(); + break; + } + } + if (window) + window->Show(); + break; + } + + case M_PREFS: + if (fPrefsWindow) + fPrefsWindow->Activate(true); + else + { + fPrefsWindow = new TPrefsWindow(BRect(prefs_window.x, + prefs_window.y, prefs_window.x + PREF_WIDTH, + prefs_window.y + PREF_HEIGHT), + &fFont, &level, &sWrapMode, &attachAttributes_mode, + &gColoredQuotes, &gDefaultChain, &gUseAccountFrom, + &gReplyPreamble, &signature, &gMailCharacterSet, + &gWarnAboutUnencodableCharacters, + &gStartWithSpellCheckOn, &show_buttonbar); + fPrefsWindow->Show(); + fPrevBBPref = show_buttonbar; + } + break; + + case PREFS_CHANGED: + { + // Do we need to update the state of the button bars? + if (fPrevBBPref != show_buttonbar) + { + // Notify all BeMail windows + TMailWindow *window; + for (int32 i = 0; (window=(TMailWindow *)fWindowList.ItemAt(i)) != NULL; i++) + { + window->Lock(); + window->UpdateViews(); + window->Unlock(); + } + fPrevBBPref = show_buttonbar; + } + break; + } + + case M_EDIT_SIGNATURE: + if (fSigWindow) + fSigWindow->Activate(true); + else + { + fSigWindow = new TSignatureWindow(signature_window); + fSigWindow->Show(); + } + break; + + case M_FONT: + FontChange(); + break; + + case M_BEGINNER: + case M_EXPERT: + level = msg->what - M_BEGINNER; + break; + + case REFS_RECEIVED: + if (msg->HasPointer("window")) + { + msg->FindPointer("window", (void **)&window); + BMessage message(*msg); + window->PostMessage(&message, window); + } + break; + + case WINDOW_CLOSED: + switch (msg->FindInt32("kind")) + { + case MAIL_WINDOW: + { + TMailWindow *window; + if( msg->FindPointer( "window", (void **)&window ) == B_OK ) + fWindowList.RemoveItem( window ); + fWindowCount--; + break; + } + + case PREFS_WINDOW: + fPrefsWindow = NULL; + break; + + case SIG_WINDOW: + fSigWindow = NULL; + break; + } + + if (!fWindowCount && !fSigWindow && !fPrefsWindow) + be_app->PostMessage(B_QUIT_REQUESTED); + break; + + case B_REFS_RECEIVED: + RefsReceived(msg); + break; + + case B_PRINTER_CHANGED: + ClearPrintSettings(); + break; + + default: + BApplication::MessageReceived(msg); + } +} + + +bool +TMailApp::QuitRequested() +{ + if (!BApplication::QuitRequested()) + return false; + + mail_window = last_window; /* Last closed window becomes standard window size. */ + LoadSavePrefs(false /* TRUE to load them */); + return true; +} + + +void +TMailApp::ReadyToRun() +{ + // Create needed indices for META:group, META:email, MAIL:draft, + // INDEX_SIGNATURE, INDEX_STATUS on the boot volume + + BVolume volume; + BVolumeRoster().GetBootVolume(&volume); + + fs_create_index(volume.Device(), "META:group", B_STRING_TYPE, 0); + fs_create_index(volume.Device(), "META:email", B_STRING_TYPE, 0); + fs_create_index(volume.Device(), "MAIL:draft", B_INT32_TYPE, 0); + fs_create_index(volume.Device(), INDEX_SIGNATURE, B_STRING_TYPE, 0); + fs_create_index(volume.Device(), INDEX_STATUS, B_STRING_TYPE, 0); + + // Load dictionaries + BPath indexDir; + BPath dictionaryDir; + BPath dataPath; + BPath indexPath; + BDirectory directory; + BEntry entry; + + // Locate user settings directory + find_directory(B_BEOS_ETC_DIRECTORY, &indexDir, true); + dictionaryDir = indexDir; + + // Setup directory paths + indexDir.Append(kIndexDirectory); + dictionaryDir.Append(kDictDirectory); + + // Create directories if needed + directory.CreateDirectory(indexDir.Path(), NULL); + directory.CreateDirectory(dictionaryDir.Path(), NULL); + + dataPath = dictionaryDir; + dataPath.Append("words"); + + // Only Load if Words Dictionary + if (BEntry(kWordsPath).Exists() || BEntry(dataPath.Path()).Exists()) + { + // If "/boot/optional/goodies/words" exists but there is no system dictionary, copy words + if (!BEntry(dataPath.Path()).Exists() && BEntry(kWordsPath).Exists()) + { + BFile words(kWordsPath, B_READ_ONLY); + BFile copy(dataPath.Path(), B_WRITE_ONLY | B_CREATE_FILE); + char buffer[4096]; + ssize_t size; + + while ((size = words.Read( buffer, 4096)) > 0) + copy.Write(buffer, size); + BNodeInfo(©).SetType("text/plain"); + } + + // Create user dictionary if it does not exist + dataPath = dictionaryDir; + dataPath.Append("user"); + if (!BEntry(dataPath.Path()).Exists()) + { + BFile user(dataPath.Path(), B_WRITE_ONLY | B_CREATE_FILE); + BNodeInfo(&user).SetType("text/plain"); + } + + // Load dictionaries + directory.SetTo(dictionaryDir.Path()); + + BString leafName; + gUserDict = -1; + + while (gDictCount < MAX_DICTIONARIES + && directory.GetNextEntry(&entry) != B_ENTRY_NOT_FOUND) + { + dataPath.SetTo(&entry); + + // Identify the user dictionary + if (strcmp("user", dataPath.Leaf()) == 0) + { + gUserDictFile = new BFile(dataPath.Path(), B_WRITE_ONLY | B_OPEN_AT_END); + gUserDict = gDictCount; + } + + indexPath = indexDir; + leafName.SetTo(dataPath.Leaf()); + leafName.Append(kMetaphone); + indexPath.Append(leafName.String()); + gWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), true); + + indexPath = indexDir; + leafName.SetTo(dataPath.Leaf()); + leafName.Append(kExact); + indexPath.Append(leafName.String()); + gExactWords[gDictCount] = new Words(dataPath.Path(), indexPath.Path(), false); + gDictCount++; + } + } + + // Create a new window if starting up without any extra arguments. + + if (!gHelpOnly && !fWindowCount) + { + TMailWindow *window; + window = NewWindow(); + window->Show(); + } +} + + +void +TMailApp::RefsReceived(BMessage *msg) +{ + bool have_names = false; + BString names; + char type[B_FILE_NAME_LENGTH]; + int32 item = 0; + BFile file; + TMailWindow *window; + entry_ref ref; + + // + // If a tracker window opened me, get a messenger from it. + // + BMessenger messenger; + if (msg->HasMessenger("TrackerViewToken")) + msg->FindMessenger("TrackerViewToken", &messenger); + + while (msg->HasRef("refs", item)) { + msg->FindRef("refs", item++, &ref); + if ((window = FindWindow(ref)) != NULL) + window->Activate(true); + else { + file.SetTo(&ref, O_RDONLY); + if (file.InitCheck() == B_NO_ERROR) { + BNodeInfo node(&file); + node.GetType(type); + if (!strcmp(type, B_MAIL_TYPE)) { + window = NewWindow(&ref, NULL, false, &messenger); + window->Show(); + } else if(!strcmp(type, "application/x-person")) { + /* Got a People contact info file, see if it has an Email address. */ + BString name; + BString email; + attr_info info; + char *attrib; + + if (file.GetAttrInfo("META:email", &info) == B_NO_ERROR) { + attrib = (char *) malloc(info.size + 1); + file.ReadAttr("META:email", B_STRING_TYPE, 0, attrib, info.size); + attrib[info.size] = 0; // Just in case it wasn't NUL terminated. + email << attrib; + free(attrib); + + /* we got something... */ + if (email.Length() > 0) { + /* see if we can get a username as well */ + if(file.GetAttrInfo("META:name", &info) == B_NO_ERROR) { + attrib = (char *) malloc(info.size + 1); + file.ReadAttr("META:name", B_STRING_TYPE, 0, attrib, info.size); + attrib[info.size] = 0; // Just in case it wasn't NUL terminated. + name << "\"" << attrib << "\" "; + email.Prepend("<"); + email.Append(">"); + free(attrib); + } + + if (names.Length() == 0) { + names << name << email; + } else { + names << ", " << name << email; + } + have_names = true; + email.SetTo(""); + name.SetTo(""); + } + } + } + else if (!strcmp(type, kDraftType)) + { + window = NewWindow(); + + // If it's a draft message, open it + window->OpenMessage(&ref); + window->Show(); + } + } /* end of else(file.InitCheck() == B_NO_ERROR */ + } + } + + if (have_names) { + window = NewWindow(NULL, names.String()); + window->Show(); + } +} + + +TMailWindow * +TMailApp::FindWindow(const entry_ref &ref) +{ + BEntry entry(&ref); + if (entry.InitCheck() < B_OK) + return NULL; + + node_ref nodeRef; + if (entry.GetNodeRef(&nodeRef) < B_OK) + return NULL; + + BWindow *window; + int32 index = 0; + while ((window = WindowAt(index++)) != NULL) { + TMailWindow *mailWindow = dynamic_cast(window); + if (mailWindow == NULL) + continue; + + node_ref mailNodeRef; + if (mailWindow->GetMailNodeRef(mailNodeRef) == B_OK + && mailNodeRef == nodeRef) + return mailWindow; + } + + return NULL; +} + + +void +TMailApp::CheckForSpamFilterExistence() +{ + // Looks at the filter settings to see if the user is using a spam filter. + // If there is one there, set gShowSpamGUI to TRUE, otherwise to FALSE. + + int32 addonNameIndex; + const char *addonNamePntr; + BDirectory inChainDir; + BPath path; + BEntry settingsEntry; + BFile settingsFile; + BMessage settingsMessage; + + gShowSpamGUI = false; + + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) + return; + path.Append("Mail/chains/inbound"); + if (inChainDir.SetTo(path.Path()) != B_OK) + return; + + while (inChainDir.GetNextEntry (&settingsEntry, true /* traverse */) == B_OK) { + if (!settingsEntry.IsFile()) + continue; + if (settingsFile.SetTo (&settingsEntry, B_READ_ONLY) != B_OK) + continue; + if (settingsMessage.Unflatten (&settingsFile) != B_OK) + continue; + for (addonNameIndex = 0; B_OK == settingsMessage.FindString ( + "filter_addons", addonNameIndex, &addonNamePntr); + addonNameIndex++) { + if (strstr (addonNamePntr, "SpamFilter") != NULL) { + gShowSpamGUI = true; // Found it! + return; + } + } + } +} + + +void +TMailApp::ClearPrintSettings() +{ + delete print_settings; + print_settings = NULL; +} + +/* +status_t +TMailApp::LoadSettings() +{ + // write settings file + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) < B_OK) + return; + + path.Append(kWorkspacesSettingFile); +} + + +status_t +TMailApp::SaveSettings() +{ + // write settings file + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) < B_OK) + return; + + path.Append(kWorkspacesSettingFile); +} +*/ + +void +TMailApp::LoadSavePrefs(bool loadThem) +{ + // Load the preferences if loadThem is TRUE, otherwise save them. Uses a + // flattened BMessage (after a year or two of inertia with the unreliable + // binary dumps) in a file named "BeMail Settings" in the Mail folder in + // the user's settings directory. It can also read (but not write) the + // older binary dump file "Mail_data" in the top level settings directory, + // if it can't find the new settings. + + BMailSettings chainSettings; + BDirectory directory; + status_t errorCode; + const char *fieldName; + BPath filePath; + BPath mailSettingsPath; + BFile prefsFile; + BMessage settingsMsg; + bool tempBool; + float tempFloat; + int32 tempInt32; + BPoint tempPoint; + BRect tempRect; + const char *tempString; + BPath topSettingsPath; + + // Prepare the settings directories. + + if (find_directory(B_USER_SETTINGS_DIRECTORY, &topSettingsPath, + true /* create if needed */) != B_OK) + return; // No main settings directory, can't do anything. + + mailSettingsPath = topSettingsPath; + mailSettingsPath.Append("Mail"); + if (directory.SetTo(mailSettingsPath.Path()) != B_OK) { + mkdir (mailSettingsPath.Path(), 0755); + if (directory.SetTo(mailSettingsPath.Path()) != B_OK) + return; + } + directory.Unset(); // Not actually used any more. + + // Read/write the default chain settings from somewhere system dependent. + + if (loadThem) { + gDefaultChain = chainSettings.DefaultOutboundChainID(); + } else if (gDefaultChain != ~0UL) { + chainSettings.SetDefaultOutboundChainID(gDefaultChain); + chainSettings.Save(); + } + + // Open the new style settings file. + + filePath = mailSettingsPath; + filePath.Append("BeMail Settings"); + errorCode = prefsFile.SetTo (filePath.Path(), loadThem ? B_READ_ONLY : + B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); + if (!loadThem && errorCode != B_OK) + return; // Need the file when saving. + + if (loadThem && errorCode == B_OK) + errorCode = settingsMsg.Unflatten (&prefsFile); + + if (loadThem && errorCode == B_OK && settingsMsg.what != 'BeMl') + errorCode = B_BAD_VALUE; + + if (loadThem && errorCode != B_OK) { + // Unable to read the new style "BeMail Settings" file, try reading the + // old style "Mail_data" file. + + filePath = topSettingsPath; + filePath.Append("Mail_data"); + errorCode = prefsFile.SetTo (filePath.Path(), B_READ_ONLY); + if (errorCode != B_OK) + return; // Can't even find an old style file. + + prefsFile.Read(&mail_window, sizeof(BRect)); + prefsFile.Read(&level, sizeof(level)); + + font_family f_family; + font_style f_style; + float size; + prefsFile.Read(&f_family, sizeof(font_family)); + prefsFile.Read(&f_style, sizeof(font_style)); + prefsFile.Read(&size, sizeof(float)); + if (size >= 9) + fFont.SetSize(size); + + if ((strlen(f_family)) && (strlen(f_style))) + fFont.SetFamilyAndStyle(f_family, f_style); + + prefsFile.Read(&signature_window, sizeof(BRect)); + prefsFile.Read(&header_flag, sizeof(bool)); + prefsFile.Read(&sWrapMode, sizeof(bool)); + prefsFile.Read(&prefs_window, sizeof(BPoint)); + int32 len; + if (prefsFile.Read(&len, sizeof(int32)) > 0) + { + free(signature); + signature = (char *)malloc(len); + prefsFile.Read(signature, len); + } + + prefsFile.Read(&gMailCharacterSet, sizeof(int32)); + for (uint32 index = 0; true; index++) { + if (kEncodings[index].flavor == B_MAIL_NULL_CONVERSION) { + gMailCharacterSet = B_MS_WINDOWS_CONVERSION; + break; + } + if (kEncodings[index].flavor == gMailCharacterSet) + break; + } + + if (prefsFile.Read(&len, sizeof(int32)) > 0) + { + char *findString = (char *)malloc(len + 1); + prefsFile.Read(findString, len); + findString[len] = '\0'; + FindWindow::SetFindString(findString); + free(findString); + } + if (prefsFile.Read(&show_buttonbar, sizeof(bool)) <= 0) + show_buttonbar = true; + if (prefsFile.Read(&gUseAccountFrom, sizeof(int32)) <= 0 + || gUseAccountFrom < ACCOUNT_USE_DEFAULT + || gUseAccountFrom > ACCOUNT_FROM_MAIL) + gUseAccountFrom = ACCOUNT_USE_DEFAULT; + if (prefsFile.Read(&gColoredQuotes, sizeof(bool)) <= 0) + gColoredQuotes = true; + + if (prefsFile.Read(&len, sizeof(int32)) > 0) + { + free(gReplyPreamble); + gReplyPreamble = (char *)malloc(len + 1); + prefsFile.Read(gReplyPreamble, len); + gReplyPreamble[len] = '\0'; + } + prefsFile.Read(&attachAttributes_mode, sizeof(bool)); + prefsFile.Read(&gWarnAboutUnencodableCharacters, sizeof(bool)); + + return; // Finished reading old style settings. + } + + // Transfer the settings between the BMessage and our various global + // variables. For loading, if the setting isn't present, leave it at the + // default value. Note that loading and saving are intermingled here to + // make code maintenance easier (less chance of forgetting to update it if + // load and save were separate functions). + + errorCode = B_OK; // So that saving settings can record an error. + + fieldName = "MailWindowSize"; + if (loadThem) { + if (settingsMsg.FindRect(fieldName, &tempRect) == B_OK) + mail_window = tempRect; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddRect(fieldName, mail_window); + + fieldName = "ExperienceLevel"; + if (loadThem) { + if (settingsMsg.FindInt32(fieldName, &tempInt32) == B_OK) + level = tempInt32; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddInt32(fieldName, level); + + font_family fontFamily; + memset (fontFamily, 0, sizeof (fontFamily)); + font_style fontStyle; + memset (fontStyle, 0, sizeof (fontStyle)); + float fontSize = 0; + + if (!loadThem) { + fFont.GetFamilyAndStyle(&fontFamily, &fontStyle); + fontSize = fFont.Size(); + } + + fieldName = "FontFamily"; + if (loadThem) { + if (settingsMsg.FindString(fieldName, &tempString) == B_OK) + strncpy (fontFamily, tempString, B_FONT_FAMILY_LENGTH); + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddString(fieldName, fontFamily); + + fieldName = "FontStyle"; + if (loadThem) { + if (settingsMsg.FindString(fieldName, &tempString) == B_OK) + strncpy (fontStyle, tempString, B_FONT_STYLE_LENGTH); + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddString(fieldName, fontStyle); + + fieldName = "FontSize"; + if (loadThem) { + if (settingsMsg.FindFloat(fieldName, &tempFloat) == B_OK) + fontSize = tempFloat; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddFloat(fieldName, fontSize); + + if (loadThem) { + if (fontSize >= 9) + fFont.SetSize(fontSize); + if (fontFamily[0] != 0 || fontStyle[0] != 0) + fFont.SetFamilyAndStyle( + (fontFamily[0] == 0) ? NULL : fontFamily, + (fontStyle[0] == 0) ? NULL : fontStyle); + } + + fieldName = "SignatureWindowSize"; + if (loadThem) { + if (settingsMsg.FindRect(fieldName, &tempRect) == B_OK) + signature_window = tempRect; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddRect(fieldName, signature_window); + + fieldName = "ShowHeadersMode"; + if (loadThem) { + if (settingsMsg.FindBool(fieldName, &tempBool) == B_OK) + header_flag = tempBool; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddBool(fieldName, header_flag); + + fieldName = "WordWrapMode"; + if (loadThem) { + if (settingsMsg.FindBool(fieldName, &tempBool) == B_OK) + sWrapMode = tempBool; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddBool(fieldName, sWrapMode); + + fieldName = "PreferencesWindowLocation"; + if (loadThem) { + if (settingsMsg.FindPoint(fieldName, &tempPoint) == B_OK) + prefs_window = tempPoint; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddPoint(fieldName, prefs_window); + + fieldName = "SignatureText"; + if (loadThem) { + if (settingsMsg.FindString(fieldName, &tempString) == B_OK) { + free(signature); + signature = (char *) malloc(strlen(tempString) + 1); + if (signature != NULL) + strcpy (signature, tempString); + } + } else if (errorCode == B_OK && signature != NULL) + errorCode = settingsMsg.AddString(fieldName, signature); + + fieldName = "CharacterSet"; + if (loadThem) { + if (settingsMsg.FindInt32(fieldName, &tempInt32) == B_OK) + gMailCharacterSet = tempInt32; + for (uint32 index = 0; true; index++) { + if (kEncodings[index].flavor == B_MAIL_NULL_CONVERSION) { + gMailCharacterSet = B_MS_WINDOWS_CONVERSION; + break; // Don't use unknown character sets. + } + if (kEncodings[index].flavor == gMailCharacterSet) + break; + } + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddInt32(fieldName, gMailCharacterSet); + + fieldName = "FindString"; + if (loadThem) { + if (settingsMsg.FindString(fieldName, &tempString) == B_OK) + FindWindow::SetFindString(tempString); + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddString(fieldName, FindWindow::GetFindString()); + + fieldName = "ShowButtonBar"; + if (loadThem) { + if (settingsMsg.FindBool(fieldName, &tempBool) == B_OK) + show_buttonbar = tempBool; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddBool(fieldName, show_buttonbar); + + fieldName = "UseAccountFrom"; + if (loadThem) { + if (settingsMsg.FindInt32(fieldName, &tempInt32) == B_OK) + gUseAccountFrom = tempInt32; + if (gUseAccountFrom < ACCOUNT_USE_DEFAULT + || gUseAccountFrom > ACCOUNT_FROM_MAIL) + gUseAccountFrom = ACCOUNT_USE_DEFAULT; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddInt32(fieldName, gUseAccountFrom); + + fieldName = "ColoredQuotes"; + if (loadThem) { + if (settingsMsg.FindBool(fieldName, &tempBool) == B_OK) + gColoredQuotes = tempBool; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddBool(fieldName, gColoredQuotes); + + fieldName = "ReplyPreamble"; + if (loadThem) { + if (settingsMsg.FindString(fieldName, &tempString) == B_OK) { + free(gReplyPreamble); + gReplyPreamble = (char *)malloc(strlen(tempString) + 1); + if (gReplyPreamble != NULL) + strcpy (gReplyPreamble, tempString); + } + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddString(fieldName, gReplyPreamble); + + fieldName = "AttachAttributes"; + if (loadThem) { + if (settingsMsg.FindBool(fieldName, &tempBool) == B_OK) + attachAttributes_mode = tempBool; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddBool(fieldName, attachAttributes_mode); + + fieldName = "WarnAboutUnencodableCharacters"; + if (loadThem) { + if (settingsMsg.FindBool(fieldName, &tempBool) == B_OK) + gWarnAboutUnencodableCharacters = tempBool; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddBool(fieldName, gWarnAboutUnencodableCharacters); + + fieldName = "StartWithSpellCheck"; + if (loadThem) { + if (settingsMsg.FindBool(fieldName, &tempBool) == B_OK) + gStartWithSpellCheckOn = tempBool; + } else if (errorCode == B_OK) + errorCode = settingsMsg.AddBool(fieldName, gStartWithSpellCheckOn); + + // Save the settings BMessage to the settings file. + + if (!loadThem && errorCode == B_OK) { + settingsMsg.what = 'BeMl'; + errorCode = settingsMsg.Flatten (&prefsFile); + } +} + + +void +TMailApp::FontChange() +{ + int32 index = 0; + BMessage msg; + BWindow *window; + + msg.what = CHANGE_FONT; + msg.AddPointer("font", &fFont); + + for (;;) { + window = WindowAt(index++); + if (!window) + break; + + window->PostMessage(&msg); + } +} + + +TMailWindow * +TMailApp::NewWindow(const entry_ref *ref, const char *to, bool resend, + BMessenger *trackerMessenger) +{ + BScreen screen(B_MAIN_SCREEN_ID); + BRect screen_frame = screen.Frame(); + + BRect r; + if ((mail_window.Width() > 1) && (mail_window.Height() > 1)) + r = mail_window; + else + r.Set(6, TITLE_BAR_HEIGHT, 6 + WIND_WIDTH, TITLE_BAR_HEIGHT + WIND_HEIGHT); + + r.OffsetBy(fWindowCount * 20, fWindowCount * 20); + + if ((r.left - 6) < screen_frame.left) + r.OffsetTo(screen_frame.left + 8, r.top); + + if ((r.left + 20) > screen_frame.right) + r.OffsetTo(6, r.top); + + if ((r.top - 26) < screen_frame.top) + r.OffsetTo(r.left, screen_frame.top + 26); + + if ((r.top + 20) > screen_frame.bottom) + r.OffsetTo(r.left, TITLE_BAR_HEIGHT); + + if (r.Width() < WIND_WIDTH) + r.right = r.left + WIND_WIDTH; + + fWindowCount++; + + BString title; + BFile file; + if (!resend && ref && file.SetTo(ref, O_RDONLY) == B_NO_ERROR) { + BString name; + if (ReadAttrString(&file, B_MAIL_ATTR_NAME, &name) == B_NO_ERROR) { + title << name; + BString subject; + if (ReadAttrString(&file, B_MAIL_ATTR_SUBJECT, &subject) == B_NO_ERROR) + title << " -> " << subject; + } + } + if (title == "") + title = "BeMail"; + + TMailWindow *window = new TMailWindow(r, title.String(), ref, to, &fFont, resend, + trackerMessenger); + fWindowList.AddItem(window); + + return window; +} + + +//==================================================================== +// #pragma mark - + + +TMailWindow::TMailWindow(BRect rect, const char *title, const entry_ref *ref, const char *to, + const BFont *font, bool resending, BMessenger *messenger) + : BWindow(rect, title, B_DOCUMENT_WINDOW, 0), + fFieldState(0), + fPanel(NULL), + fSendButton(NULL), + fSaveButton(NULL), + fPrintButton(NULL), + fSigButton(NULL), + fZoom(rect), + fEnclosuresView(NULL), + fPrevTrackerPositionSaved(false), + fNextTrackerPositionSaved(false), + fSigAdded(false), + fReplying(false), + fResending(resending), + fSent(false), + fDraft(false), + fChanged(false), + fStartingText(NULL), + fOriginatingWindow(NULL) +{ + if (messenger != NULL) + fTrackerMessenger = *messenger; + + char str[256]; + char status[272]; + uint32 message; + float height; + BMenu *menu; + BMenu *subMenu; + BMenuBar *menu_bar; + BMenuItem *item; + BMessage *msg; + attr_info info; + BFile file(ref, B_READ_ONLY); + + if (ref) { + fRef = new entry_ref(*ref); + fMail = new BEmailMessage(fRef); + fIncoming = true; + } else { + fRef = NULL; + fMail = NULL; + fIncoming = false; + } + + BRect r(0, 0, RIGHT_BOUNDARY, 15); + + // Create real menu bar + fMenuBar = menu_bar = new BMenuBar(r, ""); + + // + // File Menu + // + menu = new BMenu(MDR_DIALECT_CHOICE ("File","F) ファイル")); + + msg = new BMessage(M_NEW); + msg->AddInt32("type", M_NEW); + menu->AddItem(item = new BMenuItem(MDR_DIALECT_CHOICE ( + "New Mail Message", "N) 新規メッセージ作成"), msg, 'N')); + item->SetTarget(be_app); + + QueryMenu *queryMenu; + queryMenu = new QueryMenu(MDR_DIALECT_CHOICE ("Open Draft", "O) ドラフトを開く"), false); + queryMenu->SetTargetForItems(be_app); + + queryMenu->SetPredicate("MAIL:draft==1"); + menu->AddItem(queryMenu); + + menu->AddSeparatorItem(); + + if (!resending && fIncoming) { + subMenu = new BMenu(MDR_DIALECT_CHOICE ("Close","C) 閉じる")); + if (file.GetAttrInfo(B_MAIL_ATTR_STATUS, &info) == B_NO_ERROR) + file.ReadAttr(B_MAIL_ATTR_STATUS, B_STRING_TYPE, 0, str, info.size); + else + str[0] = 0; + + //if( (strcmp(str, "Pending")==0)||(strcmp(str, "Sent")==0) ) + // canResend = true; + + if (!strcmp(str, "New")) { + subMenu->AddItem(item = new BMenuItem( + MDR_DIALECT_CHOICE ("Leave as 'New'", "N) 新規のままにする"), + new BMessage(M_CLOSE_SAME), 'W', B_SHIFT_KEY)); + subMenu->AddItem(item = new BMenuItem( + MDR_DIALECT_CHOICE ("Set to 'Read'", "R) 開封済に設定"), + new BMessage(M_CLOSE_READ), 'W')); + message = M_CLOSE_READ; + } else { + if (strlen(str)) + sprintf(status, MDR_DIALECT_CHOICE ("Leave as '%s'","W) 属性を<%s>にする"), str); + else + sprintf(status, MDR_DIALECT_CHOICE ("Leave same","W) 属性はそのまま")); + subMenu->AddItem(item = new BMenuItem(status, + new BMessage(M_CLOSE_SAME), 'W')); + message = M_CLOSE_SAME; + AddShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(M_CLOSE_SAME)); + } + + subMenu->AddItem(new BMenuItem( + MDR_DIALECT_CHOICE ("Set to 'Saved'", "S) 属性をに設定"), + new BMessage(M_CLOSE_SAVED), 'W', B_CONTROL_KEY)); + subMenu->AddItem(new BMenuItem(new TMenu( + MDR_DIALECT_CHOICE ("Set to", "X) 他の属性に変更")B_UTF8_ELLIPSIS, + INDEX_STATUS, M_STATUS, false, false), new BMessage(M_CLOSE_CUSTOM))); + menu->AddItem(subMenu); + + subMenu->AddItem(new BMenuItem( + MDR_DIALECT_CHOICE ("Move to Trash", "T) 削除"), + new BMessage(M_DELETE), 'T', B_CONTROL_KEY)); + AddShortcut('T', B_SHIFT_KEY | B_COMMAND_KEY, new BMessage(M_DELETE_NEXT)); + } + else + { + menu->AddItem(fSendLater = new BMenuItem( + MDR_DIALECT_CHOICE ("Save as Draft", "S)ドラフトとして保存"), + new BMessage(M_SAVE_AS_DRAFT), 'S')); + menu->AddItem(new BMenuItem( + MDR_DIALECT_CHOICE ("Close", "W) 閉じる"), + new BMessage(B_CLOSE_REQUESTED), 'W')); + } + + menu->AddSeparatorItem(); + menu->AddItem(fPrint = new BMenuItem( + MDR_DIALECT_CHOICE ("Page Setup", "G) ページ設定") B_UTF8_ELLIPSIS, + new BMessage(M_PRINT_SETUP))); + menu->AddItem(fPrint = new BMenuItem( + MDR_DIALECT_CHOICE ("Print", "P) 印刷") B_UTF8_ELLIPSIS, + new BMessage(M_PRINT), 'P')); + menu->AddSeparatorItem(); + menu->AddItem(item = new BMenuItem( + MDR_DIALECT_CHOICE ("About BeMail", "A) BeMailについて") B_UTF8_ELLIPSIS, + new BMessage(B_ABOUT_REQUESTED))); + item->SetTarget(be_app); + menu->AddSeparatorItem(); + menu->AddItem(item = new BMenuItem( + MDR_DIALECT_CHOICE ("Quit", "Q) 終了"), + new BMessage(B_QUIT_REQUESTED), 'Q')); + item->SetTarget(be_app); + menu_bar->AddItem(menu); + + // + // Edit Menu + // + menu = new BMenu(MDR_DIALECT_CHOICE ("Edit","E) 編集")); + menu->AddItem(fUndo = new BMenuItem(MDR_DIALECT_CHOICE ("Undo","Z) 元に戻す"), new BMessage(B_UNDO), 'Z', 0)); + fUndo->SetTarget(NULL, this); + menu->AddItem(fRedo = new BMenuItem(MDR_DIALECT_CHOICE ("Redo","Z) やり直し"), new BMessage(M_REDO), 'Z', B_SHIFT_KEY)); + fRedo->SetTarget(NULL, this); + menu->AddSeparatorItem(); + menu->AddItem(fCut = new BMenuItem(MDR_DIALECT_CHOICE ("Cut","X) 切り取り"), new BMessage(B_CUT), 'X')); + fCut->SetTarget(NULL, this); + menu->AddItem(fCopy = new BMenuItem(MDR_DIALECT_CHOICE ("Copy","C) コピー"), new BMessage(B_COPY), 'C')); + fCopy->SetTarget(NULL, this); + menu->AddItem(fPaste = new BMenuItem(MDR_DIALECT_CHOICE ("Paste","V) 貼り付け"), new BMessage(B_PASTE), 'V')); + fPaste->SetTarget(NULL, this); + menu->AddItem(item = new BMenuItem(MDR_DIALECT_CHOICE ("Select All", "A) 全文選択"), new BMessage(M_SELECT), 'A')); + menu->AddSeparatorItem(); + item->SetTarget(NULL, this); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Find", "F) 検索") B_UTF8_ELLIPSIS, new BMessage(M_FIND), 'F')); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Find Again", "G) 次を検索"), new BMessage(M_FIND_AGAIN), 'G')); + if (!fIncoming) + { + menu->AddSeparatorItem(); + menu->AddItem(fQuote =new BMenuItem( + MDR_DIALECT_CHOICE ("Quote","Q) 引用符をつける"), + new BMessage(M_QUOTE), B_RIGHT_ARROW)); + menu->AddItem(fRemoveQuote = new BMenuItem( + MDR_DIALECT_CHOICE ("Remove Quote","R) 引用符を削除"), + new BMessage(M_REMOVE_QUOTE), B_LEFT_ARROW)); + fSignature = new TMenu( + MDR_DIALECT_CHOICE ("Add Signature", "D) 署名を追加"), + INDEX_SIGNATURE, M_SIGNATURE); + menu->AddItem(new BMenuItem(fSignature)); + fSpelling = new BMenuItem( + MDR_DIALECT_CHOICE ("Check Spelling","H) スペルチェック"), + new BMessage( M_CHECK_SPELLING ), ';' ); + menu->AddItem(fSpelling); + if (gStartWithSpellCheckOn) + PostMessage (M_CHECK_SPELLING); + } + menu->AddSeparatorItem(); + menu->AddItem(item = new BMenuItem( + MDR_DIALECT_CHOICE ("Preferences","P) BeMailの設定")B_UTF8_ELLIPSIS, + new BMessage(M_PREFS))); + item->SetTarget(be_app); + menu->AddItem(item = new BMenuItem( + MDR_DIALECT_CHOICE ("Signatures","S) 署名の編集") B_UTF8_ELLIPSIS, + new BMessage(M_EDIT_SIGNATURE))); + item->SetTarget(be_app); + menu_bar->AddItem(menu); + + // + // Message Menu + // + menu = new BMenu(MDR_DIALECT_CHOICE ("Message", "M) メッセージ")); + + if (!resending && fIncoming) { + BMenuItem *menuItem; + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply","R) 返信"), new BMessage(M_REPLY),'R')); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to Sender","S) 送信者に返信"), new BMessage(M_REPLY_TO_SENDER),'R',B_OPTION_KEY)); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to All","P) 全員に返信"), new BMessage(M_REPLY_ALL), 'R', B_SHIFT_KEY)); + + menu->AddSeparatorItem(); + + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Forward","J) 転送"), new BMessage(M_FORWARD), 'J')); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Forward without Attachments","The opposite: F) 添付ファイルを含めて転送"), new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); + menu->AddItem(menuItem = new BMenuItem(MDR_DIALECT_CHOICE ("Resend"," 再送信"), new BMessage(M_RESEND))); + menu->AddItem(menuItem = new BMenuItem(MDR_DIALECT_CHOICE ("Copy to New","D) 新規メッセージへコピー"), new BMessage(M_COPY_TO_NEW), 'D')); + + fDeleteNext = new BMenuItem(MDR_DIALECT_CHOICE ("Move to Trash","T) 削除"), new BMessage(M_DELETE_NEXT), 'T'); + menu->AddItem(fDeleteNext); + menu->AddSeparatorItem(); + + if (gShowSpamGUI) { + for (int i = 0; i < 4; i++) { + int messageCode = M_TRAIN_SPAM_AND_DELETE + i; + menu->AddItem(new BMenuItem(kSpamMenuItemTextArray[i], new BMessage(messageCode), + (messageCode == M_TRAIN_SPAM || messageCode == M_TRAIN_GENUINE) ? 'K' : 0, + (messageCode == M_TRAIN_GENUINE) ? B_SHIFT_KEY : 0)); + } + menu->AddSeparatorItem(); + } + + fPrevMsg = new BMenuItem(MDR_DIALECT_CHOICE ("Previous Message","B) 前のメッセージ"), new BMessage(M_PREVMSG), + B_UP_ARROW); + menu->AddItem(fPrevMsg); + fNextMsg = new BMenuItem(MDR_DIALECT_CHOICE ("Next Message","N) 次のメッセージ"), new BMessage(M_NEXTMSG), + B_DOWN_ARROW); + menu->AddItem(fNextMsg); + menu->AddSeparatorItem(); + menu->AddItem(fHeader = new BMenuItem(MDR_DIALECT_CHOICE ("Show Header","H) ヘッダーを表示"), new BMessage(M_HEADER), 'H')); + if (header_flag) + fHeader->SetMarked(true); + menu->AddItem(fRaw = new BMenuItem(MDR_DIALECT_CHOICE ("Show Raw Message"," メッセージを生で表示"), new BMessage(M_RAW))); + + fSaveAddrMenu = subMenu = new BMenu(MDR_DIALECT_CHOICE ("Save Address", " アドレスを保存")); + + // create the list of addresses + + BList addressList; + get_address_list(addressList, fMail->To(), extract_address); + get_address_list(addressList, fMail->CC(), extract_address); + get_address_list(addressList, fMail->From(), extract_address); + get_address_list(addressList, fMail->ReplyTo(), extract_address); + + for (int32 i = addressList.CountItems(); i-- > 0;) { + char *address = (char *)addressList.RemoveItem(0L); + + // insert the new address in alphabetical order + int32 index = 0; + while ((item = subMenu->ItemAt(index)) != NULL) { + if (!strcmp(address, item->Label())) { + // item already in list + goto skip; + } + + if (strcmp(address, item->Label()) < 0) + break; + + index++; + } + + msg = new BMessage(M_SAVE); + msg->AddString("address", address); + subMenu->AddItem(new BMenuItem(address, msg), index); + + skip: + free(address); + } + + menu->AddItem(subMenu); + } + else { + menu->AddItem(fSendNow = new BMenuItem( + MDR_DIALECT_CHOICE ("Send Message", "M) メッセージを送信"), + new BMessage(M_SEND_NOW), 'M')); + if (!fResending) + { + // We want to make alt-shift-M work to send mail as well as just alt-M + // Gross hack follows... hey, don't look at me like that... it works. + // Create a hidden menu bar with a single "Send Now" item linked to alt-shift-M + BMenuBar *fudge_bar; + fudge_bar = new BMenuBar(r, "Fudge Bar"); + fudge_bar->Hide(); + fudge_bar->AddItem(new BMenuItem("Fudge Send", new BMessage(M_SEND_NOW), 'M', B_SHIFT_KEY) ); + AddChild(fudge_bar); + } + } + menu_bar->AddItem(menu); + + // + // Enclosures Menu + // + if (!fIncoming) + { + menu = new BMenu(MDR_DIALECT_CHOICE ("Enclosures","N) 添付ファイル")); + menu->AddItem(fAdd = new BMenuItem(MDR_DIALECT_CHOICE ("Add","E) 追加")B_UTF8_ELLIPSIS, new BMessage(M_ADD), 'E')); + menu->AddItem(fRemove = new BMenuItem(MDR_DIALECT_CHOICE ("Remove","T) 削除"), new BMessage(M_REMOVE), 'T')); + menu_bar->AddItem(menu); + } + + Lock(); + AddChild(menu_bar); + height = menu_bar->Bounds().bottom + 1; + Unlock(); + + // + // Button Bar + // + float bbwidth = 0, bbheight = 0; + + if (show_buttonbar) + { + BuildButtonBar(); + fButtonBar->ShowLabels(show_buttonbar & 1); + fButtonBar->Arrange(/* True for all buttons same size, false to just fit */ + MDR_DIALECT_CHOICE (true, true)); + fButtonBar->GetPreferredSize(&bbwidth, &bbheight); + fButtonBar->ResizeTo(Bounds().right+3, bbheight+1); + fButtonBar->MoveTo(-1, height-1); + fButtonBar->Show(); + } + else + fButtonBar = NULL; + + r.top = r.bottom = height + bbheight + 1; + fHeaderView = new THeaderView (r, rect, fIncoming, fMail, resending, + (resending || !fIncoming) + ? gMailCharacterSet // Use preferences setting for composing mail. + : B_MAIL_NULL_CONVERSION); // Default is automatic selection for reading mail. + + r = Frame(); + r.OffsetTo(0, 0); + r.top = fHeaderView->Frame().bottom - 1; + fContentView = new TContentView(r, fIncoming, fMail, const_cast(font)); + // TContentView needs to be properly const, for now cast away constness + + Lock(); + AddChild(fHeaderView); + if (fEnclosuresView) + AddChild(fEnclosuresView); + AddChild(fContentView); + Unlock(); + + if (to) + { + Lock(); + fHeaderView->fTo->SetText(to); + Unlock(); + } + + SetSizeLimits(WIND_WIDTH, RIGHT_BOUNDARY, + fHeaderView->Bounds().Height() + ENCLOSURES_HEIGHT + height + 60, + RIGHT_BOUNDARY); + + AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW)); + + // + // If auto-signature, add signature to the text here. + // + + if (!fIncoming && strcmp(signature, SIG_NONE) != 0) + { + if (strcmp(signature, SIG_RANDOM) == 0) + PostMessage(M_RANDOM_SIG); + else + { + // + // Create a query to find this signature + // + BVolume volume; + BVolumeRoster().GetBootVolume(&volume); + + BQuery query; + query.SetVolume(&volume); + query.PushAttr(INDEX_SIGNATURE); + query.PushString(signature); + query.PushOp(B_EQ); + query.Fetch(); + + // + // If we find the named query, add it to the text. + // + BEntry entry; + if (query.GetNextEntry(&entry) == B_NO_ERROR) + { + off_t size; + BFile file; + file.SetTo(&entry, O_RDWR); + if (file.InitCheck() == B_NO_ERROR) + { + file.GetSize(&size); + char *str = (char *)malloc(size); + size = file.Read(str, size); + + fContentView->fTextView->Insert(str, size); + fContentView->fTextView->GoToLine(0); + fContentView->fTextView->ScrollToSelection(); + + fStartingText = (char *)malloc(size = strlen(fContentView->fTextView->Text()) + 1); + if (fStartingText != NULL) + strcpy(fStartingText, fContentView->fTextView->Text()); + } + } + else { + char tempString [2048]; + query.GetPredicate (tempString, sizeof (tempString)); + printf ("Query failed, was looking for: %s\n", tempString); + } + } + } + + if (fRef) + SetTitleForMessage(); +} + + +void +TMailWindow::BuildButtonBar() +{ + ButtonBar *bbar; + + bbar = new ButtonBar(BRect(0, 0, 100, 100), "ButtonBar", 2, 3, 0, 1, 10, 2); + bbar->AddButton(MDR_DIALECT_CHOICE ("New","新規"), 28, new BMessage(M_NEW)); + bbar->AddDivider(5); + + if (fResending) + { + fSendButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Send","送信"), 8, new BMessage(M_SEND_NOW)); + bbar->AddDivider(5); + } + else if (!fIncoming) + { + fSendButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Send","送信"), 8, new BMessage(M_SEND_NOW)); + fSendButton->SetEnabled(false); + fSigButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Signature","署名"), 4, new BMessage(M_SIG_MENU)); + fSigButton->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); + fSaveButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Save","保存"), 44, new BMessage(M_SAVE_AS_DRAFT)); + fSaveButton->SetEnabled(false); + bbar->AddDivider(5); + fPrintButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Print","印刷"), 16, new BMessage(M_PRINT)); + fPrintButton->SetEnabled(false); + bbar->AddButton(MDR_DIALECT_CHOICE ("Trash","削除"), 0, new BMessage(M_DELETE)); + } + else + { + BmapButton *button = bbar->AddButton(MDR_DIALECT_CHOICE ("Reply","返信"), 12, new BMessage(M_REPLY)); + button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); + button = bbar->AddButton(MDR_DIALECT_CHOICE ("Forward","転送"), 40, new BMessage(M_FORWARD)); + button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); + fPrintButton = bbar->AddButton(MDR_DIALECT_CHOICE ("Print","印刷"), 16, new BMessage(M_PRINT)); + bbar->AddButton(MDR_DIALECT_CHOICE ("Trash","削除"), 0, new BMessage(M_DELETE_NEXT)); + if (gShowSpamGUI) { + button = bbar->AddButton("Spam", 48, new BMessage(M_SPAM_BUTTON)); + button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); + } + bbar->AddDivider(5); + bbar->AddButton(MDR_DIALECT_CHOICE ("Next","次へ"), 24, new BMessage(M_NEXTMSG)); + bbar->AddButton(MDR_DIALECT_CHOICE ("Previous","前へ"), 20, new BMessage(M_PREVMSG)); + } + bbar->AddButton(MDR_DIALECT_CHOICE ("Inbox","受信箱"), 36, new BMessage(M_OPEN_MAIL_BOX)); + bbar->AddButton(MDR_DIALECT_CHOICE ("Mail","メール"), 32, new BMessage(M_OPEN_MAIL_FOLDER)); + bbar->AddDivider(5); + + bbar->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + bbar->Hide(); + AddChild(bbar); + fButtonBar = bbar; +} + + +void TMailWindow::UpdateViews( void ) +{ + float bbwidth = 0, bbheight = 0; + float nextY = fMenuBar->Frame().bottom+1; + + // Show/Hide Button Bar + if (show_buttonbar) + { + // Create the Button Bar if needed + if (!fButtonBar) + BuildButtonBar(); + fButtonBar->ShowLabels(show_buttonbar & 1); + fButtonBar->Arrange(/* True for all buttons same size, false to just fit */ + MDR_DIALECT_CHOICE (true, true)); + fButtonBar->GetPreferredSize( &bbwidth, &bbheight); + fButtonBar->ResizeTo(Bounds().right+3, bbheight+1); + fButtonBar->MoveTo(-1, nextY-1); + nextY += bbheight + 1; + if (fButtonBar->IsHidden()) + fButtonBar->Show(); + else + fButtonBar->Invalidate(); + } + else if (fButtonBar) + fButtonBar->Hide(); + + // Arange other views to match + fHeaderView->MoveTo(0, nextY); + nextY = fHeaderView->Frame().bottom; + if (fEnclosuresView) + { + fEnclosuresView->MoveTo(0, nextY); + nextY = fEnclosuresView->Frame().bottom+1; + } + BRect bounds(Bounds()); + fContentView->MoveTo(0, nextY-1); + fContentView->ResizeTo(bounds.right-bounds.left, bounds.bottom-nextY+1); +} + + +TMailWindow::~TMailWindow() +{ + delete fMail; + last_window = Frame(); + delete fPanel; + delete fOriginatingWindow; + + BAutolock locker(sWindowListLock); + sWindowList.RemoveItem(this); +} + + +status_t +TMailWindow::GetMailNodeRef(node_ref &nodeRef) const +{ + if (fRef == NULL) + return B_ERROR; + + BNode node(fRef); + return node.GetNodeRef(&nodeRef); +} + + +bool +TMailWindow::GetTrackerWindowFile(entry_ref *ref, bool next) const +{ + // Position was already saved + if (next && fNextTrackerPositionSaved) + { + *ref = fNextRef; + return true; + } + if (!next && fPrevTrackerPositionSaved) + { + *ref = fPrevRef; + return true; + } + + if (!fTrackerMessenger.IsValid()) + return false; + + // + // Ask the tracker what the next/prev file in the window is. + // Continue asking for the next reference until a valid + // email file is found (ignoring other types). + // + entry_ref nextRef = *ref; + bool foundRef = false; + while (!foundRef) + { + BMessage request(B_GET_PROPERTY); + BMessage spc; + if (next) + spc.what = 'snxt'; + else + spc.what = 'sprv'; + + spc.AddString("property", "Entry"); + spc.AddRef("data", &nextRef); + + request.AddSpecifier(&spc); + BMessage reply; + if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) + return false; + + if (reply.FindRef("result", &nextRef) != B_OK) + return false; + + char fileType[256]; + BNode node(&nextRef); + if (node.InitCheck() != B_OK) + return false; + + if (BNodeInfo(&node).GetType(fileType) != B_OK) + return false; + + if (strcasecmp(fileType,"text/x-email") == 0) + foundRef = true; + } + + *ref = nextRef; + return foundRef; +} + + +void +TMailWindow::SaveTrackerPosition(entry_ref *ref) +{ + // if only one of them is saved, we're not going to do it again + if (fNextTrackerPositionSaved || fPrevTrackerPositionSaved) + return; + + fNextRef = fPrevRef = *ref; + + fNextTrackerPositionSaved = GetTrackerWindowFile(&fNextRef, true); + fPrevTrackerPositionSaved = GetTrackerWindowFile(&fPrevRef, false); +} + + +void +TMailWindow::SetOriginatingWindow(BWindow *window) +{ + delete fOriginatingWindow; + fOriginatingWindow = new BMessenger(window); +} + + +void +TMailWindow::SetTrackerSelectionToCurrent() +{ + BMessage setSelection(B_SET_PROPERTY); + setSelection.AddSpecifier("Selection"); + setSelection.AddRef("data", fRef); + + fTrackerMessenger.SendMessage(&setSelection); +} + + +void +TMailWindow::SetCurrentMessageRead() +{ + BNode node(fRef); + if (node.InitCheck() == B_NO_ERROR) + { + BString status; + if (ReadAttrString(&node, B_MAIL_ATTR_STATUS, &status) == B_NO_ERROR + && !status.ICompare("New")) + { + node.RemoveAttr(B_MAIL_ATTR_STATUS); + WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Read"); + } + } +} + + +void +TMailWindow::FrameResized(float width, float height) +{ + fContentView->FrameResized(width, height); +} + + +void +TMailWindow::MenusBeginning() +{ + bool enable; + int32 finish = 0; + int32 start = 0; + BTextView *textView; + + if (!fIncoming) + { + enable = strlen(fHeaderView->fTo->Text()) || + strlen(fHeaderView->fBcc->Text()); + fSendNow->SetEnabled(enable); + fSendLater->SetEnabled(enable); + + be_clipboard->Lock(); + fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", B_MIME_TYPE) && + ((fEnclosuresView == NULL) || !fEnclosuresView->fList->IsFocus())); + be_clipboard->Unlock(); + + fQuote->SetEnabled(false); + fRemoveQuote->SetEnabled(false); + + fAdd->SetEnabled(true); + fRemove->SetEnabled((fEnclosuresView != NULL) && + (fEnclosuresView->fList->CurrentSelection() >= 0)); + } + else + { + if (fResending) + { + enable = strlen(fHeaderView->fTo->Text()); + fSendNow->SetEnabled(enable); + // fSendLater->SetEnabled(enable); + + if (fHeaderView->fTo->HasFocus()) + { + textView = fHeaderView->fTo->TextView(); + textView->GetSelection(&start, &finish); + + fCut->SetEnabled(start != finish); + be_clipboard->Lock(); + fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", B_MIME_TYPE)); + be_clipboard->Unlock(); + } + else + { + fCut->SetEnabled(false); + fPaste->SetEnabled(false); + } + } + else + { + fCut->SetEnabled(false); + fPaste->SetEnabled(false); + + if (!fTrackerMessenger.IsValid()) { + fNextMsg->SetEnabled(false); + fPrevMsg->SetEnabled(false); + } + } + } + + fPrint->SetEnabled(fContentView->fTextView->TextLength()); + + textView = dynamic_cast(CurrentFocus()); + if ((NULL != textView) && (dynamic_cast(textView->Parent()) != NULL)) + { + // one of To:, Subject:, Account:, Cc:, Bcc: + textView->GetSelection(&start, &finish); + } + else if (fContentView->fTextView->IsFocus()) + { + fContentView->fTextView->GetSelection(&start, &finish); + if (!fIncoming) + { + fQuote->SetEnabled(true); + fRemoveQuote->SetEnabled(true); + } + } + + fCopy->SetEnabled(start != finish); + if (!fIncoming) + fCut->SetEnabled(start != finish); + + // Undo stuff + bool isRedo = false; + undo_state undoState = B_UNDO_UNAVAILABLE; + + BTextView *focusTextView = dynamic_cast(CurrentFocus()); + if (focusTextView != NULL) + undoState = focusTextView->UndoState(&isRedo); + +// fUndo->SetLabel((isRedo) ? kRedoStrings[undoState] : kUndoStrings[undoState]); + fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE); +} + + +void +TMailWindow::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case FIELD_CHANGED: + { + int32 prevState = fFieldState, fieldMask = msg->FindInt32("bitmask"); + void *source; + + if (msg->FindPointer("source", &source) == B_OK) + { + int32 length; + + if (fieldMask == FIELD_BODY) + length = ((TTextView *)source)->TextLength(); + else + length = ((BComboBox *)source)->TextView()->TextLength(); + + if (length) + fFieldState |= fieldMask; + else + fFieldState &= ~fieldMask; + } + + // Has anything changed? + if (prevState != fFieldState || !fChanged) + { + // Change Buttons to reflect this + if (fSaveButton) + fSaveButton->SetEnabled(fFieldState); + if (fPrintButton) + fPrintButton->SetEnabled(fFieldState); + if (fSendButton) + fSendButton->SetEnabled((fFieldState & FIELD_TO) || (fFieldState & FIELD_BCC)); + } + fChanged = true; + + // Update title bar if "subject" has changed + if (!fIncoming && fieldMask & FIELD_SUBJECT) + { + // If no subject, set to "BeMail" + if (!fHeaderView->fSubject->TextView()->TextLength()) + SetTitle("BeMail"); + else + SetTitle(fHeaderView->fSubject->Text()); + } + break; + } + case LIST_INVOKED: + PostMessage(msg, fEnclosuresView); + break; + + case CHANGE_FONT: + PostMessage(msg, fContentView); + break; + + case M_NEW: + { + BMessage message(M_NEW); + message.AddInt32("type", msg->what); + be_app->PostMessage(&message); + break; + } + + case M_SPAM_BUTTON: + { + uint32 buttons; + if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK + && buttons == B_SECONDARY_MOUSE_BUTTON) + { + BPopUpMenu menu("Spam Actions", false, false); + for (int i = 0; i < 4; i++) + menu.AddItem(new BMenuItem(kSpamMenuItemTextArray[i], new BMessage(M_TRAIN_SPAM_AND_DELETE + i))); + + BPoint where; + msg->FindPoint("where", &where); + BMenuItem *item; + if ((item = menu.Go(where, false, false)) != NULL) + PostMessage(item->Message()); + break; + } else // Default action for left clicking on the spam button. + PostMessage (new BMessage (M_TRAIN_SPAM_AND_DELETE)); + break; + } + + case M_TRAIN_SPAM_AND_DELETE: + PostMessage (M_DELETE_NEXT); + case M_TRAIN_SPAM: + TrainMessageAs ("Spam"); + break; + + case M_UNTRAIN: + TrainMessageAs ("Uncertain"); + break; + + case M_TRAIN_GENUINE: + TrainMessageAs ("Genuine"); + break; + + case M_REPLY: + { + uint32 buttons; + if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK + && buttons == B_SECONDARY_MOUSE_BUTTON) + { + BPopUpMenu menu("Reply To", false, false); + menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply","R) 返信"),new BMessage(M_REPLY))); + menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to Sender","S) 送信者に返信"),new BMessage(M_REPLY_TO_SENDER))); + menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Reply to All","P) 全員に返信"),new BMessage(M_REPLY_ALL))); + + BPoint where; + msg->FindPoint("where", &where); + + BMenuItem *item; + if ((item = menu.Go(where, false, false)) != NULL) + { + item->SetTarget(this); + PostMessage(item->Message()); + } + break; + } + } + case M_FORWARD: + { + uint32 buttons; + if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK + && buttons == B_SECONDARY_MOUSE_BUTTON) { + BPopUpMenu menu("Forward", false, false); + menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Forward", "J) 転送"), + new BMessage(M_FORWARD))); + menu.AddItem(new BMenuItem(MDR_DIALECT_CHOICE("Forward without Attachments", + "The opposite: F) 添付ファイルを含む転送"), + new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); + + BPoint where; + msg->FindPoint("where", &where); + + BMenuItem *item; + if ((item = menu.Go(where, false, false)) != NULL) { + item->SetTarget(this); + PostMessage(item->Message()); + } + break; + } + } + + // Fall Through + case M_REPLY_ALL: + case M_REPLY_TO_SENDER: + case M_FORWARD_WITHOUT_ATTACHMENTS: + case M_RESEND: + case M_COPY_TO_NEW: + { + BMessage message(M_NEW); + message.AddRef("ref", fRef); + message.AddPointer("window", this); + message.AddInt32("type", msg->what); + be_app->PostMessage(&message); + break; + } + case M_DELETE: + case M_DELETE_PREV: + case M_DELETE_NEXT: + { + if (level == L_BEGINNER) + { + beep(); + if (!(new BAlert("", MDR_DIALECT_CHOICE ( + "Are you sure you want to move this message to the trash?", + "このメッセージを削除してもよろしいですか?"), + MDR_DIALECT_CHOICE ("Cancel","中止"), + MDR_DIALECT_CHOICE ("Trash","削除"), + NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, + B_WARNING_ALERT))->Go()) + break; + } + + if (msg->what == M_DELETE_NEXT && (modifiers() & B_SHIFT_KEY)) + msg->what = M_DELETE_PREV; + + bool foundRef = false; + entry_ref nextRef; + if ((msg->what == M_DELETE_PREV || msg->what == M_DELETE_NEXT) && fRef) + { + // + // Find the next message that should be displayed + // + nextRef = *fRef; + foundRef = GetTrackerWindowFile(&nextRef, msg->what == + M_DELETE_NEXT); + } + if (fIncoming) + SetCurrentMessageRead(); + + if (!fTrackerMessenger.IsValid() || !fIncoming) { + // + // Not associated with a tracker window. Create a new + // messenger and ask the tracker to delete this entry + // + if (fDraft || fIncoming) { + BMessenger tracker("application/x-vnd.Be-TRAK"); + if (tracker.IsValid()) { + BMessage msg('Ttrs'); + msg.AddRef("refs", fRef); + tracker.SendMessage(&msg); + } else { + (new BAlert("", + MDR_DIALECT_CHOICE ( "Need tracker to move items to trash", + "削除するにはTrackerが必要です。"), + MDR_DIALECT_CHOICE ("sorry","削除できませんでした。")))->Go(); + } + } + } else { + // + // This is associated with a tracker window. Ask the + // window to delete this entry. Do it this way if we + // can instead of the above way because it doesn't reset + // the selection (even though we set selection below, this + // still causes problems). + // + BMessage delmsg(B_DELETE_PROPERTY); + BMessage entryspec('sref'); + entryspec.AddRef("refs", fRef); + entryspec.AddString("property", "Entry"); + delmsg.AddSpecifier(&entryspec); + fTrackerMessenger.SendMessage(&delmsg); + } + + // + // If the next file was found, open it. If it was not, + // we have no choice but to close this window. + // + if (foundRef) { + TMailWindow *window = static_cast(be_app)->FindWindow(nextRef); + if (window == NULL) + OpenMessage(&nextRef, fHeaderView->fCharacterSetUserSees); + else + window->Activate(); + + SetTrackerSelectionToCurrent(); + + if (window == NULL) + break; + } + + fSent = true; + BMessage msg(B_CLOSE_REQUESTED); + PostMessage(&msg); + break; + } + + case M_CLOSE_READ: + { + BMessage message(B_CLOSE_REQUESTED); + message.AddString("status", "Read"); + PostMessage(&message); + break; + } + case M_CLOSE_SAVED: + { + BMessage message(B_CLOSE_REQUESTED); + message.AddString("status", "Saved"); + PostMessage(&message); + break; + } + case M_CLOSE_SAME: + { + BMessage message(B_CLOSE_REQUESTED); + message.AddString("status", ""); + message.AddString("same", ""); + PostMessage(&message); + break; + } + case M_CLOSE_CUSTOM: + if (msg->HasString("status")) + { + const char *str; + msg->FindString("status", (const char**) &str); + BMessage message(B_CLOSE_REQUESTED); + message.AddString("status", str); + PostMessage(&message); + } + else + { + BRect r = Frame(); + r.left += ((r.Width() - STATUS_WIDTH) / 2); + r.right = r.left + STATUS_WIDTH; + r.top += 40; + r.bottom = r.top + STATUS_HEIGHT; + + BString string = "could not read"; + BNode node(fRef); + if (node.InitCheck() == B_OK) + ReadAttrString(&node, B_MAIL_ATTR_STATUS, &string); + + new TStatusWindow(r, this, string.String()); + } + break; + + case M_STATUS: + { + BMenuItem *menu; + msg->FindPointer("source", (void **)&menu); + BMessage message(B_CLOSE_REQUESTED); + message.AddString("status", menu->Label()); + PostMessage(&message); + break; + } + case M_HEADER: + { + header_flag = !fHeader->IsMarked(); + fHeader->SetMarked(header_flag); + + BMessage message(M_HEADER); + message.AddBool("header", header_flag); + PostMessage(&message, fContentView->fTextView); + break; + } + case M_RAW: + { + bool raw = !(fRaw->IsMarked()); + fRaw->SetMarked(raw); + BMessage message(M_RAW); + message.AddBool("raw", raw); + PostMessage(&message, fContentView->fTextView); + break; + } + case M_SEND_NOW: + case M_SAVE_AS_DRAFT: + Send(msg->what == M_SEND_NOW); + break; + + case M_SAVE: + { + char *str; + if (msg->FindString("address", (const char **)&str) == B_NO_ERROR) + { + char *arg = (char *)malloc(strlen("META:email ") + strlen(str) + 1); + BVolumeRoster volumeRoster; + BVolume volume; + volumeRoster.GetBootVolume(&volume); + + BQuery query; + query.SetVolume(&volume); + sprintf(arg, "META:email=%s", str); + query.SetPredicate(arg); + query.Fetch(); + + BEntry entry; + if (query.GetNextEntry(&entry) == B_NO_ERROR) + { + BMessenger tracker("application/x-vnd.Be-TRAK"); + if (tracker.IsValid()) + { + entry_ref ref; + entry.GetRef(&ref); + + BMessage open(B_REFS_RECEIVED); + open.AddRef("refs", &ref); + tracker.SendMessage(&open); + } + } + else + { + sprintf(arg, "META:email %s", str); + status_t result = be_roster->Launch("application/x-person", 1, &arg); + if (result != B_NO_ERROR) + (new BAlert("", MDR_DIALECT_CHOICE ( + "Sorry, could not find an application that supports the 'Person' data type.", + "Peopleデータ形式をサポートするアプリケーションが見つかりませんでした。"), + MDR_DIALECT_CHOICE ("Ok","了解")))->Go(); + } + free(arg); + } + break; + } + + case M_PRINT_SETUP: + PrintSetup(); + break; + + case M_PRINT: + Print(); + break; + + case M_SELECT: + break; + + case M_FIND: + FindWindow::Find(this); + break; + + case M_FIND_AGAIN: + FindWindow::FindAgain(this); + break; + + case M_QUOTE: + case M_REMOVE_QUOTE: + PostMessage(msg->what, fContentView); + break; + + case M_RANDOM_SIG: + { + BList sigList; + BMessage *message; + + BVolume volume; + BVolumeRoster().GetBootVolume(&volume); + + BQuery query; + query.SetVolume(&volume); + + char predicate[128]; + sprintf(predicate, "%s = *", INDEX_SIGNATURE); + query.SetPredicate(predicate); + query.Fetch(); + + BEntry entry; + while (query.GetNextEntry(&entry) == B_NO_ERROR) + { + BFile file(&entry, O_RDONLY); + if (file.InitCheck() == B_NO_ERROR) + { + entry_ref ref; + entry.GetRef(&ref); + + message = new BMessage(M_SIGNATURE); + message->AddRef("ref", &ref); + sigList.AddItem(message); + } + } + if (sigList.CountItems() > 0) + { + srand(time(0)); + PostMessage((BMessage *)sigList.ItemAt(rand() % sigList.CountItems())); + + for (int32 i = 0; (message = (BMessage *)sigList.ItemAt(i)) != NULL; i++) + delete message; + } + break; + } + case M_SIGNATURE: + { + BMessage message(*msg); + PostMessage(&message, fContentView); + fSigAdded = true; + break; + } + case M_SIG_MENU: + { + TMenu *menu; + BMenuItem *item; + menu = new TMenu( "Add Signature", INDEX_SIGNATURE, M_SIGNATURE, true ); + + BPoint where; + bool open_anyway = true; + + if (msg->FindPoint("where", &where) != B_OK) + { + BRect bounds; + bounds = fSigButton->Bounds(); + where = fSigButton->ConvertToScreen(BPoint((bounds.right-bounds.left)/2, + (bounds.bottom-bounds.top)/2)); + } + else if (msg->FindInt32("buttons") == B_SECONDARY_MOUSE_BUTTON) + open_anyway = false; + + if ((item = menu->Go(where, false, open_anyway)) != NULL) + { + item->SetTarget(this); + (dynamic_cast(item))->Invoke(); + } + delete menu; + break; + } + + case M_ADD: + if (!fPanel) + { + BMessenger me(this); + BMessage msg(REFS_RECEIVED); + fPanel = new BFilePanel(B_OPEN_PANEL, &me, &open_dir, false, true, &msg); + } + else if (!fPanel->Window()->IsHidden()) + fPanel->Window()->Activate(); + + if (fPanel->Window()->IsHidden()) + fPanel->Window()->Show(); + break; + + case M_REMOVE: + PostMessage(msg->what, fEnclosuresView); + break; + + case CHARSET_CHOICE_MADE: + if (fIncoming && !fResending) { + // The user wants to see the message they are reading (not + // composing) displayed with a different kind of character set + // for decoding. Reload the whole message and redisplay. For + // messages which are being composed, the character set is + // retrieved from the header view when it is needed. + + entry_ref fileRef = *fRef; + int32 characterSet; + msg->FindInt32("charset", &characterSet); + OpenMessage(&fileRef, characterSet); + } + break; + + case REFS_RECEIVED: + AddEnclosure(msg); + break; + + // + // Navigation Messages + // + case M_PREVMSG: + case M_NEXTMSG: + if (fRef) + { + entry_ref nextRef = *fRef; + if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) { + TMailWindow *window = static_cast(be_app)->FindWindow(nextRef); + if (window == NULL) { + SetCurrentMessageRead(); + OpenMessage(&nextRef, fHeaderView->fCharacterSetUserSees); + } else { + window->Activate(); + + //fSent = true; + BMessage msg(B_CLOSE_REQUESTED); + PostMessage(&msg); + } + + SetTrackerSelectionToCurrent(); + } + else + beep(); + } + break; + case M_SAVE_POSITION: + if (fRef) + SaveTrackerPosition(fRef); + break; + + case M_OPEN_MAIL_FOLDER: + case M_OPEN_MAIL_BOX: + { + BEntry folderEntry; + BPath path; + // Get the user home directory + if (find_directory(B_USER_DIRECTORY, &path) != B_OK) + break; + if (msg->what == M_OPEN_MAIL_FOLDER) + path.Append(kMailFolder); + else + path.Append(kMailboxFolder); + if (folderEntry.SetTo(path.Path()) == B_OK && folderEntry.Exists()) + { + BMessage thePackage(B_REFS_RECEIVED); + BMessenger tracker("application/x-vnd.Be-TRAK"); + + entry_ref ref; + folderEntry.GetRef(&ref); + thePackage.AddRef("refs", &ref); + tracker.SendMessage(&thePackage); + } + break; + } + case RESET_BUTTONS: + fChanged = false; + fFieldState = 0; + if (fHeaderView->fTo->TextView()->TextLength()) + fFieldState |= FIELD_TO; + if (fHeaderView->fSubject->TextView()->TextLength()) + fFieldState |= FIELD_SUBJECT; + if (fHeaderView->fCc->TextView()->TextLength()) + fFieldState |= FIELD_CC; + if (fHeaderView->fBcc->TextView()->TextLength()) + fFieldState |= FIELD_BCC; + if (fContentView->fTextView->TextLength()) + fFieldState |= FIELD_BODY; + + if (fSaveButton) + fSaveButton->SetEnabled(false); + if (fPrintButton) + fPrintButton->SetEnabled(fFieldState); + if (fSendButton) + fSendButton->SetEnabled((fFieldState & FIELD_TO) || (fFieldState & FIELD_BCC)); + break; + + case M_CHECK_SPELLING: + if (gDictCount == 0) + // Give the application time to initialise and load the dictionaries. + snooze (1500000); + if (!gDictCount) + { + beep(); + (new BAlert("", + MDR_DIALECT_CHOICE ( + "The spell check feature requires the optional \"words\" file on your BeOS CD.", + "スペルチェク機能はBeOS CDの optional \"words\" ファイルが必要です"), + MDR_DIALECT_CHOICE ("Ok","了解"), + NULL, NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, + B_STOP_ALERT))->Go(); + } + else + { + fSpelling->SetMarked(!fSpelling->IsMarked()); + fContentView->fTextView->EnableSpellCheck(fSpelling->IsMarked()); + } + break; + + default: + BWindow::MessageReceived(msg); + } +} + + +void +TMailWindow::AddEnclosure(BMessage *msg) +{ + if (fEnclosuresView == NULL && !fIncoming) + { + BRect r; + r.left = 0; + r.top = fHeaderView->Frame().bottom - 1; + r.right = Frame().Width() + 2; + r.bottom = r.top + ENCLOSURES_HEIGHT; + + fEnclosuresView = new TEnclosuresView(r, Frame()); + AddChild(fEnclosuresView, fContentView); + fContentView->ResizeBy(0, -ENCLOSURES_HEIGHT); + fContentView->MoveBy(0, ENCLOSURES_HEIGHT); + } + + if (fEnclosuresView == NULL) + return; + + if (msg && msg->HasRef("refs")) + { + // Add enclosure to view + PostMessage(msg, fEnclosuresView); + + fChanged = true; + BEntry entry; + entry_ref ref; + msg->FindRef("refs", &ref); + entry.SetTo(&ref); + entry.GetParent(&entry); + entry.GetRef(&open_dir); + } +} + + +bool +TMailWindow::QuitRequested() +{ + int32 result; + + if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent + && (strlen(fHeaderView->fTo->Text()) + || strlen(fHeaderView->fSubject->Text()) + || (fHeaderView->fCc && strlen(fHeaderView->fCc->Text())) + || (fHeaderView->fBcc && strlen(fHeaderView->fBcc->Text())) + || (strlen(fContentView->fTextView->Text()) && (!fStartingText || fStartingText && strcmp(fContentView->fTextView->Text(), fStartingText))) + || (fEnclosuresView != NULL && fEnclosuresView->fList->CountItems()))) + { + if (fResending) { + result = (new BAlert("", + MDR_DIALECT_CHOICE ( + "Do you wish to send this message before closing?", + "閉じる前に送信しますか?"), + MDR_DIALECT_CHOICE ("Discard","無視"), + MDR_DIALECT_CHOICE ("Cancel","中止"), + MDR_DIALECT_CHOICE ("Send","送信"), + B_WIDTH_AS_USUAL, B_OFFSET_SPACING, + B_WARNING_ALERT))->Go(); + + switch (result) { + case 0: // Discard + break; + case 1: // Cancel + return false; + case 2: // Send + Send(true); + break; + } + } else { + result = (new BAlert("", + MDR_DIALECT_CHOICE ( + "Do you wish to save this message as a draft before closing?", + "閉じる前に保存しますか?"), + MDR_DIALECT_CHOICE ("Don't Save","保存しない"), + MDR_DIALECT_CHOICE ("Cancel","中止"), + MDR_DIALECT_CHOICE ("Save","保存"), + B_WIDTH_AS_USUAL, B_OFFSET_SPACING, + B_WARNING_ALERT))->Go(); + switch (result) { + case 0: // Don't Save + break; + case 1: // Cancel + return false; + case 2: // Save + Send(false); + break; + } + } + } + + BMessage message(WINDOW_CLOSED); + message.AddInt32("kind", MAIL_WINDOW); + message.AddPointer( "window", this ); + be_app->PostMessage(&message); + + if ((CurrentMessage()) && (CurrentMessage()->HasString("status"))) { + // + // User explicitly requests a status to set this message to. + // + if (!CurrentMessage()->HasString("same")) { + const char *status = CurrentMessage()->FindString("status"); + if (status != NULL) { + BNode node(fRef); + if (node.InitCheck() == B_NO_ERROR) { + node.RemoveAttr(B_MAIL_ATTR_STATUS); + WriteAttrString(&node, B_MAIL_ATTR_STATUS, status); + } + } + } + } else if (fRef) { + // + // ...Otherwise just set the message read + // + SetCurrentMessageRead(); + } + + return true; +} + + +void +TMailWindow::Show() +{ + if (Lock()) { + if (!fResending && (fIncoming || fReplying)) + fContentView->fTextView->MakeFocus(true); + else + { + BTextView *textView = fHeaderView->fTo->TextView(); + fHeaderView->fTo->MakeFocus(true); + textView->Select(0, textView->TextLength()); + } + Unlock(); + } + BWindow::Show(); +} + + +void +TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/) +{ + float height; + float width; + BScreen screen(this); + BRect r; + BRect s_frame = screen.Frame(); + + r = Frame(); + width = 80 * ((TMailApp*)be_app)->fFont.StringWidth("M") + + (r.Width() - fContentView->fTextView->Bounds().Width() + 6); + if (width > (s_frame.Width() - 8)) + width = s_frame.Width() - 8; + + height = max_c(fContentView->fTextView->CountLines(), 20) * + fContentView->fTextView->LineHeight(0) + + (r.Height() - fContentView->fTextView->Bounds().Height()); + if (height > (s_frame.Height() - 29)) + height = s_frame.Height() - 29; + + r.right = r.left + width; + r.bottom = r.top + height; + + if (abs((int)(Frame().Width() - r.Width())) < 5 + && abs((int)(Frame().Height() - r.Height())) < 5) + r = fZoom; + else + { + fZoom = Frame(); + s_frame.InsetBy(6, 6); + + if (r.Width() > s_frame.Width()) + r.right = r.left + s_frame.Width(); + if (r.Height() > s_frame.Height()) + r.bottom = r.top + s_frame.Height(); + + if (r.right > s_frame.right) + { + r.left -= r.right - s_frame.right; + r.right = s_frame.right; + } + if (r.bottom > s_frame.bottom) + { + r.top -= r.bottom - s_frame.bottom; + r.bottom = s_frame.bottom; + } + if (r.left < s_frame.left) + { + r.right += s_frame.left - r.left; + r.left = s_frame.left; + } + if (r.top < s_frame.top) + { + r.bottom += s_frame.top - r.top; + r.top = s_frame.top; + } + } + + ResizeTo(r.Width(), r.Height()); + MoveTo(r.LeftTop()); +} + + +void +TMailWindow::WindowActivated(bool status) +{ + if (status) { + BAutolock locker(sWindowListLock); + sWindowList.RemoveItem(this); + sWindowList.AddItem(this, 0); + } +} + + +void +TMailWindow::Forward(entry_ref *ref, TMailWindow *window, bool includeAttachments) +{ + BEmailMessage *mail = window->Mail(); + if (mail == NULL) + return; + + fMail = mail->ForwardMessage(gUseAccountFrom == ACCOUNT_FROM_MAIL, includeAttachments); + + BFile file(ref, O_RDONLY); + if (file.InitCheck() < B_NO_ERROR) + return; + + fHeaderView->fSubject->SetText(fMail->Subject()); + + // set mail account + + if (gUseAccountFrom == ACCOUNT_FROM_MAIL) { + fHeaderView->fChain = fMail->Account(); + + BMenu *menu = fHeaderView->fAccountMenu; + for (int32 i = menu->CountItems(); i-- > 0;) { + BMenuItem *item = menu->ItemAt(i); + BMessage *msg; + if (item && (msg = item->Message()) != NULL + && msg->FindInt32("id") == fHeaderView->fChain) + item->SetMarked(true); + } + } + + if (fMail->CountComponents() > 1) { + // if there are any enclosures to be added, first add the enclosures + // view to the window + AddEnclosure(NULL); + if (fEnclosuresView) + fEnclosuresView->AddEnclosuresFromMail(fMail); + } + + fContentView->fTextView->LoadMessage(fMail, false, NULL); + fChanged = false; + fFieldState = 0; +} + + +class HorizontalLine : public BView { + public: + HorizontalLine(BRect rect) : BView (rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW) {} + virtual void Draw(BRect rect) + { + FillRect(rect,B_SOLID_HIGH); + } +}; + + +void +TMailWindow::Print() +{ + if (!print_settings) + { + PrintSetup(); + if (!print_settings) + return; + } + + BPrintJob print(Title()); + print.SetSettings(new BMessage(*print_settings)); + + if (print.ConfigJob() == B_NO_ERROR) + { + int32 curPage = 1; + int32 lastLine = 0; + BTextView header_view(print.PrintableRect(),"header",print.PrintableRect().OffsetByCopy(BPoint(-print.PrintableRect().left,-print.PrintableRect().top)),B_FOLLOW_ALL_SIDES); + + //---------Init the header fields + #define add_header_field(field) {/*header_view.SetFontAndColor(be_bold_font);*/ \ + header_view.Insert(fHeaderView->field->Label()); \ + header_view.Insert(" ");\ + /*header_view.SetFontAndColor(be_plain_font);*/ \ + header_view.Insert(fHeaderView->field->Text()); \ + header_view.Insert("\n");} + add_header_field(fSubject); + add_header_field(fTo); + if ((fHeaderView->fCc != NULL) && (strcmp(fHeaderView->fCc->Text(),"") != 0)) + add_header_field(fCc); + header_view.Insert(fHeaderView->fDate->Text()); + + int32 maxLine = fContentView->fTextView->CountLines(); + BRect pageRect = print.PrintableRect(); + BRect curPageRect = pageRect; + + print.BeginJob(); + float header_height = header_view.TextHeight(0,header_view.CountLines()); + BBitmap bmap(BRect(0,0,pageRect.Width(),header_height),B_BITMAP_ACCEPTS_VIEWS,B_RGBA32); + bmap.Lock(); + bmap.AddChild(&header_view); + print.DrawView(&header_view,BRect(0,0,pageRect.Width(),header_height),BPoint(0.0,0.0)); + HorizontalLine line(BRect(0,0,pageRect.right,0)); + bmap.AddChild(&line); + print.DrawView(&line,line.Bounds(),BPoint(0,header_height+1)); + bmap.Unlock(); + header_height += 5; + + do + { + int32 lineOffset = fContentView->fTextView->OffsetAt(lastLine); + curPageRect.OffsetTo(0, fContentView->fTextView->PointAt(lineOffset).y); + + int32 fromLine = lastLine; + lastLine = fContentView->fTextView->LineAt(BPoint(0.0, curPageRect.bottom - ((curPage == 1) ? header_height : 0))); + + float curPageHeight = fContentView->fTextView->TextHeight(fromLine, lastLine) + ((curPage == 1) ? header_height : 0); + if(curPageHeight > pageRect.Height()) + curPageHeight = fContentView->fTextView->TextHeight(fromLine, --lastLine) + ((curPage == 1) ? header_height : 0); + + curPageRect.bottom = curPageRect.top + curPageHeight - 1.0; + + if((curPage >= print.FirstPage()) && + (curPage <= print.LastPage())) + { + print.DrawView(fContentView->fTextView, curPageRect, BPoint(0.0, (curPage == 1) ? header_height : 0.0)); + print.SpoolPage(); + } + + curPageRect = pageRect; + lastLine++; + curPage++; + + } while (print.CanContinue() && lastLine < maxLine); + + print.CommitJob(); + bmap.RemoveChild(&header_view); + bmap.RemoveChild(&line); + } +} + + +void +TMailWindow::PrintSetup() +{ + BPrintJob print("mail_print"); + status_t result; + + if (print_settings) + print.SetSettings(new BMessage(*print_settings)); + + if ((result = print.ConfigPage()) == B_NO_ERROR) + { + delete print_settings; + print_settings = print.Settings(); + } +} + + +void +TMailWindow::SetTo(const char *mailTo, const char *subject, const char *ccTo, + const char *bccTo, const BString *body, BMessage *enclosures) +{ + Lock(); + + if (mailTo && mailTo[0]) + fHeaderView->fTo->SetText(mailTo); + if (subject && subject[0]) + fHeaderView->fSubject->SetText(subject); + if (ccTo && ccTo[0]) + fHeaderView->fCc->SetText(ccTo); + if (bccTo && bccTo[0]) + fHeaderView->fBcc->SetText(bccTo); + + if (body && body->Length()) + { + fContentView->fTextView->SetText(body->String(), body->Length()); + fContentView->fTextView->GoToLine(0); + } + + if (enclosures && enclosures->HasRef("refs")) + AddEnclosure(enclosures); + + Unlock(); +} + + +void +TMailWindow::CopyMessage(entry_ref *ref, TMailWindow *src) +{ + BNode file(ref); + if (file.InitCheck() == B_OK) { + BString string; + if (fHeaderView->fTo && ReadAttrString(&file, B_MAIL_ATTR_TO, &string) == B_OK) + fHeaderView->fTo->SetText(string.String()); + + if (fHeaderView->fSubject && ReadAttrString(&file, B_MAIL_ATTR_SUBJECT, &string) == B_OK) + fHeaderView->fSubject->SetText(string.String()); + + if (fHeaderView->fCc && ReadAttrString(&file, B_MAIL_ATTR_CC, &string) == B_OK) + fHeaderView->fCc->SetText(string.String()); + } + + TTextView *text = src->fContentView->fTextView; + text_run_array *style = text->RunArray(0, text->TextLength()); + + fContentView->fTextView->SetText(text->Text(), text->TextLength(), style); + + free(style); +} + + +void +TMailWindow::Reply(entry_ref *ref, TMailWindow *window, uint32 type) +{ + const char *notImplementedString = ""; + + fRepliedMail = *ref; + SetOriginatingWindow(window); + + BEmailMessage *mail = window->Mail(); + if (mail == NULL) + return; + + if (type == M_REPLY_ALL) + type = B_MAIL_REPLY_TO_ALL; + else if (type == M_REPLY_TO_SENDER) + type = B_MAIL_REPLY_TO_SENDER; + else + type = B_MAIL_REPLY_TO; + + fMail = mail->ReplyMessage(mail_reply_to_mode(type), + gUseAccountFrom == ACCOUNT_FROM_MAIL, QUOTE); + + // set header fields + fHeaderView->fTo->SetText(fMail->To()); + fHeaderView->fCc->SetText(fMail->CC()); + fHeaderView->fSubject->SetText(fMail->Subject()); + + int32 chainID; + BFile file(window->fRef, B_READ_ONLY); + if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &chainID, 4) < B_OK) + chainID = -1; + + // set mail account + + if ((gUseAccountFrom == ACCOUNT_FROM_MAIL) || (chainID > -1)) { + if (gUseAccountFrom == ACCOUNT_FROM_MAIL) + fHeaderView->fChain = fMail->Account(); + else + fHeaderView->fChain = chainID; + + BMenu *menu = fHeaderView->fAccountMenu; + for (int32 i = menu->CountItems(); i-- > 0;) { + BMenuItem *item = menu->ItemAt(i); + BMessage *msg; + if (item && (msg = item->Message()) != NULL + && msg->FindInt32("id") == fHeaderView->fChain) + item->SetMarked(true); + } + } + + // create preamble string + + char preamble[1024], *from = gReplyPreamble, *to = preamble; + while (*from) { + if (*from == '%') { + // insert special content + int32 length; + + switch (*++from) { + case 'n': // full name + { + BString fullName(mail->From()); + if (fullName.Length() <= 0) + fullName = "No-From-Address-Available"; + + extract_address_name(fullName); + length = fullName.Length(); + memcpy(to, fullName.String(), length); + to += length; + break; + } + + case 'e': // eMail address + { + const char *address = mail->From(); + if (address == NULL) + address = ""; + length = strlen(address); + memcpy(to, address, length); + to += length; + break; + } + + case 'd': // date + { + const char *date = mail->Date(); + if (date == NULL) + date = "No-Date-Available"; + length = strlen(date); + memcpy(to, date, length); + to += length; + break; + } + + // ToDo: parse stuff! + case 'f': // first name + case 'l': // last name + length = strlen(notImplementedString); + memcpy(to, notImplementedString, length); + to += length; + break; + + default: // Sometimes a % is just a %. + *to++ = *from; + } + } else if (*from == '\\') { + switch (*++from) { + case 'n': + *to++ = '\n'; + break; + + default: + *to++ = *from; + } + } else + *to++ = *from; + + from++; + } + *to = '\0'; + + // insert (if selection) or load (if whole mail) message text into text view + + int32 finish, start; + window->fContentView->fTextView->GetSelection(&start, &finish); + if (start != finish) { + char *text = (char *)malloc(finish - start + 1); + if (text == NULL) + return; + + window->fContentView->fTextView->GetText(start, finish - start, text); + if (text[strlen(text) - 1] != '\n') { + text[strlen(text)] = '\n'; + finish++; + } + fContentView->fTextView->SetText(text, finish - start); + free(text); + + finish = fContentView->fTextView->CountLines() - 1; + for (int32 loop = 0; loop < finish; loop++) { + fContentView->fTextView->GoToLine(loop); + fContentView->fTextView->Insert((const char *)QUOTE); + } + + if (gColoredQuotes) { + const BFont *font = fContentView->fTextView->Font(); + int32 length = fContentView->fTextView->TextLength(); + + TextRunArray style(length / 8 + 8); + + FillInQuoteTextRuns(fContentView->fTextView, fContentView->fTextView->Text(), + length, font, &style.Array(), style.MaxEntries()); + + fContentView->fTextView->SetRunArray(0, length, &style.Array()); + } + + fContentView->fTextView->GoToLine(0); + if (strlen(preamble) > 0) + fContentView->fTextView->Insert(preamble); + } + else + fContentView->fTextView->LoadMessage(mail, true, preamble); + + fReplying = true; +} + + +status_t +TMailWindow::Send(bool now) +{ + uint32 characterSetToUse = gMailCharacterSet; + mail_encoding encodingForBody = quoted_printable; + mail_encoding encodingForHeaders = quoted_printable; + + if (!now) { + status_t status; + + if ((status = SaveAsDraft()) != B_OK) { + beep(); + (new BAlert("", + MDR_DIALECT_CHOICE ("E-mail draft could not be saved!","ドラフトは保存できませんでした。"), + MDR_DIALECT_CHOICE ("Ok","了解")))->Go(); + } + return status; + } + + if (fHeaderView != NULL) + characterSetToUse = fHeaderView->fCharacterSetUserSees; + + // Set up the encoding to use for converting binary to printable ASCII. + // Normally this will be quoted printable, but for some old software, + // particularly Japanese stuff, they only understand base64. They also + // prefer it for the smaller size. Later on this will be reduced to 7bit + // if the encoded text is just 7bit characters. + if (characterSetToUse == B_SJIS_CONVERSION || + characterSetToUse == B_EUC_CONVERSION) + encodingForBody = base64; + else if (characterSetToUse == B_JIS_CONVERSION || + characterSetToUse == B_MAIL_US_ASCII_CONVERSION || + characterSetToUse == B_ISO1_CONVERSION || + characterSetToUse == B_EUC_KR_CONVERSION) + encodingForBody = eight_bit; + + // Using quoted printable headers on almost completely non-ASCII Japanese + // is a waste of time. Besides, some stupid cell phone services need + // base64 in the headers. + if (characterSetToUse == B_SJIS_CONVERSION || + characterSetToUse == B_EUC_CONVERSION || + characterSetToUse == B_JIS_CONVERSION || + characterSetToUse == B_EUC_KR_CONVERSION) + encodingForHeaders = base64; + + // Count the number of characters in the message body which aren't in the + // currently selected character set. Also see if the resulting encoded + // text can safely use 7 bit characters. + if (fContentView->fTextView->TextLength() > 0) { + // First do a trial encoding with the user's character set. + int32 converterState = 0; + int32 originalLength; + BString tempString; + int32 tempStringLength; + char *tempStringPntr; + originalLength = fContentView->fTextView->TextLength(); + tempStringLength = originalLength * + 6 /* Some character sets bloat up on escape codes */; + tempStringPntr = tempString.LockBuffer (tempStringLength); + if (tempStringPntr != NULL && + B_OK == mail_convert_from_utf8 ( + characterSetToUse, + fContentView->fTextView->Text(), &originalLength, + tempStringPntr, &tempStringLength, &converterState, + 0x1A /* The code to substitute for unknown characters */)) { + + // Check for any characters which don't fit in a 7 bit encoding. + int i; + bool has8Bit = false; + for (i = 0; i < tempStringLength; i++) + if (tempString[i] == 0 || (tempString[i] & 0x80)) { + has8Bit = true; + break; + } + if (!has8Bit) + encodingForBody = seven_bit; + tempString.UnlockBuffer (tempStringLength); + + // Count up the number of unencoded characters and warn the user about them. + if (gWarnAboutUnencodableCharacters) { + int32 offset = 0; + int count = 0; + while (offset >= 0) { + offset = tempString.FindFirst (0x1A, offset); + if (offset >= 0) { + count++; + offset++; // Don't get stuck finding the same character again. + } + } + if (count > 0) { + int32 userAnswer; + BString messageString; + MDR_DIALECT_CHOICE ( + messageString << "Your main text contains " << count << + " unencodable characters. Perhaps a different character " + "set would work better? Hit Send to send it anyway " + "(a substitute character will be used in place of " + "the unencodable ones), or choose Cancel to go back " + "and try fixing it up." + , + messageString << "送信メールの本文には " << count << + " 個のエンコードできない文字があります。" + "違う文字セットを使うほうがよい可能性があります。" + "このまま送信の場合は「送信」ボタンを押してください。" + "その場合、代用文字がUnicode化可能な文字に代わって使われます。" + "文字セットを変更する場合は「中止」ボタンを押して下さい。" + ); + userAnswer = (new BAlert ("Question", messageString.String(), + MDR_DIALECT_CHOICE ("Send","送信"), + MDR_DIALECT_CHOICE ("Cancel","中止"), // Default is cancel. + NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT)) + ->Go(); + if (userAnswer == 1) + return -1; // Cancel was picked. + } + } + } + } + + status_t result; + + if (fResending) { + BFile file(fRef, O_RDONLY); + result = file.InitCheck(); + if (result == B_OK) + { + BEmailMessage mail(&file); + mail.SetTo(fHeaderView->fTo->Text(), characterSetToUse, encodingForHeaders); + + if (fHeaderView->fChain != ~0L) + mail.SendViaAccount(fHeaderView->fChain); + + result = mail.Send(now); + } + } else { + if (fMail == NULL) + // the mail will be deleted when the window is closed + fMail = new BEmailMessage; + + // Had an embarrassing bug where replying to a message and clearing the + // CC field meant that it got sent out anyway, so pass in empty strings + // when changing the header to force it to remove the header. + + fMail->SetTo(fHeaderView->fTo->Text(), characterSetToUse, encodingForHeaders); + fMail->SetSubject(fHeaderView->fSubject->Text(), characterSetToUse, encodingForHeaders); + fMail->SetCC(fHeaderView->fCc->Text(), characterSetToUse, encodingForHeaders); + fMail->SetBCC(fHeaderView->fBcc->Text()); + + //--- Add X-Mailer field + { + // get app version + version_info versionInfo; + memset(&versionInfo, 0, sizeof(version_info)); + + app_info appInfo; + if (be_app->GetAppInfo(&appInfo) == B_OK) { + BFile file(&appInfo.ref, B_READ_ONLY); + if (file.InitCheck() == B_OK) { + BAppFileInfo info(&file); + if (info.InitCheck() == B_OK) + info.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND); + } + } + // prepare version variety string + const char *varietyStrings[] = { + "Development", "Alpha", "Beta", + "Gamma", "Golden master", "Final" + }; + char varietyString[32]; + strcpy(varietyString, varietyStrings[versionInfo.variety % 6]); + if (versionInfo.variety < 5) + sprintf(varietyString + strlen(varietyString), "/%li", versionInfo.internal); + + char versionString[255]; + sprintf(versionString, + "BeMail - Mail Daemon Replacement %ld.%ld.%ld %s", + versionInfo.major, versionInfo.middle, versionInfo.minor, varietyString); + fMail->SetHeaderField("X-Mailer", versionString); + } + + /****/ + + // the content text is always added to make sure there is a mail body + fMail->SetBodyTextTo(""); + fContentView->fTextView->AddAsContent(fMail, sWrapMode, characterSetToUse, + encodingForBody); + + if (fEnclosuresView != NULL) { + TListItem *item; + int32 index = 0; + while ((item = (TListItem *)fEnclosuresView->fList->ItemAt(index++)) != NULL) { + if (item->Component()) + continue; + + // leave out missing enclosures + BEntry entry(item->Ref()); + if (!entry.Exists()) + continue; + + fMail->Attach(item->Ref(), attachAttributes_mode); + } + } + if (fHeaderView->fChain != ~0L) + fMail->SendViaAccount(fHeaderView->fChain); + + result = fMail->Send(now); + + if (fReplying) { + // Set status of the replied mail + + BNode node(&fRepliedMail); + if (node.InitCheck() >= B_OK) { + if (fOriginatingWindow) { + BMessage msg(M_SAVE_POSITION), reply; + fOriginatingWindow->SendMessage(&msg, &reply); + } + WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied"); + } + } + } + + bool close = false; + char errorMessage[256]; + + switch (result) { + case B_NO_ERROR: + close = true; + fSent = true; + + // If it's a draft, remove the draft file + if (fDraft) { + BEntry entry(fRef); + entry.Remove(); + } + break; + + case B_MAIL_NO_DAEMON: + { + close = true; + fSent = true; + + int32 start = (new BAlert("no daemon", + MDR_DIALECT_CHOICE ("The mail_daemon is not running. " + "The message is queued and will be sent when the mail_daemon is started.", + "mail_daemon が開始されていません " + "このメッセージは処理待ちとなり、mail_daemon 開始後に処理されます"), + MDR_DIALECT_CHOICE ("Start Now","ただちに開始する"), + MDR_DIALECT_CHOICE ("Ok","了解")))->Go(); + + if (start == 0) { + result = be_roster->Launch("application/x-vnd.Be-POST"); + if (result == B_OK) + BMailDaemon::SendQueuedMail(); + else + sprintf(errorMessage,"The mail_daemon could not be started:\n (0x%.8lx) %s", + result,strerror(result)); + } + break; + } + +// case B_MAIL_UNKNOWN_HOST: +// case B_MAIL_ACCESS_ERROR: +// sprintf(errorMessage, "An error occurred trying to connect with the SMTP " +// "host. Check your SMTP host name."); +// break; +// +// case B_MAIL_NO_RECIPIENT: +// sprintf(errorMessage, "You must have either a \"To\" or \"Bcc\" recipient."); +// break; + + default: + sprintf(errorMessage, "An error occurred trying to send mail (0x%.8lx): %s", + result,strerror(result)); + } + + if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) { + beep(); + (new BAlert("", errorMessage, "Ok"))->Go(); + } + if (close) + PostMessage(B_QUIT_REQUESTED); + + return result; +} + + +status_t +TMailWindow::SaveAsDraft() +{ + status_t status; + BPath draftPath; + BDirectory dir; + BFile draft; + uint32 flags = 0; + + if (fDraft) { + if ((status = draft.SetTo(fRef, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE)) != B_OK) + return status; + } else { + // Get the user home directory + if ((status = find_directory(B_USER_DIRECTORY, &draftPath)) != B_OK) + return status; + + // Append the relative path of the draft directory + draftPath.Append(kDraftPath); + + // Create the file + status = dir.SetTo(draftPath.Path()); + switch (status) { + // Create the directory if it does not exist + case B_ENTRY_NOT_FOUND: + if ((status = dir.CreateDirectory(draftPath.Path(), &dir)) != B_OK) + return status; + case B_OK: + { + char fileName[512], *eofn; + int32 i; + + // save as some version of the message's subject + strncpy(fileName, fHeaderView->fSubject->Text(), sizeof(fileName)-10); + fileName[sizeof(fileName)-10]='\0'; // terminate like strncpy doesn't + eofn = fileName + strlen(fileName); + + // convert /, \ and : to - + for (char *bad = fileName; (bad = strchr(bad, '/')) != NULL; ++bad) *bad = '-'; + for (char *bad = fileName; (bad = strchr(bad, '\\')) != NULL;++bad) *bad = '-'; + for (char *bad = fileName; (bad = strchr(bad, ':')) != NULL; ++bad) *bad = '-'; + + // Create the file; if the name exists, find a unique name + flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS; + for (i = 1; (status = draft.SetTo(&dir, fileName, flags )) != B_OK; i++) { + if( status != B_FILE_EXISTS ) + return status; + sprintf(eofn, "%ld", i ); + } + + // Cache the ref + delete fRef; + BEntry entry(&dir, fileName); + fRef = new entry_ref; + entry.GetRef(fRef); + break; + } + default: + return status; + } + } + + // Write the content of the message + draft.Write(fContentView->fTextView->Text(), fContentView->fTextView->TextLength()); + + // + // Add the header stuff as attributes + // + WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->fTo->Text()); + WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->fTo->Text()); + WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->fSubject->Text()); + if (fHeaderView->fCc != NULL) + WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->fCc->Text()); + if (fHeaderView->fBcc != NULL) + WriteAttrString(&draft, "MAIL:bcc", fHeaderView->fBcc->Text()); + + // Add the draft attribute for indexing + uint32 draftAttr = true; + draft.WriteAttr( "MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32) ); + + // Add Attachment paths in attribute + if (fEnclosuresView != NULL) { + TListItem *item; + BPath path; + BString pathStr; + + for (int32 i = 0; (item = (TListItem *)fEnclosuresView->fList->ItemAt(i)) != NULL; i++) { + if (i > 0) + pathStr.Append(":"); + + BEntry entry(item->Ref(), true); + if (!entry.Exists()) + continue; + + entry.GetPath(&path); + pathStr.Append(path.Path()); + } + if (pathStr.Length()) + WriteAttrString(&draft, "MAIL:attachments", pathStr.String()); + } + + // Set the MIME Type of the file + BNodeInfo info(&draft); + info.SetType(kDraftType); + + fSent = true; + fDraft = true; + fChanged = false; + + return B_OK; +} + + +status_t +TMailWindow::TrainMessageAs(const char *CommandWord) +{ + status_t errorCode = -1; + char errorString[1500]; + BEntry fileEntry; + BPath filePath; + BMessage replyMessage; + BMessage scriptingMessage; + team_id serverTeam; + + if (fRef == NULL) + goto ErrorExit; // Need to have a real file and name. + errorCode = fileEntry.SetTo(fRef, true /* traverse */); + if (errorCode != B_OK) + goto ErrorExit; + errorCode = fileEntry.GetPath(&filePath); + if (errorCode != B_OK) + goto ErrorExit; + fileEntry.Unset(); + + // Get a connection to the spam database server. Launch if needed. + + if (!gMessengerToSpamServer.IsValid()) { + // Make sure the server is running. + if (!be_roster->IsRunning(kSpamServerSignature)) { + errorCode = be_roster->Launch(kSpamServerSignature); + if (errorCode != B_OK) + goto ErrorExit; + } + + // Set up the messenger to the database server. + errorCode = B_SERVER_NOT_FOUND; + serverTeam = be_roster->TeamFor(kSpamServerSignature); + if (serverTeam < 0) + goto ErrorExit; + + gMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam, &errorCode); + if (!gMessengerToSpamServer.IsValid()) + goto ErrorExit; + } + + // Ask the server to train on the message. Give it the command word and + // the absolute path name to use. + + scriptingMessage.MakeEmpty(); + scriptingMessage.what = B_SET_PROPERTY; + scriptingMessage.AddSpecifier(CommandWord); + errorCode = scriptingMessage.AddData("data", B_STRING_TYPE, + filePath.Path(), strlen(filePath.Path()) + 1, false /* fixed size */); + if (errorCode != B_OK) + goto ErrorExit; + replyMessage.MakeEmpty(); + errorCode = gMessengerToSpamServer.SendMessage(&scriptingMessage, + &replyMessage); + if (errorCode != B_OK + || replyMessage.FindInt32("error", &errorCode) != B_OK + || errorCode != B_OK) + goto ErrorExit; // Classification failed in one of many ways. + + SetTitleForMessage(); // Update window title to show new spam classification. + return B_OK; + +ErrorExit: + beep(); + sprintf(errorString, "Unable to train the message file \"%s\" as %s. " + "Possibly useful error code: %s (%ld).", + filePath.Path(), CommandWord, strerror (errorCode), errorCode); + (new BAlert("", errorString, + MDR_DIALECT_CHOICE("Ok","了解")))->Go(); + return errorCode; +} + + +void +TMailWindow::SetTitleForMessage() +{ + // + // Figure out the title of this message and set the title bar + // + BString title = "BeMail"; + + if (fIncoming) + { + if (fMail->GetName(&title) == B_OK) + title << ": \"" << fMail->Subject() << "\""; + else + title = fMail->Subject(); + + if (gShowSpamGUI && fRef != NULL) { + BString classification; + BNode node (fRef); + char numberString [30]; + BString oldTitle (title); + float spamRatio; + if (node.InitCheck() != B_OK || node.ReadAttrString + ("MAIL:classification", &classification) != B_OK) + classification = "Unrated"; + if (classification != "Spam" && classification != "Genuine") { + // Uncertain, Unrated and other unknown classes, show the ratio. + if (node.InitCheck() == B_OK && sizeof (spamRatio) == + node.ReadAttr("MAIL:ratio_spam", B_FLOAT_TYPE, 0, + &spamRatio, sizeof (spamRatio))) { + sprintf (numberString, "%.4f", spamRatio); + classification << " " << numberString; + } + } + title = ""; + title << "[" << classification << "] " << oldTitle; + } + } + SetTitle(title.String()); +} + + +// +// Open *another* message in the existing mail window. Some code here is +// duplicated from various constructors. +// The duplicated code should be in a private initializer method -- axeld. +// + +status_t +TMailWindow::OpenMessage(entry_ref *ref, uint32 characterSetForDecoding) +{ + // + // Set some references to the email file + // + if (fRef) + delete fRef; + fRef = new entry_ref(*ref); + + if (fStartingText) + { + free(fStartingText); + fStartingText = NULL; + } + fPrevTrackerPositionSaved = false; + fNextTrackerPositionSaved = false; + + fContentView->fTextView->StopLoad(); + delete fMail; + + BFile file(fRef, B_READ_ONLY); + status_t err = file.InitCheck(); + if (err != B_OK) + return err; + + char mimeType[256]; + BNodeInfo fileInfo(&file); + fileInfo.GetType(mimeType); + + // Check if it's a draft file, which contains only the text, and has the + // from, to, bcc, attachments listed as attributes. + if (!strcmp(kDraftType, mimeType)) + { + BNode node(fRef); + off_t size; + BString string; + + fMail = new BEmailMessage; // Not really used much, but still needed. + + // Load the raw UTF-8 text from the file. + file.GetSize(&size); + fContentView->fTextView->SetText(&file, 0, size); + + // Restore Fields from attributes + if (ReadAttrString(&node, B_MAIL_ATTR_TO, &string) == B_OK) + fHeaderView->fTo->SetText(string.String()); + if (ReadAttrString(&node, B_MAIL_ATTR_SUBJECT, &string) == B_OK) + fHeaderView->fSubject->SetText(string.String()); + if (ReadAttrString(&node, B_MAIL_ATTR_CC, &string) == B_OK) + fHeaderView->fCc->SetText(string.String()); + if (ReadAttrString(&node, "MAIL:bcc", &string) == B_OK) + fHeaderView->fBcc->SetText(string.String()); + + // Restore attachments + if (ReadAttrString(&node, "MAIL:attachments", &string) == B_OK) + { + BMessage msg(REFS_RECEIVED); + entry_ref enc_ref; + + char *s = strtok((char *)string.String(), ":"); + while (s) + { + BEntry entry(s, true); + if (entry.Exists()) + { + entry.GetRef(&enc_ref); + msg.AddRef("refs", &enc_ref); + } + s = strtok(NULL, ":"); + } + AddEnclosure(&msg); + } + PostMessage(RESET_BUTTONS); + fIncoming = false; + fDraft = true; + } + else // A real mail message, parse its headers to get from, to, etc. + { + fMail = new BEmailMessage(fRef, characterSetForDecoding); + fIncoming = true; + fHeaderView->LoadMessage(fMail); + } + + err = fMail->InitCheck(); + if (err < B_OK) + { + delete fMail; + fMail = NULL; + return err; + } + + SetTitleForMessage(); + + if (fIncoming) + { + // + // Put the addresses in the 'Save Address' Menu + // + BMenuItem *item; + while ((item = fSaveAddrMenu->RemoveItem(0L)) != NULL) + delete item; + + // create the list of addresses + + BList addressList; + get_address_list(addressList, fMail->To(), extract_address); + get_address_list(addressList, fMail->CC(), extract_address); + get_address_list(addressList, fMail->From(), extract_address); + get_address_list(addressList, fMail->ReplyTo(), extract_address); + + BMessage *msg; + + for (int32 i = addressList.CountItems(); i-- > 0;) { + char *address = (char *)addressList.RemoveItem(0L); + + // insert the new address in alphabetical order + int32 index = 0; + while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) { + if (!strcmp(address, item->Label())) { + // item already in list + goto skip; + } + + if (strcmp(address, item->Label()) < 0) + break; + + index++; + } + + msg = new BMessage(M_SAVE); + msg->AddString("address", address); + fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index); + + skip: + free(address); + } + + // + // Clear out existing contents of text view. + // + fContentView->fTextView->SetText("", (int32)0); + + fContentView->fTextView->LoadMessage(fMail, false, NULL); + } + + return B_OK; +} + + +TMailWindow * +TMailWindow::FrontmostWindow() +{ + BAutolock locker(sWindowListLock); + if (sWindowList.CountItems() > 0) + return (TMailWindow *)sWindowList.ItemAt(0); + + return NULL; +} + + +//==================================================================== +// #pragma mark - + + +TMenu::TMenu(const char *name, const char *attribute, int32 message, bool popup, bool addRandom) + : BPopUpMenu(name, false, false), + fPopup(popup), + fAddRandom(addRandom), + fMessage(message) +{ + fAttribute = (char *)malloc(strlen(attribute) + 1); + strcpy(fAttribute, attribute); + fPredicate = (char *)malloc(strlen(fAttribute) + 5); + sprintf(fPredicate, "%s = *", fAttribute); + + BuildMenu(); +} + + +TMenu::~TMenu() +{ + free(fAttribute); + free(fPredicate); +} + + +void +TMenu::AttachedToWindow() +{ + BuildMenu(); + BPopUpMenu::AttachedToWindow(); +} + + +BPoint +TMenu::ScreenLocation(void) +{ + if (fPopup) + return BPopUpMenu::ScreenLocation(); + + return BMenu::ScreenLocation(); +} + + +void +TMenu::BuildMenu() +{ + BMenuItem *item; + while ((item = RemoveItem((int32)0)) != NULL) + delete item; + + BVolume volume; + BVolumeRoster().GetBootVolume(&volume); + + BQuery query; + query.SetVolume(&volume); + query.SetPredicate(fPredicate); + query.Fetch(); + + int32 index = 0; + BEntry entry; + while (query.GetNextEntry(&entry) == B_NO_ERROR) + { + BFile file(&entry, O_RDONLY); + if (file.InitCheck() == B_NO_ERROR) + { + BMessage *msg = new BMessage(fMessage); + + entry_ref ref; + entry.GetRef(&ref); + msg->AddRef("ref", &ref); + + char name[B_FILE_NAME_LENGTH]; + file.ReadAttr(fAttribute, B_STRING_TYPE, 0, name, sizeof(name)); + + if (index < 9 && !fPopup) + AddItem(new BMenuItem(name, msg, '1' + index)); + else + AddItem(new BMenuItem(name, msg)); + index++; + } + } + if (fAddRandom && CountItems()) + { + AddItem(new BSeparatorItem(), 0); + //AddSeparatorItem(); + BMessage *msg = new BMessage(M_RANDOM_SIG); + if (!fPopup) + AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Random","R) 自動決定"), msg, '0'), 0); + else + AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Random","R) 自動決定"), msg), 0); + } +} + diff --git a/src/apps/bemail/Mail.h b/src/apps/bemail/Mail.h new file mode 100644 index 0000000000..fa44e53bec --- /dev/null +++ b/src/apps/bemail/Mail.h @@ -0,0 +1,350 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Mail.h +// +//-------------------------------------------------------------------- + +#ifndef _MAIL_H +#define _MAIL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MAX_DICTIONARIES 8 +#define TITLE_BAR_HEIGHT 25 +#define WIND_WIDTH 457 +#define WIND_HEIGHT 400 +#define RIGHT_BOUNDARY 8191 +#define SEPARATOR_MARGIN 7 +#define VIEW_COLOR 216 +#define FONT_SIZE 11.0 +#define QUOTE "> " + +enum MESSAGES { + REFS_RECEIVED = 64, + LIST_INVOKED, + WINDOW_CLOSED, + CHANGE_FONT, + RESET_BUTTONS, + PREFS_CHANGED, + CHARSET_CHOICE_MADE +}; + +enum TEXT { + SUBJECT_FIELD = REFS_RECEIVED + 64, + TO_FIELD, + ENCLOSE_FIELD, + CC_FIELD, + BCC_FIELD, + NAME_FIELD +}; + +enum MENUS { + /* app */ + M_NEW = SUBJECT_FIELD + 64, + M_PREFS, + M_EDIT_SIGNATURE, + M_FONT, + M_STYLE, + M_SIZE, + M_BEGINNER, + M_EXPERT, + /* file */ + M_REPLY, + M_REPLY_TO_SENDER, + M_REPLY_ALL, + M_FORWARD, + M_FORWARD_WITHOUT_ATTACHMENTS, + M_RESEND, + M_COPY_TO_NEW, + M_HEADER, + M_RAW, + M_SEND_NOW, + M_SAVE_AS_DRAFT, + M_SAVE, + M_PRINT_SETUP, + M_PRINT, + M_DELETE, + M_DELETE_PREV, + M_DELETE_NEXT, + M_CLOSE_READ, + M_CLOSE_SAVED, + M_CLOSE_SAME, + M_CLOSE_CUSTOM, + M_STATUS, + M_OPEN_MAIL_BOX, + M_OPEN_MAIL_FOLDER, + /* edit */ + M_SELECT, + M_QUOTE, + M_REMOVE_QUOTE, + M_CHECK_SPELLING, + M_SIGNATURE, + M_RANDOM_SIG, + M_SIG_MENU, + M_FIND, + M_FIND_AGAIN, + /* encls */ + M_ADD, + M_REMOVE, + M_OPEN, + M_COPY, + /* nav */ + M_NEXTMSG, + M_PREVMSG, + M_SAVE_POSITION, + /* Spam GUI button and menu items. Order is important. */ + M_SPAM_BUTTON, + M_TRAIN_SPAM_AND_DELETE, + M_TRAIN_SPAM, + M_UNTRAIN, + M_TRAIN_GENUINE, + + M_REDO +}; + +enum USER_LEVEL { + L_BEGINNER = 0, + L_EXPERT +}; + +enum WINDOW_TYPES { + MAIL_WINDOW = 0, + PREFS_WINDOW, + SIG_WINDOW +}; + +class TMailWindow; +class THeaderView; +class TEnclosuresView; +class TContentView; +class TMenu; +class TPrefsWindow; +class TSignatureWindow; +class BMenuItem; +class BmapButton; + +class BFile; +class BFilePanel; +class ButtonBar; +class BMenuBar; +class Words; +class BEmailMessage; + +//==================================================================== + +class TMailApp : public BApplication { + public: + TMailApp(); + ~TMailApp(); + + virtual void AboutRequested(); + virtual void ArgvReceived(int32, char **); + virtual void MessageReceived(BMessage *); + virtual bool QuitRequested(); + virtual void ReadyToRun(); + virtual void RefsReceived(BMessage *); + + TMailWindow *FindWindow(const entry_ref &); + void FontChange(); + TMailWindow *NewWindow(const entry_ref *rec = NULL, const char *to = NULL, + bool resend = false, BMessenger *messenger = NULL); + + BFont fFont; + + private: + void ClearPrintSettings(); + void CheckForSpamFilterExistence(); + void LoadSavePrefs (bool loadThem); + + BList fWindowList; + int32 fWindowCount; + TPrefsWindow *fPrefsWindow; + TSignatureWindow *fSigWindow; + //BMessenger fTrackerMessenger; + // Talks to tracker window that this was launched from. + + bool fPrevBBPref; +}; + +//-------------------------------------------------------------------- + +class BMailMessage; + +class TMailWindow : public BWindow { + public: + TMailWindow(BRect, const char *, const entry_ref *, const char *, + const BFont *font, bool, BMessenger *trackerMessenger); + virtual ~TMailWindow(); + + virtual void FrameResized(float width, float height); + virtual void MenusBeginning(); + virtual void MessageReceived(BMessage*); + virtual bool QuitRequested(); + virtual void Show(); + virtual void Zoom(BPoint, float, float); + virtual void WindowActivated(bool state); + + void SetTo(const char *mailTo, const char *subject, const char *ccTo = NULL, + const char *bccTo = NULL, const BString *body = NULL, BMessage *enclosures = NULL); + void AddSignature(BMailMessage *); + void Forward(entry_ref *, TMailWindow *, bool includeAttachments); + void Print(); + void PrintSetup(); + void Reply(entry_ref *, TMailWindow *, uint32); + void CopyMessage(entry_ref *ref, TMailWindow *src); + status_t Send(bool); + status_t SaveAsDraft( void ); + status_t OpenMessage(entry_ref *ref, uint32 characterSetForDecoding = B_MAIL_NULL_CONVERSION); + + status_t GetMailNodeRef(node_ref &nodeRef) const; + BEmailMessage *Mail() const { return fMail; } + + bool GetTrackerWindowFile(entry_ref *, bool dir) const; + void SaveTrackerPosition(entry_ref *); + void SetOriginatingWindow(BWindow *window); + + void SetCurrentMessageRead(); + void SetTrackerSelectionToCurrent(); + TMailWindow* FrontmostWindow(); + void UpdateViews(); + + protected: + void SetTitleForMessage(); + void AddEnclosure(BMessage *msg); + void BuildButtonBar(); + status_t TrainMessageAs (const char *CommandWord); + + private: + BEmailMessage *fMail; + entry_ref *fRef; // Reference to currently displayed file + int32 fFieldState; + BFilePanel *fPanel; + BMenuBar *fMenuBar; + BMenuItem *fAdd; + BMenuItem *fCut; + BMenuItem *fCopy; + BMenuItem *fHeader; + BMenuItem *fPaste; + BMenuItem *fPrint; + BMenuItem *fPrintSetup; + BMenuItem *fQuote; + BMenuItem *fRaw; + BMenuItem *fRemove; + BMenuItem *fRemoveQuote; + BMenuItem *fSendNow; + BMenuItem *fSendLater; + BMenuItem *fUndo; + BMenuItem *fRedo; + BMenuItem *fNextMsg; + BMenuItem *fPrevMsg; + BMenuItem *fDeleteNext; + BMenuItem *fSpelling; + BMenu *fSaveAddrMenu; + ButtonBar *fButtonBar; + BmapButton *fSendButton; + BmapButton *fSaveButton; + BmapButton *fPrintButton; + BmapButton *fSigButton; + BRect fZoom; + TContentView *fContentView; + THeaderView *fHeaderView; + TEnclosuresView *fEnclosuresView; + TMenu *fSignature; + BMessenger fTrackerMessenger; + // Talks to tracker window that this was launched from. + + entry_ref fPrevRef, fNextRef; + bool fPrevTrackerPositionSaved : 1; + bool fNextTrackerPositionSaved : 1; + + static BList sWindowList; + static BLocker sWindowListLock; + + bool fSigAdded; + bool fIncoming; + bool fReplying; + bool fResending; + bool fSent; + bool fDraft; + bool fChanged; + + char *fStartingText; + entry_ref fRepliedMail; + BMessenger *fOriginatingWindow; +}; + +//==================================================================== + +class TMenu: public BPopUpMenu +{ + public: + TMenu(const char *, const char *, int32, bool popup = false, bool addRandom = true); + ~TMenu(); + + virtual BPoint ScreenLocation(void); + virtual void AttachedToWindow(); + + void BuildMenu(); + + private: + char *fAttribute; + char *fPredicate; + bool fPopup, fAddRandom; + int32 fMessage; +}; + +//==================================================================== + +int32 header_len(BFile *); +extern Words *gWords[MAX_DICTIONARIES]; +extern Words *gExactWords[MAX_DICTIONARIES]; +extern int32 gUserDict; +extern BFile *gUserDictFile; +extern int32 gDictCount; + +#endif // #ifndef _MAIL_H diff --git a/src/apps/bemail/ObjectList.h b/src/apps/bemail/ObjectList.h new file mode 100644 index 0000000000..5e3b604fdc --- /dev/null +++ b/src/apps/bemail/ObjectList.h @@ -0,0 +1,792 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +/**************************************************************************** +** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ** +** ** +** DANGER, WILL ROBINSON! ** +** ** +** The interfaces contained here are part of BeOS's ** +** ** +** >> PRIVATE NOT FOR PUBLIC USE << ** +** ** +** implementation. ** +** ** +** These interfaces WILL CHANGE in future releases. ** +** If you use them, your app WILL BREAK at some future time. ** +** ** +** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ** +****************************************************************************/ + +// +// ObjectList is a wrapper around BList that adds type safety, +// optional object ownership, search, insert operations, etc. +// + +#ifndef __OBJECT_LIST__ +#define __OBJECT_LIST__ + +#ifndef _BE_H +#include +#endif + +#include + +template class BObjectList; + +template +struct UnaryPredicate { + virtual int operator()(const T *) const + // virtual could be avoided here if FindBinaryInsertionIndex, + // etc. were member template functions + { return 0; } + +private: + static int _unary_predicate_glue(const void *item, void *context); + +friend class BObjectList; +}; + +template +int +UnaryPredicate::_unary_predicate_glue(const void *item, void *context) +{ + return ((UnaryPredicate *)context)->operator()((const T *)item); +} + + +class _PointerList_ : public BList { +public: + _PointerList_(const _PointerList_ &list); + _PointerList_(int32 itemsPerBlock = 20, bool owning = false); + ~_PointerList_(); + + typedef void *(* GenericEachFunction)(void *, void *); + typedef int (* GenericCompareFunction)(const void *, const void *); + typedef int (* GenericCompareFunctionWithState)(const void *, const void *, + void *); + typedef int (* UnaryPredicateGlue)(const void *, void *); + + void *EachElement(GenericEachFunction, void *); + void SortItems(GenericCompareFunction); + void SortItems(GenericCompareFunctionWithState, void *state); + void HSortItems(GenericCompareFunction); + void HSortItems(GenericCompareFunctionWithState, void *state); + + void *BinarySearch(const void *, GenericCompareFunction) const; + void *BinarySearch(const void *, GenericCompareFunctionWithState, void *state) const; + + int32 BinarySearchIndex(const void *, GenericCompareFunction) const; + int32 BinarySearchIndex(const void *, GenericCompareFunctionWithState, void *state) const; + int32 BinarySearchIndexByPredicate(const void *, UnaryPredicateGlue) const; + + bool Owning() const; + bool ReplaceItem(int32, void *); + +protected: + bool owning; +}; + +template +class BObjectList : private _PointerList_ { +public: + // iteration and sorting + typedef T *(* EachFunction)(T *, void *); + typedef const T *(* ConstEachFunction)(const T *, void *); + typedef int (* CompareFunction)(const T *, const T *); + typedef int (* CompareFunctionWithState)(const T *, const T *, void *state); + + BObjectList(int32 itemsPerBlock = 20, bool owning = false); + BObjectList(const BObjectList &list); + // clones list; if list is owning, makes copies of all + // the items + + virtual ~BObjectList(); + + BObjectList &operator=(const BObjectList &list); + // clones list; if list is owning, makes copies of all + // the items + + // adding and removing + // ToDo: + // change Add calls to return const item + bool AddItem(T *); + bool AddItem(T *, int32); + bool AddList(BObjectList *); + bool AddList(BObjectList *, int32); + + bool RemoveItem(T *, bool deleteIfOwning = true); + // if owning, deletes the removed item + T *RemoveItemAt(int32); + // returns the removed item + + void MakeEmpty(); + + // item access + T *ItemAt(int32) const; + + bool ReplaceItem(int32 index, T *); + // if list is owning, deletes the item at first + T *SwapWithItem(int32 index, T *newItem); + // same as ReplaceItem, except does not delete old item at , + // returns it instead + + T *FirstItem() const; + T *LastItem() const; + + // misc. getters + int32 IndexOf(const T *) const; + bool HasItem(const T *) const; + bool IsEmpty() const; + int32 CountItems() const; + + T *EachElement(EachFunction, void *); + const T *EachElement(ConstEachFunction, void *) const; + + void SortItems(CompareFunction); + void SortItems(CompareFunctionWithState, void *state); + void HSortItems(CompareFunction); + void HSortItems(CompareFunctionWithState, void *state); + + // linear search, returns first item that matches predicate + const T *FindIf(const UnaryPredicate &) const; + T *FindIf(const UnaryPredicate &); + + // list must be sorted with CompareFunction for these to work + const T *BinarySearch(const T &, CompareFunction) const; + const T *BinarySearch(const T &, CompareFunctionWithState, void *state) const; + + // Binary insertion - list must be sorted with CompareFunction for + // these to work + + // simple insert + void BinaryInsert(T *, CompareFunction); + void BinaryInsert(T *, CompareFunctionWithState, void *state); + void BinaryInsert(T *, const UnaryPredicate &); + + // unique insert, returns false if item already in list + bool BinaryInsertUnique(T *, CompareFunction); + bool BinaryInsertUnique(T *, CompareFunctionWithState, void *state); + bool BinaryInsertUnique(T *, const UnaryPredicate &); + + // insert a copy of the item, returns new inserted item + T *BinaryInsertCopy(const T ©This, CompareFunction); + T *BinaryInsertCopy(const T ©This, CompareFunctionWithState, void *state); + + // insert a copy of the item if not in list already + // returns new inserted item or existing item in case of a conflict + T *BinaryInsertCopyUnique(const T ©This, CompareFunction); + T *BinaryInsertCopyUnique(const T ©This, CompareFunctionWithState, void *state); + + int32 FindBinaryInsertionIndex(const UnaryPredicate &, bool *alreadyInList = 0) const; + // returns either the index into which a new item should be inserted + // or index of an existing item that matches the predicate + + // deprecated API, will go away + BList *AsBList() + { return this; } + const BList *AsBList() const + { return this; } + +private: + void SetItem(int32, T *); +}; + +template +Result +WhileEachListItem(BObjectList *list, Result (Item::*func)(Param1), Param1 p1) +{ + Result result = 0; + int32 count = list->CountItems(); + + for (int32 index = 0; index < count; index++) + if ((result = (list->ItemAt(index)->*func)(p1)) != 0) + break; + + return result; +} + +template +Result +WhileEachListItem(BObjectList *list, Result (*func)(Item *, Param1), Param1 p1) +{ + Result result = 0; + int32 count = list->CountItems(); + + for (int32 index = 0; index < count; index++) + if ((result = (*func)(list->ItemAt(index), p1)) != 0) + break; + + return result; +} + +template +Result +WhileEachListItem(BObjectList *list, Result (Item::*func)(Param1, Param2), + Param1 p1, Param2 p2) +{ + Result result = 0; + int32 count = list->CountItems(); + + for (int32 index = 0; index < count; index++) + if ((result = (list->ItemAt(index)->*func)(p1, p2)) != 0) + break; + + return result; +} + +template +Result +WhileEachListItem(BObjectList *list, Result (*func)(Item *, Param1, Param2), + Param1 p1, Param2 p2) +{ + Result result = 0; + int32 count = list->CountItems(); + + for (int32 index = 0; index < count; index++) + if ((result = (*func)(list->ItemAt(index), p1, p2)) != 0) + break; + + return result; +} + +template +Result +WhileEachListItem(BObjectList *list, Result (*func)(Item *, Param1, Param2, + Param3, Param4), Param1 p1, Param2 p2, Param3 p3, Param4 p4) +{ + Result result = 0; + int32 count = list->CountItems(); + + for (int32 index = 0; index < count; index++) + if ((result = (*func)(list->ItemAt(index), p1, p2, p3, p4)) != 0) + break; + + return result; +} + +template +void +EachListItemIgnoreResult(BObjectList *list, Result (Item::*func)()) +{ + int32 count = list->CountItems(); + for (int32 index = 0; index < count; index++) + (list->ItemAt(index)->*func)(); +} + +template +void +EachListItem(BObjectList *list, void (*func)(Item *, Param1), Param1 p1) +{ + int32 count = list->CountItems(); + for (int32 index = 0; index < count; index++) + (func)(list->ItemAt(index), p1); +} + +template +void +EachListItem(BObjectList *list, void (Item::*func)(Param1, Param2), + Param1 p1, Param2 p2) +{ + int32 count = list->CountItems(); + for (int32 index = 0; index < count; index++) + (list->ItemAt(index)->*func)(p1, p2); +} + +template +void +EachListItem(BObjectList *list, void (*func)(Item *,Param1, Param2), + Param1 p1, Param2 p2) +{ + int32 count = list->CountItems(); + for (int32 index = 0; index < count; index++) + (func)(list->ItemAt(index), p1, p2); +} + +template +void +EachListItem(BObjectList *list, void (*func)(Item *,Param1, Param2, + Param3), Param1 p1, Param2 p2, Param3 p3) +{ + int32 count = list->CountItems(); + for (int32 index = 0; index < count; index++) + (func)(list->ItemAt(index), p1, p2, p3); +} + + +template +void +EachListItem(BObjectList *list, void (*func)(Item *,Param1, Param2, + Param3, Param4), Param1 p1, Param2 p2, Param3 p3, Param4 p4) +{ + int32 count = list->CountItems(); + for (int32 index = 0; index < count; index++) + (func)(list->ItemAt(index), p1, p2, p3, p4); +} + +// inline code + +inline bool +_PointerList_::Owning() const +{ + return owning; +} + +template +BObjectList::BObjectList(int32 itemsPerBlock, bool owning) + : _PointerList_(itemsPerBlock, owning) +{ +} + +template +BObjectList::BObjectList(const BObjectList &list) + : _PointerList_(list) +{ + owning = list.owning; + if (owning) { + // make our own copies in an owning list + int32 count = list.CountItems(); + for (int32 index = 0; index < count; index++) { + T *item = list.ItemAt(index); + if (item) + item = new T(*item); + SetItem(index, item); + } + } +} + +template +BObjectList::~BObjectList() +{ + if (Owning()) + // have to nuke elements first + MakeEmpty(); + +} + +template +BObjectList & +BObjectList::operator=(const BObjectList &list) +{ + owning = list.owning; + BObjectList &result = (BObjectList &)_PointerList_::operator=(list); + if (owning) { + // make our own copies in an owning list + int32 count = list.CountItems(); + for (int32 index = 0; index < count; index++) { + T *item = list.ItemAt(index); + if (item) + item = new T(*item); + SetItem(index, item); + } + } + return result; +} + +template +bool +BObjectList::AddItem(T *item) +{ + // need to cast to void * to make T work for const pointers + return _PointerList_::AddItem((void *)item); +} + +template +bool +BObjectList::AddItem(T *item, int32 atIndex) +{ + return _PointerList_::AddItem((void *)item, atIndex); +} + +template +bool +BObjectList::AddList(BObjectList *newItems) +{ + return _PointerList_::AddList(newItems); +} + +template +bool +BObjectList::AddList(BObjectList *newItems, int32 atIndex) +{ + return _PointerList_::AddList(newItems, atIndex); +} + + +template +bool +BObjectList::RemoveItem(T *item, bool deleteIfOwning) +{ + bool result = _PointerList_::RemoveItem((void *)item); + + if (result && Owning() && deleteIfOwning) + delete item; + + return result; +} + +template +T * +BObjectList::RemoveItemAt(int32 index) +{ + return (T *)_PointerList_::RemoveItem(index); +} + +template +inline T * +BObjectList::ItemAt(int32 index) const +{ + return (T *)_PointerList_::ItemAt(index); +} + +template +bool +BObjectList::ReplaceItem(int32 index, T *item) +{ + if (owning) + delete ItemAt(index); + return _PointerList_::ReplaceItem(index, (void *)item); +} + +template +T * +BObjectList::SwapWithItem(int32 index, T *newItem) +{ + T *result = ItemAt(index); + _PointerList_::ReplaceItem(index, (void *)newItem); + return result; +} + +template +void +BObjectList::SetItem(int32 index, T *newItem) +{ + _PointerList_::ReplaceItem(index, (void *)newItem); +} + +template +int32 +BObjectList::IndexOf(const T *item) const +{ + return _PointerList_::IndexOf((void *)item); +} + +template +T * +BObjectList::FirstItem() const +{ + return (T *)_PointerList_::FirstItem(); +} + +template +T * +BObjectList::LastItem() const +{ + return (T *)_PointerList_::LastItem(); +} + +template +bool +BObjectList::HasItem(const T *item) const +{ + return _PointerList_::HasItem((void *)item); +} + +template +bool +BObjectList::IsEmpty() const +{ + return _PointerList_::IsEmpty(); +} + +template +int32 +BObjectList::CountItems() const +{ + return _PointerList_::CountItems(); +} + +template +void +BObjectList::MakeEmpty() +{ + if (owning) { + int32 count = CountItems(); + for (int32 index = 0; index < count; index++) + delete ItemAt(index); + } + _PointerList_::MakeEmpty(); +} + +template +T * +BObjectList::EachElement(EachFunction func, void *params) +{ + return (T *)_PointerList_::EachElement((GenericEachFunction)func, params); +} + + +template +const T * +BObjectList::EachElement(ConstEachFunction func, void *params) const +{ + return (const T *) + const_cast *>(this)->_PointerList_::EachElement( + (GenericEachFunction)func, params); +} + +template +const T * +BObjectList::FindIf(const UnaryPredicate &predicate) const +{ + int32 count = CountItems(); + for (int32 index = 0; index < count; index++) + if (predicate.operator()(ItemAt(index)) == 0) + return ItemAt(index); + return 0; +} + +template +T * +BObjectList::FindIf(const UnaryPredicate &predicate) +{ + int32 count = CountItems(); + for (int32 index = 0; index < count; index++) + if (predicate.operator()(ItemAt(index)) == 0) + return ItemAt(index); + return 0; +} + + +template +void +BObjectList::SortItems(CompareFunction function) +{ + _PointerList_::SortItems((GenericCompareFunction)function); +} + +template +void +BObjectList::SortItems(CompareFunctionWithState function, void *state) +{ + _PointerList_::SortItems((GenericCompareFunctionWithState)function, state); +} + +template +void +BObjectList::HSortItems(CompareFunction function) +{ + _PointerList_::HSortItems((GenericCompareFunction)function); +} + +template +void +BObjectList::HSortItems(CompareFunctionWithState function, void *state) +{ + _PointerList_::HSortItems((GenericCompareFunctionWithState)function, state); +} + +template +const T * +BObjectList::BinarySearch(const T &key, CompareFunction func) const +{ + return (const T *)_PointerList_::BinarySearch(&key, + (GenericCompareFunction)func); +} + +template +const T * +BObjectList::BinarySearch(const T &key, CompareFunctionWithState func, void *state) const +{ + return (const T *)_PointerList_::BinarySearch(&key, + (GenericCompareFunctionWithState)func, state); +} + +template +void +BObjectList::BinaryInsert(T *item, CompareFunction func) +{ + int32 index = _PointerList_::BinarySearchIndex(item, + (GenericCompareFunction)func); + if (index >= 0) + // already in list, add after existing + AddItem(item, index + 1); + else + AddItem(item, -index - 1); +} + +template +void +BObjectList::BinaryInsert(T *item, CompareFunctionWithState func, void *state) +{ + int32 index = _PointerList_::BinarySearchIndex(item, + (GenericCompareFunctionWithState)func, state); + if (index >= 0) + // already in list, add after existing + AddItem(item, index + 1); + else + AddItem(item, -index - 1); +} + +template +bool +BObjectList::BinaryInsertUnique(T *, CompareFunction func) +{ + int32 index = _PointerList_::BinarySearchIndex(item, + (GenericCompareFunction)func); + if (index >= 0) + return false; + + AddItem(item, -index - 1); + return true; +} + +template +bool +BObjectList::BinaryInsertUnique(T *, CompareFunctionWithState func, void *state) +{ + int32 index = _PointerList_::BinarySearchIndex(item, + (GenericCompareFunctionWithState)func, state); + if (index >= 0) + return false; + + AddItem(item, -index - 1); + return true; +} + + +template +T * +BObjectList::BinaryInsertCopy(const T ©This, CompareFunction func) +{ + int32 index = _PointerList_::BinarySearchIndex(©This, + (GenericCompareFunction)func); + + if (index >= 0) + index++; + else + index = -index - 1; + + T *newItem = new T(copyThis); + AddItem(newItem, index); + return newItem; +} + +template +T * +BObjectList::BinaryInsertCopy(const T ©This, CompareFunctionWithState func, void *state) +{ + int32 index = _PointerList_::BinarySearchIndex(©This, + (GenericCompareFunctionWithState)func, state); + + if (index >= 0) + index++; + else + index = -index - 1; + + T *newItem = new T(copyThis); + AddItem(newItem, index); + return newItem; +} + +template +T * +BObjectList::BinaryInsertCopyUnique(const T ©This, CompareFunction func) +{ + int32 index = _PointerList_::BinarySearchIndex(©This, + (GenericCompareFunction)func); + if (index >= 0) + return ItemAt(index); + + index = -index - 1; + T *newItem = new T(copyThis); + AddItem(newItem, index); + return newItem; +} + +template +T * +BObjectList::BinaryInsertCopyUnique(const T ©This, CompareFunctionWithState func, + void *state) +{ + int32 index = _PointerList_::BinarySearchIndex(©This, + (GenericCompareFunctionWithState)func, state); + if (index >= 0) + return ItemAt(index); + + index = -index - 1; + T *newItem = new T(copyThis); + AddItem(newItem, index); + return newItem; +} + +template +int32 +BObjectList::FindBinaryInsertionIndex(const UnaryPredicate &pred, bool *alreadyInList) + const +{ + int32 index = _PointerList_::BinarySearchIndexByPredicate(&pred, + (UnaryPredicateGlue)&UnaryPredicate::_unary_predicate_glue); + + if (alreadyInList) + *alreadyInList = index >= 0; + + if (index < 0) + index = -index - 1; + + return index; +} + +template +void +BObjectList::BinaryInsert(T *item, const UnaryPredicate &pred) +{ + int32 index = FindBinaryInsertionIndex(pred); + AddItem(item, index); +} + +template +bool +BObjectList::BinaryInsertUnique(T *item, const UnaryPredicate &pred) +{ + bool alreadyInList; + int32 index = FindBinaryInsertionIndex(pred, &alreadyInList); + if (alreadyInList) + return false; + + AddItem(item, index); + return true; +} + +#endif // #ifndef __OBJECT_LIST__ diff --git a/src/apps/bemail/Prefs.cpp b/src/apps/bemail/Prefs.cpp new file mode 100644 index 0000000000..c24db6a13d --- /dev/null +++ b/src/apps/bemail/Prefs.cpp @@ -0,0 +1,966 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Prefs.cpp +// +//-------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Mail.h" +#include "Prefs.h" + +#define BUTTON_WIDTH 70 +#define BUTTON_HEIGHT 20 +#define ITEM_SPACE 6 + +#define FONT_TEXT MDR_DIALECT_CHOICE ("Font:", "フォント:") +#define SIZE_TEXT MDR_DIALECT_CHOICE ("Size:", "サイズ:") +#define LEVEL_TEXT MDR_DIALECT_CHOICE ("User Level:", "ユーザーレベル:") +#define WRAP_TEXT MDR_DIALECT_CHOICE ("Text Wrapping:", "テキスト・ラップ:") +#define ATTACH_ATTRIBUTES_TEXT MDR_DIALECT_CHOICE ("Attach Attributes:", "ファイル属性情報:") +#define QUOTES_TEXT MDR_DIALECT_CHOICE ("Colored Quotes:", "引用部分の着色:") +#define ACCOUNT_TEXT MDR_DIALECT_CHOICE ("Default Account:", "標準アカウント:") +#define REPLYTO_TEXT MDR_DIALECT_CHOICE ("Reply Account:", "返信用アカウント:") +#define REPLYTO_USE_DEFAULT_TEXT MDR_DIALECT_CHOICE ("Use Default Account", "標準アカウントを使う") +#define REPLYTO_FROM_MAIL_TEXT MDR_DIALECT_CHOICE ("Account From Mail", "メールのアカウントを使う") +#define REPLY_PREAMBLE_TEXT MDR_DIALECT_CHOICE ("Reply Preamble:", "返信へ追加:") +#define SIGNATURE_TEXT MDR_DIALECT_CHOICE ("Auto Signature:", "自動署名:") +#define ENCODING_TEXT MDR_DIALECT_CHOICE ("Encoding:", "エンコード形式:") +#define WARN_UNENCODABLE_TEXT MDR_DIALECT_CHOICE ("Warn Unencodable:", "警告: エンコードできません") +#define SPELL_CHECK_START_ON_TEXT MDR_DIALECT_CHOICE ("Initial Spell Check Mode:", "編集時スペルチェック:") + +#define BUTTONBAR_TEXT MDR_DIALECT_CHOICE ("Button Bar:", "ボタンバー:") + +#define OK_BUTTON_X1 (PREF_WIDTH - BUTTON_WIDTH - 6) +#define OK_BUTTON_X2 (OK_BUTTON_X1 + BUTTON_WIDTH) +#define OK_BUTTON_TEXT MDR_DIALECT_CHOICE ("Done", "設定") +#define CANCEL_BUTTON_TEXT MDR_DIALECT_CHOICE ("Cancel", "中止") + +#define REVERT_BUTTON_X1 8 +#define REVERT_BUTTON_X2 (REVERT_BUTTON_X1 + BUTTON_WIDTH) +#define REVERT_BUTTON_TEXT MDR_DIALECT_CHOICE ("Revert", "復元") + +enum P_MESSAGES {P_OK = 128, P_CANCEL, P_REVERT, P_FONT, + P_SIZE, P_LEVEL, P_WRAP, P_ATTACH_ATTRIBUTES, + P_SIG, P_ENC, P_WARN_UNENCODABLE, + P_SPELL_CHECK_START_ON, P_BUTTON_BAR, + P_ACCOUNT, P_REPLYTO, P_REPLY_PREAMBLE, + P_COLORED_QUOTES}; + +#define ICON_LABEL_TEXT MDR_DIALECT_CHOICE ("Show Icons & Labels", "アイコンとラベル") +#define ICON_TEXT MDR_DIALECT_CHOICE ("Show Icons Only", "アイコンのみ") +#define HIDE_TEXT MDR_DIALECT_CHOICE ("Hide", "隠す") + + +extern BPoint prefs_window; + +const EncodingItem kEncodings[] = +{ + // B_MS_WINDOWS is a superset of B_ISO1, MS mailers lie and send Windows + // chars as ISO-1 we still don't want to pretend we would use the Windows + // 1252 codetable; this should only be done at decoding stage, axeld. + // {"ISO-8859-1", B_MS_WINDOWS_CONVERSION}, + {"ISO-8859-1 (Latin-1)", B_ISO1_CONVERSION}, + {"ISO-8859-2", B_ISO2_CONVERSION}, + {"ISO-8859-3", B_ISO3_CONVERSION}, + {"ISO-8859-4", B_ISO4_CONVERSION}, + {"ISO-8859-5", B_ISO5_CONVERSION}, + {"ISO-8859-6", B_ISO6_CONVERSION}, + {"ISO-8859-7", B_ISO7_CONVERSION}, + {"ISO-8859-8", B_ISO8_CONVERSION}, + {"ISO-8859-9", B_ISO9_CONVERSION}, + {"ISO-8859-10", B_ISO10_CONVERSION}, + {"ISO-8859-13", B_ISO13_CONVERSION}, + {"ISO-8859-14", B_ISO14_CONVERSION}, + {"ISO-8859-15", B_ISO15_CONVERSION}, + {"SHIFT-JIS (obsolete)", B_SJIS_CONVERSION}, + {"ISO-2022-JP", B_JIS_CONVERSION}, + {"EUC-JP (obsolete)", B_EUC_CONVERSION}, + {"EUC-KR", B_EUC_KR_CONVERSION}, + {"KOI8-R", B_KOI8R_CONVERSION}, + {"Windows-1251",B_MS_WINDOWS_1251_CONVERSION}, + {"Windows-1252 (\"ANSI\")",B_MS_WINDOWS_CONVERSION}, + {"DOS-437 (common)", B_MS_DOS_CONVERSION}, + {"DOS-866 (rarer)", B_MS_DOS_866_CONVERSION}, + {"Macintosh Roman", B_MAC_ROMAN_CONVERSION}, + {"US-ASCII", B_MAIL_US_ASCII_CONVERSION}, + {"UTF-8 (BeOS)", B_MAIL_UTF8_CONVERSION}, + + {"Automatic", B_MAIL_NULL_CONVERSION /* marks end of list, only visible when decoding */} +}; + +#define ATTRIBUTE_ON_TEXT MDR_DIALECT_CHOICE ("Include BeOS Attributes in Attachments", "BeOSの属性を付ける") +#define ATTRIBUTE_OFF_TEXT MDR_DIALECT_CHOICE ("No BeOS Attributes, just Plain Data", "BeOSの属性を付けない(データのみ)") + +//==================================================================== + + +TPrefsWindow::TPrefsWindow(BRect rect, BFont *font, int32 *level, bool *wrap, + bool *attachAttributes, bool *cquotes, uint32 *account, int32 *replyTo, + char **preamble, char **sig, uint32 *encoding, bool *warnUnencodable, + bool *spellCheckStartOn, bool *buttonBar) + : BWindow(rect, MDR_DIALECT_CHOICE ("BeMail Preferences","BeMailの設定"), B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE) +{ + BMenuField *menu; + + fNewFont = font; fFont = *fNewFont; + fNewLevel = level; fLevel = *fNewLevel; + fNewWrap = wrap; fWrap = *fNewWrap; + fNewAttachAttributes = attachAttributes; fAttachAttributes = *fNewAttachAttributes; + fNewColoredQuotes = cquotes; fColoredQuotes = *fNewColoredQuotes; + fNewAccount = account; fAccount = *fNewAccount; + fNewReplyTo = replyTo; fReplyTo = *fNewReplyTo; + fNewEncoding = encoding; fEncoding = *fNewEncoding; + fNewWarnUnencodable = warnUnencodable; fWarnUnencodable = *fNewWarnUnencodable; + fNewSpellCheckStartOn = spellCheckStartOn; fSpellCheckStartOn = *fNewSpellCheckStartOn; + fNewButtonBar = buttonBar; fButtonBar = *fNewButtonBar; + + fNewPreamble = preamble; + + fNewSignature = sig; + fSignature = (char *)malloc(strlen(*fNewSignature) + 1); + strcpy(fSignature, *fNewSignature); + + BRect r = Bounds(); + BView *view = new BView(r, NULL, B_FOLLOW_ALL, B_FRAME_EVENTS); + view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + AddChild(view); + + // determine font height + font_height fontHeight; + view->GetFontHeight(&fontHeight); + int32 height = (int32)(fontHeight.ascent + fontHeight.descent + fontHeight.leading) + 6; + int32 labelWidth = (int32)view->StringWidth(SPELL_CHECK_START_ON_TEXT) + SEPARATOR_MARGIN; + + // group boxes + + r.Set(8,4,Bounds().right - 8,4 + 7 * (height + ITEM_SPACE)); + BBox *interfaceBox = new BBox(r,NULL,B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); + interfaceBox->SetLabel(MDR_DIALECT_CHOICE ("User Interface","ユーザーインターフェース")); + view->AddChild(interfaceBox); + + r.top = r.bottom + 8; r.bottom = r.top + 9 * (height + ITEM_SPACE); + BBox *mailBox = new BBox(r,NULL,B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); + mailBox->SetLabel(MDR_DIALECT_CHOICE ("Mailing","メール関係")); + view->AddChild(mailBox); + + // revert, ok & cancel + + r.top = r.bottom + 10; r.bottom = r.top + height; + r.left = OK_BUTTON_X1; r.right = OK_BUTTON_X2; + BButton *button = new BButton(r, "ok", OK_BUTTON_TEXT, new BMessage(P_OK)); + button->MakeDefault(true); + view->AddChild(button); + + r.OffsetBy(-(OK_BUTTON_X2 - OK_BUTTON_X1 + 10), 0); + button = new BButton(r, "cancel", CANCEL_BUTTON_TEXT, new BMessage(P_CANCEL)); + view->AddChild(button); + + r.left = REVERT_BUTTON_X1; r.right = REVERT_BUTTON_X2; + fRevert = new BButton(r, "revert", REVERT_BUTTON_TEXT, new BMessage(P_REVERT)); + fRevert->SetEnabled(false); + view->AddChild(fRevert); + + // User Interface + + r = interfaceBox->Bounds(); + r.left += 8; r.right -= 8; r.top = height; r.bottom = r.top + height - 3; + fButtonBarMenu = BuildButtonBarMenu(*buttonBar); + menu = new BMenuField(r, "bar", BUTTONBAR_TEXT, fButtonBarMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + interfaceBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fFontMenu = BuildFontMenu(font); + menu = new BMenuField(r, "font", FONT_TEXT, fFontMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + interfaceBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fSizeMenu = BuildSizeMenu(font); + menu = new BMenuField(r, "size", SIZE_TEXT, fSizeMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + interfaceBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fColoredQuotesMenu = BuildColoredQuotesMenu(fColoredQuotes); + menu = new BMenuField(r, "cquotes", QUOTES_TEXT, fColoredQuotesMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + interfaceBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fSpellCheckStartOnMenu = BuildSpellCheckStartOnMenu(fSpellCheckStartOn); + menu = new BMenuField(r, "spellCheckStartOn", SPELL_CHECK_START_ON_TEXT, + fSpellCheckStartOnMenu, B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + interfaceBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fLevelMenu = BuildLevelMenu(*level); + menu = new BMenuField(r, "level", LEVEL_TEXT, fLevelMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + interfaceBox->AddChild(menu); + + // Mail Accounts + + r = mailBox->Bounds(); + r.left += 8; r.right -= 8; r.top = height; r.bottom = r.top + height - 3; + fAccountMenu = BuildAccountMenu(fAccount); + menu = new BMenuField(r, "account", ACCOUNT_TEXT, fAccountMenu, B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + mailBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fReplyToMenu = BuildReplyToMenu(fReplyTo); + menu = new BMenuField(r, "replyTo", REPLYTO_TEXT, fReplyToMenu, B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + mailBox->AddChild(menu); + + // Mail Contents + + r.OffsetBy(0, height + ITEM_SPACE); r.right -= 25; + fReplyPreamble = new BTextControl(r, "replytext", REPLY_PREAMBLE_TEXT, *preamble, + new BMessage(P_REPLY_PREAMBLE), B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE); + fReplyPreamble->SetDivider(labelWidth); + fReplyPreamble->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); + mailBox->AddChild(fReplyPreamble); + + BRect popRect = r; + popRect.left = r.right + 6; r.right += 25; popRect.right = r.right; + fReplyPreambleMenu = BuildReplyPreambleMenu(); + menu = new BMenuField(popRect, "replyPreamble", B_EMPTY_STRING, fReplyPreambleMenu, + B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(0); + mailBox->AddChild(menu); + + r.OffsetBy(0, height + ITEM_SPACE); + fSignatureMenu = BuildSignatureMenu(*sig); + menu = new BMenuField(r, "sig", SIGNATURE_TEXT, fSignatureMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + mailBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fEncodingMenu = BuildEncodingMenu(fEncoding); + menu = new BMenuField(r, "enc", ENCODING_TEXT, fEncodingMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + mailBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fWarnUnencodableMenu = BuildWarnUnencodableMenu(fWarnUnencodable); + menu = new BMenuField(r, "warnUnencodable", WARN_UNENCODABLE_TEXT, + fWarnUnencodableMenu, B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + mailBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fWrapMenu = BuildWrapMenu(*wrap); + menu = new BMenuField(r, "wrap", WRAP_TEXT, fWrapMenu,B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + mailBox->AddChild(menu); + + r.OffsetBy(0,height + ITEM_SPACE); + fAttachAttributesMenu = BuildAttachAttributesMenu(*attachAttributes); + menu = new BMenuField(r, "attachAttributes", ATTACH_ATTRIBUTES_TEXT, fAttachAttributesMenu, B_FOLLOW_ALL, + B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + menu->SetDivider(labelWidth); + menu->SetAlignment(B_ALIGN_RIGHT); + mailBox->AddChild(menu); + + ResizeTo(Frame().Width(), fRevert->Frame().bottom + 8); + Show(); +} + + +TPrefsWindow::~TPrefsWindow() +{ + BMessage msg(WINDOW_CLOSED); + + prefs_window = Frame().LeftTop(); + + msg.AddInt32("kind", PREFS_WINDOW); + be_app->PostMessage(&msg); +} + + +void +TPrefsWindow::MessageReceived(BMessage *msg) +{ + bool changed; + bool revert = true; + const char *family; + const char *signature; + const char *style; + char label[256]; + int32 new_size; + int32 old_size; + font_family new_family; + font_family old_family; + font_style new_style; + font_style old_style; + BMenuItem *item; + BMessage message; + + switch (msg->what) + { + case P_OK: + if (strcmp(fReplyPreamble->Text(), *fNewPreamble)) + { + free(*fNewPreamble); + *fNewPreamble = (char *)malloc(strlen(fReplyPreamble->Text()) + 1); + strcpy(*fNewPreamble, fReplyPreamble->Text()); + } + be_app->PostMessage(PREFS_CHANGED); + Quit(); + break; + + case P_CANCEL: + revert = false; + // supposed to fall through + case P_REVERT: + fFont.GetFamilyAndStyle(&old_family, &old_style); + fNewFont->GetFamilyAndStyle(&new_family, &new_style); + old_size = (int32) fFont.Size(); + new_size = (int32) fNewFont->Size(); + if (strcmp(old_family, new_family) || strcmp(old_style, new_style) + || old_size != new_size) + { + fNewFont->SetFamilyAndStyle(old_family, old_style); + if (revert) + { + sprintf(label, "%s %s", old_family, old_style); + item = fFontMenu->FindItem(label); + item->SetMarked(true); + } + + fNewFont->SetSize(old_size); + if (revert) + { + sprintf(label, "%ld", old_size); + item = fSizeMenu->FindItem(label); + item->SetMarked(true); + } + message.what = M_FONT; + be_app->PostMessage(&message); + } + *fNewLevel = fLevel; + *fNewWrap = fWrap; + *fNewAttachAttributes = fAttachAttributes; + + if (strcmp(fSignature, *fNewSignature)) + { + free(*fNewSignature); + *fNewSignature = (char *)malloc(strlen(fSignature) + 1); + strcpy(*fNewSignature, fSignature); + } + + *fNewEncoding = fEncoding; + *fNewWarnUnencodable = fWarnUnencodable; + *fNewSpellCheckStartOn = fSpellCheckStartOn; + *fNewButtonBar = fButtonBar; + + be_app->PostMessage(PREFS_CHANGED); + + if (revert) + { + if (fLevel == L_EXPERT) + strcpy(label, "Expert"); + else + strcpy(label, "Beginner"); + + item = fLevelMenu->FindItem(label); + if (item) + item->SetMarked(true); + + for (int i = fAccountMenu->CountItems();i-- > 0;) + { + if (BMenuItem *item = fAccountMenu->ItemAt(i)) + if (item->Message()->FindInt32("id") == *(int32 *)&fAccount) + item->SetMarked(true); + } + + strcpy(label,fReplyTo == ACCOUNT_USE_DEFAULT ? REPLYTO_USE_DEFAULT_TEXT + : REPLYTO_FROM_MAIL_TEXT); + if ((item = fReplyToMenu->FindItem(label)) != NULL) + item->SetMarked(true); + + strcpy(label, fWrap ? "On" : "Off"); + if ((item = fWrapMenu->FindItem(label)) != NULL) + item->SetMarked(true); + + strcpy(label, fAttachAttributes ? ATTRIBUTE_ON_TEXT : ATTRIBUTE_OFF_TEXT); + if ((item = fAttachAttributesMenu->FindItem(label)) != NULL) + item->SetMarked(true); + + strcpy(label, fColoredQuotes ? "On" : "Off"); + if ((item = fColoredQuotesMenu->FindItem(label)) != NULL) + item->SetMarked(true); + + if (strcmp(fReplyPreamble->Text(), *fNewPreamble)) + fReplyPreamble->SetText(*fNewPreamble); + + item = fSignatureMenu->FindItem(fSignature); + if (item) + item->SetMarked(true); + + for (uint32 index = 0; kEncodings[index].flavor != B_MAIL_NULL_CONVERSION; index++) + { + if (kEncodings[index].flavor == *fNewEncoding) + { + item = fEncodingMenu->FindItem(kEncodings[index].name); + if (item) + item->SetMarked(true); + break; + } + } + + strcpy(label, fWarnUnencodable ? "On" : "Off"); + if ((item = fWarnUnencodableMenu->FindItem(label)) != NULL) + item->SetMarked(true); + + strcpy(label, fSpellCheckStartOn ? "On" : "Off"); + if ((item = fSpellCheckStartOnMenu->FindItem(label)) != NULL) + item->SetMarked(true); + } + else + Quit(); + break; + + case P_FONT: + family = NULL; + style = NULL; + int32 family_menu_index; + if (msg->FindString("font", &family) == B_OK) + { + msg->FindString("style", &style); + fNewFont->SetFamilyAndStyle(family, style); + message.what = M_FONT; + be_app->PostMessage(&message); + } + + /* grab this little tidbit so we can set the correct Family */ + if(msg->FindInt32("parent_index", &family_menu_index) == B_OK) + fFontMenu->ItemAt(family_menu_index)->SetMarked(true); + break; + + case P_SIZE: + old_size = (int32) fNewFont->Size(); + msg->FindInt32("size", &new_size); + if (old_size != new_size) + { + fNewFont->SetSize(new_size); + message.what = M_FONT; + be_app->PostMessage(&message); + } + break; + + case P_LEVEL: + msg->FindInt32("level", fNewLevel); + break; + case P_WRAP: + msg->FindBool("wrap", fNewWrap); + break; + case P_ATTACH_ATTRIBUTES: + msg->FindBool("attachAttributes", fNewAttachAttributes); + break; + case P_COLORED_QUOTES: + msg->FindBool("cquotes", fNewColoredQuotes); + break; + case P_ACCOUNT: + msg->FindInt32("id",(int32 *)fNewAccount); + break; + case P_REPLYTO: + msg->FindInt32("replyTo", fNewReplyTo); + break; + case P_REPLY_PREAMBLE: + { + int32 index = -1; + if (msg->FindInt32("index", &index) < B_OK) + break; + BMenuItem *item = fReplyPreambleMenu->ItemAt(index); + if (item == NULL) { + msg->PrintToStream(); + break; + } + + BTextView *text = fReplyPreamble->TextView(); + // To do: insert at selection point rather than at the end. + text->Insert(text->TextLength(), item->Label(), 2); + } + case P_SIG: + free(*fNewSignature); + if (msg->FindString("signature", &signature) == B_NO_ERROR) + { + *fNewSignature = (char *)malloc(strlen(signature) + 1); + strcpy(*fNewSignature, signature); + } + else + { + *fNewSignature = (char *)malloc(strlen(SIG_NONE) + 1); + strcpy(*fNewSignature, SIG_NONE); + } + break; + case P_ENC: + msg->FindInt32("encoding", (int32 *)fNewEncoding); + break; + case P_WARN_UNENCODABLE: + msg->FindBool("warnUnencodable", fNewWarnUnencodable); + break; + case P_SPELL_CHECK_START_ON: + msg->FindBool("spellCheckStartOn", fNewSpellCheckStartOn); + break; + case P_BUTTON_BAR: + msg->FindInt8("bar", (int8 *)fNewButtonBar); + be_app->PostMessage( PREFS_CHANGED ); + break; + + default: + BWindow::MessageReceived(msg); + } + + fFont.GetFamilyAndStyle(&old_family, &old_style); + fNewFont->GetFamilyAndStyle(&new_family, &new_style); + old_size = (int32) fFont.Size(); + new_size = (int32) fNewFont->Size(); + changed = old_size != new_size + || fLevel != *fNewLevel + || fWrap != *fNewWrap + || fAttachAttributes != *fNewAttachAttributes + || fColoredQuotes != *fNewColoredQuotes + || fAccount != *fNewAccount + || fReplyTo != *fNewReplyTo + || strcmp(old_family, new_family) + || strcmp(old_style, new_style) + || strcmp(fReplyPreamble->Text(), *fNewPreamble) + || strcmp(fSignature, *fNewSignature) + || fEncoding != *fNewEncoding + || fWarnUnencodable != *fNewWarnUnencodable + || fSpellCheckStartOn != *fNewSpellCheckStartOn + || fButtonBar != *fNewButtonBar; + fRevert->SetEnabled(changed); +} + + +BPopUpMenu * +TPrefsWindow::BuildFontMenu(BFont *font) +{ + + font_family def_family; + font_style def_style; + font_family f_family; + font_style f_style; + + BPopUpMenu *menu = new BPopUpMenu(""); + font->GetFamilyAndStyle(&def_family, &def_style); + + int32 family_menu_index=0; + int family_count = count_font_families(); + for (int family_loop = 0; family_loop < family_count; family_loop++) { + get_font_family(family_loop, &f_family); + BMenu *family_menu = new BMenu(f_family); + + int style_count = count_font_styles(f_family); + for (int style_loop = 0; style_loop < style_count; style_loop++) { + get_font_style(f_family, style_loop, &f_style); + + BMessage *msg = new BMessage(P_FONT); + msg->AddString("font", f_family); + msg->AddString("style", f_style); + /* we send this to make setting the Family easier when things change */ + msg->AddInt32("parent_index", family_menu_index); + + BMenuItem *item = new BMenuItem(f_style, msg); + family_menu->AddItem(item); + if ((strcmp(def_family, f_family) == 0) && (strcmp(def_style, f_style) == 0)) { + item->SetMarked(true); + } + item->SetTarget(this); + } + + menu->AddItem(family_menu); + BMenuItem *item = menu->ItemAt(family_menu_index); + BMessage *msg = new BMessage(P_FONT); + msg->AddString("font", f_family); + + item->SetMessage(msg); + item->SetTarget(this); + if (strcmp(def_family, f_family) == 0) { item->SetMarked(true); } + family_menu_index++; + } + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildLevelMenu(int32 level) +{ + BMenuItem *item; + BMessage *msg; + BPopUpMenu *menu; + + menu = new BPopUpMenu(""); + msg = new BMessage(P_LEVEL); + msg->AddInt32("level", L_BEGINNER); + menu->AddItem(item = new BMenuItem(MDR_DIALECT_CHOICE ("Beginner","初心者"),msg)); + if (level == L_BEGINNER) + item->SetMarked(true); + + msg = new BMessage(P_LEVEL); + msg->AddInt32("level", L_EXPERT); + menu->AddItem(item = new BMenuItem(MDR_DIALECT_CHOICE ("Expert","上級者"),msg)); + if (level == L_EXPERT) + item->SetMarked(true); + + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildAccountMenu(uint32 account) +{ + BPopUpMenu *menu = new BPopUpMenu(""); + + BMenuItem *item; + + //menu->SetRadioMode(true); + BList chains; + if (GetOutboundMailChains(&chains) < B_OK) + { + menu->AddItem(item = new BMenuItem("",NULL)); + item->SetEnabled(false); + return menu; + } + + BMessage *msg; + for (int32 i = 0;i < chains.CountItems();i++) + { + BMailChain *chain = (BMailChain *)chains.ItemAt(i); + item = new BMenuItem(chain->Name(),msg = new BMessage(P_ACCOUNT)); + + msg->AddInt32("id",chain->ID()); + + if (account == chain->ID()) + item->SetMarked(true); + + menu->AddItem(item); + delete chain; + } + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildReplyToMenu(int32 account) +{ + BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING); + + BMenuItem *item; + BMessage *msg; + menu->AddItem(item = new BMenuItem(REPLYTO_USE_DEFAULT_TEXT, msg = new BMessage(P_REPLYTO))); + msg->AddInt32("replyTo", ACCOUNT_USE_DEFAULT); + if (account == ACCOUNT_USE_DEFAULT) + item->SetMarked(true); + + menu->AddItem(item = new BMenuItem(REPLYTO_FROM_MAIL_TEXT, msg = new BMessage(P_REPLYTO))); + msg->AddInt32("replyTo", ACCOUNT_FROM_MAIL); + if (account == ACCOUNT_FROM_MAIL) + item->SetMarked(true); + + return menu; +} + + +BMenu * +TPrefsWindow::BuildReplyPreambleMenu() +{ + const char *substitutes[] = { +/* To do: Not yet working, leave out for 2.0.0 beta 4: + "%f - First name", + "%l - Last name", +*/ + MDR_DIALECT_CHOICE ("%n - Full name", "%n - フルネーム"), + MDR_DIALECT_CHOICE ("%e - E-mail address", "%e - E-mailアドレス"), + MDR_DIALECT_CHOICE ("%d - Date", "%d - 日付"), + "", + MDR_DIALECT_CHOICE ("\\n - Newline", "\\n - 空行"), + NULL + }; + + BMenu *menu = new BMenu(B_EMPTY_STRING); + + for (int32 i = 0; substitutes[i]; i++) + { + if (*substitutes[i] == '\0') + menu->AddSeparatorItem(); + else + menu->AddItem(new BMenuItem(substitutes[i], new BMessage(P_REPLY_PREAMBLE))); + } + + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildSignatureMenu(char *sig) +{ + char name[B_FILE_NAME_LENGTH]; + BEntry entry; + BFile file; + BMenuItem *item; + BMessage *msg; + BPopUpMenu *menu; + BQuery query; + BVolume vol; + BVolumeRoster volume; + + menu = new BPopUpMenu(""); + + msg = new BMessage(P_SIG); + msg->AddString("signature", SIG_NONE); + menu->AddItem(item = new BMenuItem(SIG_NONE, msg)); + if (!strcmp(sig, SIG_NONE)) + item->SetMarked(true); + + msg = new BMessage(P_SIG); + msg->AddString("signature", SIG_RANDOM); + menu->AddItem(item = new BMenuItem(SIG_RANDOM, msg)); + if (!strcmp(sig, SIG_RANDOM)) + item->SetMarked(true); + menu->AddSeparatorItem(); + + volume.GetBootVolume(&vol); + query.SetVolume(&vol); + query.SetPredicate("_signature = *"); + query.Fetch(); + + while (query.GetNextEntry(&entry) == B_NO_ERROR) { + file.SetTo(&entry, O_RDONLY); + if (file.InitCheck() == B_NO_ERROR) { + msg = new BMessage(P_SIG); + file.ReadAttr("_signature", B_STRING_TYPE, 0, name, sizeof(name)); + msg->AddString("signature", name); + menu->AddItem(item = new BMenuItem(name, msg)); + if (!strcmp(sig, name)) + item->SetMarked(true); + } + } + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildSizeMenu(BFont *font) +{ + char label[16]; + uint32 loop; + int32 sizes[] = {9, 10, 11, 12, 14, 18, 24}; + float size; + BMenuItem *item; + BMessage *msg; + BPopUpMenu *menu; + + menu = new BPopUpMenu(""); + size = font->Size(); + for (loop = 0; loop < sizeof(sizes) / sizeof(int32); loop++) { + msg = new BMessage(P_SIZE); + msg->AddInt32("size", sizes[loop]); + sprintf(label, "%ld", sizes[loop]); + menu->AddItem(item = new BMenuItem(label, msg)); + if (sizes[loop] == (int32)size) + item->SetMarked(true); + } + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildBoolMenu(uint32 what,const char *boolItem,bool isTrue) +{ + BMenuItem *item; + BMessage *msg; + BPopUpMenu *menu; + + menu = new BPopUpMenu(""); + msg = new BMessage(what); + msg->AddBool(boolItem, true); + menu->AddItem(item = new BMenuItem("On", msg)); + if (isTrue) + item->SetMarked(true); + + msg = new BMessage(what); + msg->AddInt32(boolItem, false); + menu->AddItem(item = new BMenuItem("Off", msg)); + if (!isTrue) + item->SetMarked(true); + + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildWrapMenu(bool wrap) +{ + return BuildBoolMenu(P_WRAP,"wrap",wrap); +} + + +BPopUpMenu * +TPrefsWindow::BuildAttachAttributesMenu(bool attachAttributes) +{ + BMenuItem *item; + BMessage *msg; + BPopUpMenu *menu; + + menu = new BPopUpMenu(""); + msg = new BMessage(P_ATTACH_ATTRIBUTES); + msg->AddBool("attachAttributes", true); + menu->AddItem(item = new BMenuItem(ATTRIBUTE_ON_TEXT, msg)); + if (attachAttributes) + item->SetMarked(true); + + msg = new BMessage(P_ATTACH_ATTRIBUTES); + msg->AddInt32("attachAttributes", false); + menu->AddItem(item = new BMenuItem(ATTRIBUTE_OFF_TEXT, msg)); + if (!attachAttributes) + item->SetMarked(true); + + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildColoredQuotesMenu(bool quote) +{ + return BuildBoolMenu(P_COLORED_QUOTES,"cquotes",quote); +} + + +BPopUpMenu * +TPrefsWindow::BuildEncodingMenu(uint32 encoding) +{ + uint32 loop; + BMenuItem *item; + BMessage *msg; + BPopUpMenu *menu; + + menu = new BPopUpMenu(""); + for (loop = 0; kEncodings[loop].flavor != B_MAIL_NULL_CONVERSION; loop++) { + msg = new BMessage(P_ENC); + msg->AddInt32("encoding", kEncodings[loop].flavor); + menu->AddItem(item = new BMenuItem(kEncodings[loop].name, msg)); + if (encoding == kEncodings[loop].flavor) + item->SetMarked(true); + } + return menu; +} + + +BPopUpMenu * +TPrefsWindow::BuildWarnUnencodableMenu(bool warnUnencodable) +{ + return BuildBoolMenu(P_WARN_UNENCODABLE,"warnUnencodable",warnUnencodable); +} + + +BPopUpMenu * +TPrefsWindow::BuildSpellCheckStartOnMenu(bool spellCheckStartOn) +{ + return BuildBoolMenu(P_SPELL_CHECK_START_ON,"spellCheckStartOn",spellCheckStartOn); +} + + +BPopUpMenu * +TPrefsWindow::BuildButtonBarMenu(bool show) +{ + BMenuItem *item; + BMessage *msg; + BPopUpMenu *menu; + + menu = new BPopUpMenu(""); + + msg = new BMessage(P_BUTTON_BAR); + msg->AddInt8("bar", 1); + menu->AddItem(item = new BMenuItem(ICON_LABEL_TEXT, msg)); + if (show & 1) + item->SetMarked(true); + + msg = new BMessage(P_BUTTON_BAR); + msg->AddInt8("bar", 2); + menu->AddItem(item = new BMenuItem(ICON_TEXT, msg)); + if (show & 2) + item->SetMarked(true); + + msg = new BMessage(P_BUTTON_BAR); + msg->AddInt8("bar", 0); + menu->AddItem(item = new BMenuItem(HIDE_TEXT, msg)); + if (!show) + item->SetMarked(true); + return menu; +} diff --git a/src/apps/bemail/Prefs.h b/src/apps/bemail/Prefs.h new file mode 100644 index 0000000000..9e8c992da7 --- /dev/null +++ b/src/apps/bemail/Prefs.h @@ -0,0 +1,139 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Prefs.h +// +//-------------------------------------------------------------------- + +#ifndef _PREFS_H +#define _PREFS_H + +#include +#include + +#define ACCOUNT_USE_DEFAULT 0 +#define ACCOUNT_FROM_MAIL 1 + +#define PREF_WIDTH 340 +#define PREF_HEIGHT 330 + +#define SIG_NONE MDR_DIALECT_CHOICE ("None", "無し") +#define SIG_RANDOM MDR_DIALECT_CHOICE ("Random", "自動選択") + +struct EncodingItem +{ + char *name; + uint32 flavor; +}; + +extern const EncodingItem kEncodings[]; + + +class Button; + +//==================================================================== + +class TPrefsWindow : public BWindow +{ + public: + TPrefsWindow(BRect rect, BFont *font, int32 *level, bool *warp, + bool *attachAttributes, bool *cquotes, uint32 *account, + int32 *replyTo, char **preamble, char **sig, uint32 *encoding, + bool *warnUnencodable, bool *spellCheckStartOn, bool *buttonBar); + ~TPrefsWindow(); + + virtual void MessageReceived(BMessage*); + + BPopUpMenu *BuildFontMenu(BFont*); + BPopUpMenu *BuildLevelMenu(int32); + BPopUpMenu *BuildAccountMenu(uint32); + BPopUpMenu *BuildReplyToMenu(int32); + BMenu *BuildReplyPreambleMenu(); + BPopUpMenu *BuildSignatureMenu(char*); + BPopUpMenu *BuildSizeMenu(BFont*); + BPopUpMenu *BuildWrapMenu(bool); + BPopUpMenu *BuildAttachAttributesMenu(bool); + BPopUpMenu *BuildColoredQuotesMenu(bool quote); + BPopUpMenu *BuildEncodingMenu(uint32 encoding); + BPopUpMenu *BuildWarnUnencodableMenu(bool warnUnencodable); + BPopUpMenu *BuildSpellCheckStartOnMenu(bool spellCheckStartOn); + BPopUpMenu *BuildButtonBarMenu(bool show); + + private: + BPopUpMenu *BuildBoolMenu(uint32 msg, const char *boolItem, bool isTrue); + + bool fWrap; + bool *fNewWrap; + bool fAttachAttributes; + bool *fNewAttachAttributes; + bool fButtonBar; + bool *fNewButtonBar; + bool fColoredQuotes, *fNewColoredQuotes; + uint32 fAccount; + uint32 *fNewAccount; + int32 fReplyTo; + int32 *fNewReplyTo; + char **fNewPreamble; + char *fSignature; + char **fNewSignature; + int32 fLevel; + int32 *fNewLevel; + BFont fFont; + BFont *fNewFont; + uint32 fEncoding; + uint32 *fNewEncoding; + bool fWarnUnencodable; + bool *fNewWarnUnencodable; + bool fSpellCheckStartOn; + bool *fNewSpellCheckStartOn; + BButton *fRevert; + + BPopUpMenu *fFontMenu; + BPopUpMenu *fSizeMenu; + BPopUpMenu *fLevelMenu; + BPopUpMenu *fWrapMenu, *fColoredQuotesMenu; + BPopUpMenu *fAttachAttributesMenu; + BPopUpMenu *fAccountMenu, *fReplyToMenu; + BMenu *fReplyPreambleMenu; + BTextControl *fReplyPreamble; + BPopUpMenu *fSignatureMenu; + BPopUpMenu *fEncodingMenu; + BPopUpMenu *fWarnUnencodableMenu; + BPopUpMenu *fSpellCheckStartOnMenu; + BPopUpMenu *fButtonBarMenu; +}; + +#endif /* _PREFS_H */ diff --git a/src/apps/bemail/QueryMenu.cpp b/src/apps/bemail/QueryMenu.cpp new file mode 100644 index 0000000000..7101af059c --- /dev/null +++ b/src/apps/bemail/QueryMenu.cpp @@ -0,0 +1,306 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "QueryMenu.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +BLooper *QueryMenu::fQueryLooper = NULL; +int32 QueryMenu::fMenuCount = 0; + + +// *** +// QHandler +// *** + + +class QHandler : public BHandler +{ + public: + QHandler(QueryMenu *queryMenu); + virtual void MessageReceived(BMessage *msg); + + QueryMenu *fQueryMenu; +}; + + +QHandler::QHandler(QueryMenu *queryMenu) + : BHandler((const char *)NULL), + fQueryMenu(queryMenu) +{ +} + + +void QHandler::MessageReceived(BMessage *msg) +{ + switch (msg->what) + { + case B_QUERY_UPDATE: + fQueryMenu->DoQueryMessage(msg); + break; + + default: + BHandler::MessageReceived(msg); + break; + } +} + + +// *** +// QueryMenu +// *** + + +QueryMenu::QueryMenu(const char *title, bool popUp, bool radioMode, bool autoRename) + : BPopUpMenu(title, radioMode, autoRename), + fTargetHandler(NULL), + fPopUp(popUp) +{ + if (atomic_add(&fMenuCount, 1) == 0) + { + fQueryLooper = new BLooper("Query Watcher"); + fQueryLooper->Run(); + } + fQueryHandler = new QHandler(this); + fQueryLooper->Lock(); + fQueryLooper->AddHandler(fQueryHandler); + fQueryLooper->Unlock(); + + BMessenger mercury(fQueryHandler, fQueryLooper); + fQuery = new BQuery(); + fQuery->SetTarget(mercury); +} + + +QueryMenu::~QueryMenu(void) +{ + fCancelQuery = true; + fQueryLock.Lock(); + delete fQuery; + fQueryLock.Unlock(); + + fQueryLooper->Lock(); + fQueryLooper->RemoveHandler(fQueryHandler); + delete fQueryHandler; + if (atomic_add(&fMenuCount, -1) == 1) + fQueryLooper->Quit(); + else + fQueryLooper->Unlock(); +} + + +void QueryMenu::DoQueryMessage(BMessage *msg) +{ + int32 opcode; + int64 directory; + int32 device; + int64 node; + if (msg->FindInt32("opcode", &opcode) == B_OK + && msg->FindInt64("directory", &directory) == B_OK + && msg->FindInt32("device", &device) == B_OK + && msg->FindInt64("node", &node) == B_OK) + { + const char *name; + if (opcode == B_ENTRY_CREATED && msg->FindString("name", &name) == B_OK) + { + entry_ref ref(device, directory, name); + EntryCreated(ref, node); + return; + } + else if (opcode == B_ENTRY_REMOVED) + { + BAutolock lock(fQueryLock); + if (!lock.IsLocked()) + return; + EntryRemoved(node); + } + } +} + + +status_t QueryMenu::SetPredicate(const char *expr, BVolume *volume) +{ + status_t status; + + // Set the volume + if (volume == NULL) + { + BVolume bootVolume; + BVolumeRoster().GetBootVolume(&bootVolume); + + if ( (status = fQuery->SetVolume(&bootVolume)) != B_OK) + return status; + } + else if ((status = fQuery->SetVolume(volume)) != B_OK) + return status; + + if ((status = fQuery->SetPredicate(expr)) < B_OK) + return status; + + // Force query thread to exit if still running + fCancelQuery = true; + fQueryLock.Lock(); + + // Remove all existing menu items (if any... ) + RemoveEntries(); + fQueryLock.Unlock(); + + // Resolve Query/Build Menu in seperate thread + thread_id thread; + thread = spawn_thread(query_thread, "query menu thread", B_NORMAL_PRIORITY, this); + + return resume_thread(thread); +} + + +void QueryMenu::RemoveEntries() +{ + int64 node; + for (int32 i = CountItems() - 1;i >= 0;i--) + { + if (ItemAt(i)->Message()->FindInt64("node", &node) == B_OK) + RemoveItem(i); + } +} + + +int32 QueryMenu::query_thread(void *data) +{ + return ((QueryMenu *)(data))->QueryThread(); +} + + +int32 QueryMenu::QueryThread() +{ + BAutolock lock(fQueryLock); + + if (!lock.IsLocked()) + return B_ERROR; + + // Begin resolving query + fCancelQuery = false; + fQuery->Fetch(); + + // Build Menu + entry_ref ref; + node_ref node; + while (fQuery->GetNextRef(&ref) == B_OK && !fCancelQuery) + { + BEntry entry(&ref); + entry.GetNodeRef(&node); + EntryCreated(ref, node.node); + } + + // Remove the group separator if there are no groups or no items without groups + BMenuItem *item; + if (dynamic_cast(item = ItemAt(0)) != NULL) + RemoveItem(item); + else if (dynamic_cast(item = ItemAt(CountItems() - 1)) != NULL) + RemoveItem(item); + + return B_OK; +} + + +status_t QueryMenu::SetTargetForItems(BHandler *handler) +{ + fTargetHandler = handler; + return BMenu::SetTargetForItems(handler); +} + +// Include the following version of SetTargetForItems() to eliminate +// hidden polymorphism warning. Should be correct, but is unused and untested. + +status_t QueryMenu::SetTargetForItems(BMessenger messenger) +{ + if (messenger.IsTargetLocal()) + { + BLooper *ignore; // don't care what value this gets + fTargetHandler = messenger.Target(&ignore); + return BMenu::SetTargetForItems(messenger); + } + return B_ERROR; +} + + +void QueryMenu::EntryCreated(const entry_ref &ref, ino_t node) +{ + BMessage *msg; + BMenuItem *item; + + msg = new BMessage(B_REFS_RECEIVED); + msg->AddRef("refs", &ref); + msg->AddInt64("node", node); + item = new BMenuItem(ref.name, msg); + if (fTargetHandler) + item->SetTarget(fTargetHandler); + AddItem(item); +} + + +void QueryMenu::EntryRemoved(ino_t node) +{ + // Search for item in menu + BMenuItem *item; + for (int32 i = 0;(item = ItemAt(i)) != NULL;i++) + { + // Is it our item? + int64 inode; + if ((item->Message())->FindInt64("node", &inode) == B_OK + && inode == node) + { + RemoveItem(i); + return; + } + } +} + + +BPoint QueryMenu::ScreenLocation() +{ + if (fPopUp) + return BPopUpMenu::ScreenLocation(); + + return BMenu::ScreenLocation(); +} + diff --git a/src/apps/bemail/QueryMenu.h b/src/apps/bemail/QueryMenu.h new file mode 100644 index 0000000000..039b6bab97 --- /dev/null +++ b/src/apps/bemail/QueryMenu.h @@ -0,0 +1,80 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _QUERY_MENU +#define _QUERY_MENU + +#include +#include + +class BLooper; +class BQuery; +class BVolume; +class QHandler; + +class QueryMenu : public BPopUpMenu { + friend QHandler; + + public: + QueryMenu(const char *title, bool popUp=false, + bool radioMode = false, bool autoRename = false); + virtual ~QueryMenu(void); + + virtual BPoint ScreenLocation(void); + virtual status_t SetTargetForItems(BHandler *handler); + virtual status_t SetTargetForItems(BMessenger messenger); + status_t SetPredicate(const char *expr, BVolume *vol = NULL); + + protected: + virtual void EntryCreated(const entry_ref &ref, ino_t node); + virtual void EntryRemoved(ino_t node); + virtual void RemoveEntries(void); + + BHandler *fTargetHandler; + static BLooper *fQueryLooper; + + private: + virtual void DoQueryMessage(BMessage *msg); + static int32 query_thread(void *data); + int32 QueryThread(void); + + BLocker fQueryLock; + BQuery *fQuery; + QHandler *fQueryHandler; + bool fCancelQuery; + bool fPopUp; + static int32 fMenuCount; +}; + +#endif // #ifndef _QUERY_MENU diff --git a/src/apps/bemail/Signature.cpp b/src/apps/bemail/Signature.cpp new file mode 100644 index 0000000000..40e2762963 --- /dev/null +++ b/src/apps/bemail/Signature.cpp @@ -0,0 +1,611 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Signature.cpp +// +//-------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include + +#include "Mail.h" +#include "Signature.h" + +#include + +extern BRect signature_window; +extern int32 level; +extern const char *kUndoStrings[]; +extern const char *kRedoStrings[]; + +const char kNameText[] = MDR_DIALECT_CHOICE ("Title:", "署名の名称:"); +const char kSigText[] = MDR_DIALECT_CHOICE ("Signature:", "署名:"); + + +//==================================================================== + +TSignatureWindow::TSignatureWindow(BRect rect) + : BWindow (rect, MDR_DIALECT_CHOICE ("Signatures","署名の編集"), B_TITLED_WINDOW, 0), + fFile(NULL) +{ + BMenu *menu; + BMenuBar *menu_bar; + BMenuItem *item; + + BRect r = Bounds(); + /*** Set up the menus ****/ + menu_bar = new BMenuBar(r, "MenuBar"); + menu = new BMenu(MDR_DIALECT_CHOICE ("Signature","S) 署名")); + menu->AddItem(fNew = new BMenuItem(MDR_DIALECT_CHOICE ("New","N) 新規"), new BMessage(M_NEW), 'N')); + fSignature = new TMenu(MDR_DIALECT_CHOICE ("Open","O) 開く"), INDEX_SIGNATURE, M_SIGNATURE); + menu->AddItem(new BMenuItem(fSignature)); + menu->AddSeparatorItem(); + menu->AddItem(fSave = new BMenuItem(MDR_DIALECT_CHOICE ("Save","S) 保存"), new BMessage(M_SAVE), 'S')); + menu->AddItem(fDelete = new BMenuItem(MDR_DIALECT_CHOICE ("Delete","T) 削除"), new BMessage(M_DELETE), 'T')); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem(MDR_DIALECT_CHOICE ("Close","W) 閉じる"), new BMessage(B_CLOSE_REQUESTED), 'W')); + menu_bar->AddItem(menu); + + menu = new BMenu(MDR_DIALECT_CHOICE ("Edit","E) 編集")); + menu->AddItem(fUndo = new BMenuItem(MDR_DIALECT_CHOICE ("Undo","Z) やり直し"), new BMessage(B_UNDO), 'Z')); + fUndo->SetTarget(NULL, this); + menu->AddSeparatorItem(); + menu->AddItem(fCut = new BMenuItem(MDR_DIALECT_CHOICE ("Cut","X) 切り取り"), new BMessage(B_CUT), 'X')); + fCut->SetTarget(NULL, this); + menu->AddItem(fCopy = new BMenuItem(MDR_DIALECT_CHOICE ("Copy","C) コピー"), new BMessage(B_COPY), 'C')); + fCopy->SetTarget(NULL, this); + menu->AddItem(fPaste = new BMenuItem(MDR_DIALECT_CHOICE ("Paste","V) 貼り付け"), new BMessage(B_PASTE), 'V')); + fPaste->SetTarget(NULL, this); + menu->AddItem(item = new BMenuItem(MDR_DIALECT_CHOICE ("Select All","A) 全文選択"), new BMessage(M_SELECT), 'A')); + item->SetTarget(NULL, this); + menu_bar->AddItem(menu); + + AddChild(menu_bar); + /**** Done with the menu set up *****/ + + /**** Add on the panel, giving it the width and at least one vertical pixel *****/ + fSigView = new TSignatureView(BRect(0, menu_bar->Frame().bottom+1, + rect.Width(), menu_bar->Frame().bottom+2)); + AddChild(fSigView); + + /* resize the window to the correct height */ + fSigView->SetResizingMode(B_FOLLOW_NONE); + ResizeTo(rect.Width()-2, fSigView->Frame().bottom-2); + fSigView->SetResizingMode(B_FOLLOW_ALL); + + SetSizeLimits(kSigWidth, RIGHT_BOUNDARY, r.top + 100, RIGHT_BOUNDARY); +} + + +TSignatureWindow::~TSignatureWindow() +{ + signature_window = Frame(); +} + + +void +TSignatureWindow::MenusBeginning() +{ + int32 finish = 0; + int32 start = 0; + BTextView *text_view; + + fDelete->SetEnabled(fFile); + fSave->SetEnabled(IsDirty()); + fUndo->SetEnabled(false); // ***TODO*** + + text_view = (BTextView *)fSigView->fName->ChildAt(0); + if (text_view->IsFocus()) + text_view->GetSelection(&start, &finish); + else + fSigView->fTextView->GetSelection(&start, &finish); + + fCut->SetEnabled(start != finish); + fCopy->SetEnabled(start != finish); + + fNew->SetEnabled(text_view->TextLength() | fSigView->fTextView->TextLength()); + be_clipboard->Lock(); + fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", B_MIME_TYPE)); + be_clipboard->Unlock(); + + // Undo stuff + bool isRedo = false; + undo_state undoState = B_UNDO_UNAVAILABLE; + + BTextView *focusTextView = dynamic_cast(CurrentFocus()); + if (focusTextView != NULL) + undoState = focusTextView->UndoState(&isRedo); + + fUndo->SetLabel((isRedo) ? kRedoStrings[undoState] : kUndoStrings[undoState]); + fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE); +} + + +void +TSignatureWindow::MessageReceived(BMessage* msg) +{ + char *sig; + char name[B_FILE_NAME_LENGTH]; + BFont *font; + BTextView *text_view; + entry_ref ref; + off_t size; + + switch(msg->what) { + case CHANGE_FONT: + msg->FindPointer("font", (void **)&font); + fSigView->fTextView->SetFontAndColor(font); + fSigView->fTextView->Invalidate(fSigView->fTextView->Bounds()); + break; + + case M_NEW: + if (Clear()) { + fSigView->fName->SetText(""); + fSigView->fTextView->SetText(NULL, (int32)0); + fSigView->fName->MakeFocus(true); + } + break; + + case M_SAVE: + Save(); + break; + + case M_DELETE: + if (level == L_BEGINNER) { + beep(); + if (!(new BAlert("",MDR_DIALECT_CHOICE ( + "Are you sure you want to delete this signature?", + "この署名を削除しますか?"), + MDR_DIALECT_CHOICE ("Cancel","取消l"), + MDR_DIALECT_CHOICE ("Delete","削除"), NULL, B_WIDTH_AS_USUAL, + B_WARNING_ALERT))->Go()) + break; + } + if (fFile) { + delete fFile; + fFile = NULL; + fEntry.Remove(); + fSigView->fName->SetText(""); + fSigView->fTextView->SetText(NULL, (int32)0); + fSigView->fName->MakeFocus(true); + } + break; + + case M_SIGNATURE: + if (Clear()) { + msg->FindRef("ref", &ref); + fEntry.SetTo(&ref); + fFile = new BFile(&ref, O_RDWR); + if (fFile->InitCheck() == B_NO_ERROR) { + fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name)); + fSigView->fName->SetText(name); + fFile->GetSize(&size); + sig = (char *)malloc(size); + size = fFile->Read(sig, size); + fSigView->fTextView->SetText(sig, size); + fSigView->fName->MakeFocus(true); + text_view = (BTextView *)fSigView->fName->ChildAt(0); + text_view->Select(0, text_view->TextLength()); + fSigView->fTextView->fDirty = false; + } + else { + fFile = NULL; + beep(); + (new BAlert("", MDR_DIALECT_CHOICE ( + "An error occurred trying to open this signature.", + "署名を開く時にエラーが発生しました。"), + MDR_DIALECT_CHOICE ("Sorry","了解")))->Go(); + } + } + break; + + default: + BWindow::MessageReceived(msg); + } +} + + +bool +TSignatureWindow::QuitRequested() +{ + if (Clear()) { + BMessage msg(WINDOW_CLOSED); + msg.AddInt32("kind", SIG_WINDOW); + + be_app->PostMessage(&msg); + return true; + } + return false; +} + + +void +TSignatureWindow::FrameResized(float width, float height) +{ + fSigView->FrameResized(width, height); +} + + +void +TSignatureWindow::Show() +{ + BTextView *text_view; + + Lock(); + text_view = (BTextView *)fSigView->fName->TextView(); + fSigView->fName->MakeFocus(true); + text_view->Select(0, text_view->TextLength()); + Unlock(); + + BWindow::Show(); +} + + +bool +TSignatureWindow::Clear() +{ + int32 result; + + if (IsDirty()) { + beep(); + result = (new BAlert("", + MDR_DIALECT_CHOICE ("Save changes to signature?","変更した署名を保存しますか?"), + MDR_DIALECT_CHOICE ("Don't save","保存しない"), + MDR_DIALECT_CHOICE ("Cancel","中止"), + MDR_DIALECT_CHOICE ("Save","保存する"), + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + if (result == 1) + return false; + if (result == 2) + Save(); + } + + delete fFile; + fFile = NULL; + fSigView->fTextView->fDirty = false; + return true; +} + + +bool +TSignatureWindow::IsDirty() +{ + char name[B_FILE_NAME_LENGTH]; + + if (fFile) { + fFile->ReadAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, name, sizeof(name)); + if ((strcmp(name, fSigView->fName->Text())) || (fSigView->fTextView->fDirty)) + return true; + } + else { + if ((strlen(fSigView->fName->Text())) || + (fSigView->fTextView->TextLength())) + return true; + } + return false; +} + + +void +TSignatureWindow::Save() +{ + char name[B_FILE_NAME_LENGTH]; + int32 index = 0; + status_t result; + BDirectory dir; + BEntry entry; + BNodeInfo *node; + BPath path; + + if (!fFile) { + find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); + dir.SetTo(path.Path()); + + if (dir.FindEntry("bemail", &entry) == B_NO_ERROR) + dir.SetTo(&entry); + else + dir.CreateDirectory("bemail", &dir); + + if (dir.InitCheck() != B_NO_ERROR) + goto err_exit; + + if (dir.FindEntry("signatures", &entry) == B_NO_ERROR) + dir.SetTo(&entry); + else + dir.CreateDirectory("signatures", &dir); + + if (dir.InitCheck() != B_NO_ERROR) + goto err_exit; + + fFile = new BFile(); + while(true) { + sprintf(name, "signature_%ld", index++); + if ((result = dir.CreateFile(name, fFile, true)) == B_NO_ERROR) + break; + if (result != EEXIST) + goto err_exit; + } + dir.FindEntry(name, &fEntry); + node = new BNodeInfo(fFile); + node->SetType("text/plain"); + delete node; + } + + fSigView->fTextView->fDirty = false; + fFile->Seek(0, 0); + fFile->Write(fSigView->fTextView->Text(), + fSigView->fTextView->TextLength()); + fFile->SetSize(fFile->Position()); + fFile->WriteAttr(INDEX_SIGNATURE, B_STRING_TYPE, 0, fSigView->fName->Text(), + strlen(fSigView->fName->Text()) + 1); + return; + +err_exit: + beep(); + (new BAlert("", MDR_DIALECT_CHOICE ( + "An error occurred trying to save this signature.", + "署名を保存しようとした時にエラーが発生しました。"), + MDR_DIALECT_CHOICE ("Sorry","了解")))->Go(); +} + + +//==================================================================== +// #pragma mark - + + +TSignatureView::TSignatureView(BRect rect) + : BBox(rect, "SigView", B_FOLLOW_ALL, B_WILL_DRAW) +{ +} + + +void +TSignatureView::AttachedToWindow() +{ + BRect rect = Bounds(); + float name_text_length = StringWidth(kNameText); + float sig_text_length = StringWidth(kSigText); + float divide_length; + + if (name_text_length > sig_text_length) + divide_length = name_text_length; + else + divide_length = sig_text_length; + + rect.InsetBy(8,0); + rect.top+= 8; + + fName = new TNameControl(rect, kNameText, new BMessage(NAME_FIELD)); + AddChild(fName); + + fName->SetDivider(divide_length + 10); + fName->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); + + rect.OffsetBy(0,fName->Bounds().Height()+5); + rect.bottom = rect.top + kSigHeight; + rect.left = fName->TextView()->Frame().left; + + BRect text = rect; + text.OffsetTo(10,0); + fTextView = new TSigTextView(rect, text); + BScrollView *scroller = new BScrollView("SigScroller", fTextView, B_FOLLOW_ALL, 0, false, true); + AddChild(scroller); + scroller->ResizeBy(-1 * scroller->ScrollBar(B_VERTICAL)->Frame().Width() - 9, 0); + scroller->MoveBy(7,0); + + /* back up a bit to make room for the label */ + + rect = scroller->Frame(); + BStringView *stringView = new BStringView(rect, "SigLabel", kSigText); + AddChild(stringView); + + float tWidth, tHeight; + stringView->GetPreferredSize(&tWidth, &tHeight); + + /* the 5 is for the spacer in the TextView */ + + rect.OffsetBy(-1 *(tWidth) - 5, 0); + rect.right = rect.left + tWidth; + rect.bottom = rect.top + tHeight; + + stringView->MoveTo(rect.LeftTop()); + stringView->ResizeTo(rect.Width(), rect.Height()); + + /* Resize the View to the correct height */ + scroller->SetResizingMode(B_FOLLOW_NONE); + ResizeTo(Frame().Width(), scroller->Frame().bottom + 8); + scroller->SetResizingMode(B_FOLLOW_ALL); +} + + +//==================================================================== +// #pragma mark - + + +TNameControl::TNameControl(BRect rect, const char *label, BMessage *msg) + :BTextControl(rect, "", label, "", msg, B_FOLLOW_LEFT_RIGHT) +{ + strcpy(fLabel, label); +} + + +void +TNameControl::AttachedToWindow() +{ + BTextControl::AttachedToWindow(); + + SetDivider(StringWidth(fLabel) + 6); + TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1); +} + + +void +TNameControl::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case M_SELECT: + TextView()->Select(0, TextView()->TextLength()); + break; + + default: + BTextControl::MessageReceived(msg); + } +} + + +//==================================================================== +// #pragma mark - + + +TSigTextView::TSigTextView(BRect frame, BRect text) + :BTextView(frame, "SignatureView", text, B_FOLLOW_ALL, B_NAVIGABLE | B_WILL_DRAW) +{ + fDirty = false; + SetDoesUndo(true); +} + + +void +TSigTextView::FrameResized(float /*width*/, float /*height*/) +{ + BRect r(Bounds()); + r.InsetBy(3, 3); + SetTextRect(r); +} + + +void +TSigTextView::DeleteText(int32 offset, int32 len) +{ + fDirty = true; + BTextView::DeleteText(offset, len); +} + + +void +TSigTextView::InsertText(const char *text, int32 len, int32 offset, + const text_run_array *runs) +{ + fDirty = true; + BTextView::InsertText(text, len, offset, runs); +} + + +void +TSigTextView::KeyDown(const char *key, int32 count) +{ + bool up = false; + int32 height; + BRect r; + + switch (key[0]) { + case B_HOME: + Select(0, 0); + ScrollToSelection(); + break; + + case B_END: + Select(TextLength(), TextLength()); + ScrollToSelection(); + break; + + case B_PAGE_UP: + up = true; + case B_PAGE_DOWN: + r = Bounds(); + height = (int32)((up ? r.top - r.bottom : r.bottom - r.top) - 25); + if ((up) && (!r.top)) + break; + ScrollBy(0, height); + break; + + default: + BTextView::KeyDown(key, count); + } +} + + +void +TSigTextView::MessageReceived(BMessage *msg) +{ + char type[B_FILE_NAME_LENGTH]; + char *text; + int32 end; + int32 start; + BFile file; + BNodeInfo *node; + entry_ref ref; + off_t size; + + switch (msg->what) { + case B_SIMPLE_DATA: + if (msg->HasRef("refs")) { + msg->FindRef("refs", &ref); + file.SetTo(&ref, O_RDONLY); + if (file.InitCheck() == B_NO_ERROR) { + node = new BNodeInfo(&file); + node->GetType(type); + delete node; + file.GetSize(&size); + if ((!strncasecmp(type, "text/", 5)) && (size)) { + text = (char *)malloc(size); + file.Read(text, size); + Delete(); + GetSelection(&start, &end); + Insert(text, size); + Select(start, start + size); + free(text); + } + } + } + else + BTextView::MessageReceived(msg); + break; + + case M_SELECT: + if (IsSelectable()) + Select(0, TextLength()); + break; + + default: + BTextView::MessageReceived(msg); + } +} + diff --git a/src/apps/bemail/Signature.h b/src/apps/bemail/Signature.h new file mode 100644 index 0000000000..199f23f424 --- /dev/null +++ b/src/apps/bemail/Signature.h @@ -0,0 +1,145 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Signature.h +// +//-------------------------------------------------------------------- + +#ifndef _SIGNATURE_H +#define _SIGNATURE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +const float kSigHeight = 200; +const float kSigWidth = 457; + +#define INDEX_SIGNATURE "_signature" + +class TMenu; +class TNameControl; +class TScrollView; +class TSignatureView; +class TSigTextView; + +//==================================================================== + +class TSignatureWindow : public BWindow { +public: + TSignatureWindow(BRect); + ~TSignatureWindow(); + virtual void MenusBeginning(); + virtual void MessageReceived(BMessage*); + virtual bool QuitRequested(); + virtual void Show(); + void FrameResized(float width, float height); + bool Clear(); + bool IsDirty(); + void Save(); + +private: + BMenuItem *fCut; + BMenuItem *fCopy; + BMenuItem *fDelete; + BMenuItem *fNew; + BMenuItem *fPaste; + BMenuItem *fSave; + BMenuItem *fUndo; + BEntry fEntry; + BFile *fFile; + TMenu *fSignature; + TSignatureView *fSigView; +}; + +//-------------------------------------------------------------------- + +class TSignatureView : public BBox { +public: + TSignatureView(BRect); + virtual void AttachedToWindow(); + + TNameControl *fName; + TSigTextView *fTextView; + +private: + float fOffset; +}; + +//==================================================================== + +class TNameControl : public BTextControl { +public: + TNameControl(BRect, const char*, BMessage*); + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage*); + +private: + char fLabel[100]; +}; + +//==================================================================== + +class TSigTextView : public BTextView { +public: + TSigTextView(BRect, BRect); + void FrameResized(float width, float height); + + virtual void DeleteText(int32, int32); + virtual void KeyDown(const char*, int32); + virtual void InsertText(const char*, int32, int32, const text_run_array*); + virtual void MessageReceived(BMessage*); + + bool fDirty; + +private: + TSignatureView *fParent; +}; + +#endif // #ifndef _SIGNATURE_H diff --git a/src/apps/bemail/Spell.html b/src/apps/bemail/Spell.html new file mode 100644 index 0000000000..4fe9eda17e --- /dev/null +++ b/src/apps/bemail/Spell.html @@ -0,0 +1,574 @@ + + +GNU ISPELL V4.0 - GNU ISPELL +

Go to the previous section.

+

GNU ISPELL

+Ispell is a program that helps you to correct typos in a file, +and to find the correct spelling of words. When presented with a +word that is not in the dictionary, ispell attempts to find +near misses that might include the word you meant. +

+This manual describes how to use ispell, as well as a little about +its implementation. +

+

Using ispell from emacs

+

+

Checking a single word

+

+The simplest emacs command for calling ispell is 'M-$' (meta-dollar. +On some terminals, you must type ESC-$.) This checks the spelling of +the word under the cursor. If the word is found in the dictionary, +then a message is printed in the echo area. Otherwise, ISPELL +attempts to generate near misses. +

+If any near misses are found, they are displayed in a separate window, +each preceded by a digit. If one of these is the word you wanted, +just type its digit, and it will replace the original word in your +buffer. +

+If no near miss is right, or if none are displayed, you +have four choices: +

+

+
I +

+Insert the word in your private dictionary. Use this if you +know that the word is spelled correctly. +

+

A +

+Accept the word for the duration of this editing session, but do not +put it in your private dictionary. Use this if you are not sure about +the spelling of the word, but you do not want to look it up +immediately. The next time you start ispell, it will have forgotten +any accepted words. You can make it forget accepted words at any time +by typing M-x reload-ispell. +

+

SPC +

+Leave the word alone, and consider it misspelled if it is checked again. +

+

R +

+Replace the word. This command prompts you for a string in the +minibuffer. You may type more than one word, and each word you type +is checked again, possibly finding other near misses. This command +provides a handy way to close in on a word that you have no idea how +to spell. You can keep trying different spellings until you find one +that is close enough to get a near miss. +

+

L +

+Lookup. Display words from the dictionary that contain a +specified substring. The substring is a regular expression, +which means it can contain special characters to be more +selective about which words get displayed. +See section `Regexps' in emacs.

+If the only special character in the regular express is a leading +^, then a very fast binary search will be used, instead of +scanning the whole file. +

+Only a few matching words can be displayed in the ISPELL window. +If you want to see more, use the look program directly from +the shell. +

+

+Of course, you can also type ^G to stop the command without +changing anything. +

+If you make a change that you don't like, just use emacs' normal undo +feature See section `undo' in emacs. +

+

Checking a whole buffer

+

+If you want to check the spelling of all the words in a buffer, type +the command M-x ispell. This command scans the file, and makes +a list of all the misspelled words. When it is done, it moves the +cursor to the first word on the list, and acts like you just typed M-$ +See section Checking a single word. +

+When you finish with one word, the cursor is automatically moved to the +next. If you want to stop in the middle of the list type Q or +^G. Later, you can pick up where you left off by typing +C-X $. +

+

Checking a region

+

+You may check the words in the region with the command M-x ispell-region. +See See section `mark' in emacs. +

+The commands available are the same as for checking a whole buffer. +

+

Old Emacs

+

+Until ispell becomes part of the standard emacs distribution, you will +have to explicitly request that it be loaded. Put the following lines +in your emacs init file See section `init file' in emacs. +

+

 
+(autoload 'ispell "ispell" "Run ispell over buffer" t) 
+(autoload 'ispell-region "ispell" "Run ispell over region" t) 
+(autoload 'ispell-word "ispell" "Check word under cursor" t) 
+(define-key esc-map "$" 'ispell-word) 
+
+

+(It will do no harm to have these lines in your init file even after +ispell is installed by default.) +

+

Using ispell by itself

+

+To check the words in a file, give the command ispell FILE. This +will present a screen of information, and accept commands for every word +that is not found in the dictionary. +

+The screen shows the offending word at the top, as well as two lines of +context at the bottom. If any near misses are found, they are shown in the +middle of the screen, each preceded by a digit. +

+You may use the same commands as inside of emacs to accept the word, +place it in your private dictionary, select a near miss, or type a +replacement See section Checking a single word. You may also choose from the following +commands: +

+

+
? +

+Print a help message. +

+

Q +
Quit. Accept the rest of the words in the file and exit. +

+

X +
Exit. Abandon any changes made to this file and exit immediately. You +are asked if you are sure you want to do this. +

+

! +
Shell escape. The shell command that you type is executed as +a subprocess. +

+

^Z +
Suspend. On systems that support job control, this suspends ISPELL. +On other systems it executes a subshell. +

+

^L +
Redraw the screen. +
+

+If you type your interrupt character (usually ^C or DEL), then +ispell will immediately enter its command loop. If ispell was generating +near misses at the time, then all that it had found so far will be +displayed, along with a message stating that there might be more, and that +you can type RET to generate them. If it was scanning the file, it +will display `(INTERRUPT)' where it would normally display a bad word, +and the commands that change the file will be disabled. +

+The feature is handy if you have left out a space between words, and +ispell is futilely looking up the 1000 potential near misses for a +string that has twenty letters. +

+

Using ispell to look up individual words +

+

+When ispell is run with no arguments, it reads words from the standard +input. For each one, it prints a message telling whether it is in the +dictionary. For any words not in the dictionary, near misses are +computed, and any that are found are printed. +

+

 
+% ispell 
+word: independant 
+how about: independent 
+word: xyzzy 
+not found 
+word: ^D 
+
+

+

Your private dictionary

+

+Whenever ispell is started the file `ispell.words' is read from your +home directory (if it exists). This file contains a list of words, one per +line, and neither the order nor the case of the words is important. Ispell +will consider all of the words good, and will use them as possible near +misses. +

+The I command adds words to `ispell.words', so normally you +don't have to worry about the file. You may want to check it from +time to time to make sure you have not accidentally inserted a +misspelled word. +

+

Compatibility with the traditional spell program +

+

+The `-u' flag tells ispell to be compatible with the traditional +`spell' program. This flag is automatically turned on if the +program is invoked by the name `spell'. +

+This flag causes the following behavior: +

+All of the files listed as arguments (or the standard input if none) +are checked, and misspellings are printed on the standard output. The +output is sorted, only one instance of each word appears (however, +a word may appear more than once with different capitalizations.) +

+You may specify a file containing good words with `+filename'. +

+The troff commands `.so' and `.nx' (to include a file, or +switch to a file, respectively) are obeyed, unless you give the flag +`-i'. +

+The other `spell' flags `-v', `-b', `-x' and +`-l' are ignored. +

+By the way, ispell seems to be about three times faster +than traditional spell. +

+

All commands in emacs and standalone modes +

+

+Commands valid in both modes: +

+DIGIT Select a near miss +I Insert into private dictionary +A Accept for this session +SPACE Skip this time +R Replace with one or more words +L Lookup: search the dictionary using a regular expression +

+Standalone only: +

+Q Accept rest of file +X Abandon changes +! Shell escape +? Help +^Z Suspend +^L Redraw screen +^C Give up generating near misses, or show position in file +

+Emacs only: +

+M-$ Check word +M-x ispell Check buffer +M-x ispell-region Check region +M-x reload-ispell Reread private dictionary +M-x kill-ispell Kill current subprocess, and start a new one + next time +^G When in M-x ispell, stop working on current + bad word list +^X $ Resume working on bad word list. +

+

Definition of a near miss

+

+Two words are near each other if they can be made identical with one +of the following changes to one of the words: +

+

 
+Interchange two adjacent letters. 
+Change one letter. 
+Delete one letter. 
+Add one letter. 
+
+

+Someday, perhaps ispell will be extended so that words that sound +alike would also be considered near misses. If you would like to +implement this, see Knuth, Volume 3, page 392 for a description of the +Soundex algorithm which might apply. +

+

Flags to the ispell command

+

+Ispell's arguments are parsed by getopt(3). Therefore, there is +considerable flexibility about where to put spaces between arguments. +The way to be safe is to give only one flag per dash, and put a space +between a flag and its argument. +

+If ispell is run with no arguments, it enters `ask' mode See section Using ispell to look up individual words +. +With one or more file name arguments, it interactively checks each one. +

+

+
-p privname +
Use privname as the private dictionary. +

+

-d dictname +
Use dictname as the system dictionary. You may also specify a system +dictionary with the environment variable ISPELL_DICTIONARY. +

+

-l +
List mode. Scan the file, and print any misspellings on the standard +output. This mode is compatible with the traditional spell program, +except that the output is not sorted. See section Compatibility with the traditional spell program +. +

+

-u +
Compatibility mode. See section Compatibility with the traditional spell program +. +

+

-a +
Old style program interface, See section How other programs can use ispell +. +

+

-S +
New program interface, See section How other programs can use ispell +. +

+

-D +
Print the dictionary on the standard output with flags. +

+

-E +
Print the dictionary on the standard output with all flags expanded. +

+

+

+

How other programs can use ispell

+

+Ispell can be used as a subprocess communicating through a pipe. Two +interfaces are available: +

+

New style, for EMACS

+

+To use this interface, start ispell with the '-S' flag. Ispell will +print a version number and greeting message that looks like: +

+

 
+(1 "ISPELL V4.0")= 
+
+

+The number is the version number of the protocol to be spoken over +the pipe. The string is a message possibly of interest to the user. +

+All messages from ispell end in an equal sign, and ispell guarantees not to +print an equal sign except to end a message. Therefore, if you do not want +to deal with the greeting, just throw away characters until you get to an +equals. +

+Ispell then reads one line commands from the standard input, and +writes responses on the standard output. +

+If a command does not start with a colon, then it is considered a +single word. The word is looked up in the dictionary, and if it is +found, the response is t. If the word is not in the +dictionary, and no near misses can be found, then the response is +nil. If there are near misses, the response is a line containing +a list of strings in lisp form. For example: +

+ INPUT OUTPUT + the t + xxx nil + teh ("tea" "ten" "the") +

+The near miss response is suitable for passing directly to the lisp +read function, but it can also be parsed simply in C. In +particular, ispell promises that the list will appear all on one line, +and that the structure will not change. A parser that reads the whole +line, then treats the parentheses and quotes as whitespace will work fine. +

+The list will contain a maximum of ten strings, and each string will be +no longer than 40 characters. Also, the capitalization of the near +misses is the same as the input word. +

+

Colon commands

+

+If the input line starts with a colon, then it is one of the following +commands: +

+:file filename +Run the word checker over the named filename. The response is zero or +more lines each containing a number. The numbers are file offsets of +words that do not appear in the dictionary. Since the near miss +checker is not run, this is fairly fast. +

+After the last number, there will be a line containing either t if +the checker got to the end of the file, or nil if it received an +interrupt. If ispell ignores any interrupts received except while +scanning a file. +

+:insert word +Place word in the private dictionary. +

+:accept word +Do not complain about word for the rest of the session. +

+:dump +Write the private dictionary. +

+:reload +Reread the private dictionary. +

+:tex +Enable the tex parser for future :file commands. +

+:troff +Enable the tex parser for future :file commands. +

+:generic +Disable any text formatter parsers for future :file commands. +

+

Old style, like ITS

+

+To use this interface, start ispell with the '-a' flag. Ispell +will read words from its standard input (one per line), and write +a one line of output for each one. +

+If the first character of the line is *, then word was found in the +dictionary. (Other versions of ispell made a distinction between words +that were found directly, and words that were found after suffix +removal. These lines began with +, followed by a space, then +followed by the root word. To remain compatible with these version, +treat + and * the same.) +

+If the line starts with &, then the input word was not found, but +some near misses were found. They are listed on the output line +separated by spaces. Also, the output words will have the same +capitalization as the input. +

+Finally, if the line starts with #, then the word was not in the +dictionaries, and no near misses were found. +

+ INPUT OUTPUT + the * + xxx # + teh & tea ten the +

+

How the suffix stripper works

+

+This section is excerpted from the ITS spell.info file. +

+ Words in SPELL's main dictionary (but not the private dictionary) may +have flags associated with them to indicate the legality of suffixes +without the need to keep the full suffixed words in the dictionary. The +flags have "names" consisting of single letters. Their meaning is as +follows: +

+Let # and @ be "variables" that can stand for any letter. Upper case +letters are constants. "..." stands for any string of zero or more +letters, but note that no word may exist in the dictionary which is not at +least 2 letters long, so, for example, FLY may not be produced by placing +the "Y" flag on "F". Also, no flag is effective unless the word that it +creates is at least 4 letters long, so, for example, WED may not be +produced by placing the "D" flag on "WE". +

+"V" flag: + ...E --> ...IVE as in CREATE --> CREATIVE + if # .ne. E, ...# --> ...#IVE as in PREVENT --> PREVENTIVE +

+"N" flag: + ...E --> ...ION as in CREATE --> CREATION + ...Y --> ...ICATION as in MULTIPLY --> MULTIPLICATION + if # .ne. E or Y, ...# --> ...#EN as in FALL --> FALLEN +

+"X" flag: + ...E --> ...IONS as in CREATE --> CREATIONS + ...Y --> ...ICATIONS as in MULTIPLY --> MULTIPLICATIONS + if # .ne. E or Y, ...# --> ...#ENS as in WEAK --> WEAKENS +

+"H" flag: + ...Y --> ...IETH as in TWENTY --> TWENTIETH + if # .ne. Y, ...# --> ...#TH as in HUNDRED --> HUNDREDTH +

+"Y" FLAG: + ... --> ...LY as in QUICK --> QUICKLY +

+"G" FLAG: + ...E --> ...ING as in FILE --> FILING + if # .ne. E, ...# --> ...#ING as in CROSS --> CROSSING +

+"J" FLAG" + ...E --> ...INGS as in FILE --> FILINGS + if # .ne. E, ...# --> ...#INGS as in CROSS --> CROSSINGS +

+"D" FLAG: + ...E --> ...ED as in CREATE --> CREATED + if @ .ne. A, E, I, O, or U, + ...@Y --> ...@IED as in IMPLY --> IMPLIED + if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U) + ...@# --> ...@#ED as in CROSS --> CROSSED + or CONVEY --> CONVEYED +

+"T" FLAG: + ...E --> ...EST as in LATE --> LATEST + if @ .ne. A, E, I, O, or U, + ...@Y --> ...@IEST as in DIRTY --> DIRTIEST + if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U) + ...@# --> ...@#EST as in SMALL --> SMALLEST + or GRAY --> GRAYEST +

+"R" FLAG: + ...E --> ...ER as in SKATE --> SKATER + if @ .ne. A, E, I, O, or U, + ...@Y --> ...@IER as in MULTIPLY --> MULTIPLIER + if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U) + ...@# --> ...@#ER as in BUILD --> BUILDER + or CONVEY --> CONVEYER +

+"Z FLAG: + ...E --> ...ERS as in SKATE --> SKATERS + if @ .ne. A, E, I, O, or U, + ...@Y --> ...@IERS as in MULTIPLY --> MULTIPLIERS + if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U) + ...@# --> ...@#ERS as in BUILD --> BUILDERS + or SLAY --> SLAYERS +

+"S" FLAG: + if @ .ne. A, E, I, O, or U, + ...@Y --> ...@IES as in IMPLY --> IMPLIES + if # .eq. S, X, Z, or H, + ...# --> ...#ES as in FIX --> FIXES + if # .ne. S, X, Z, H, or Y, or (# = Y and @ = A, E, I, O, or U) + ...# --> ...#S as in BAT --> BATS + or CONVEY --> CONVEYS +

+"P" FLAG: + if @ .ne. A, E, I, O, or U, + ...@Y --> ...@INESS as in CLOUDY --> CLOUDINESS + if # .ne. Y, or @ = A, E, I, O, or U, + ...@# --> ...@#NESS as in LATE --> LATENESS + or GRAY --> GRAYNESS +

+"M" FLAG: + ... --> ...'S as in DOG --> DOG'S +

+Note: The existence of a flag on a root word in the directory is not by +itself sufficient to cause SPELL to recognize the indicated word ending. +If there is more than one root for which a flag will indicate a given word, +only one of the roots is the correct one for which the flag is effective; +generally it is the longest root. For example, the "D" rule implies that +either PASS or PASSE, with a "D" flag, will yield PASSED. The flag must be +on PASSE; it will be ineffective on PASS. This is because, when SPELL +encounters the word PASSED and fails to find it in its dictionary, it +strips off the "D" and looks up PASSE. Upon finding PASSE, it then accepts +PASSED if and only if PASSE has the "D" flag. Only if the word PASSE is +not in the main dictionary at all does the program strip off the "E" and +search for PASS. +

+Therefore, never install a flag by hand. Instead, just add complete +new words to the dictionary file, then use the build program with the +options '-a -r' to replace as many roots with flags as possible. +

+

Where it came from

+

+I first came across ispell on TOPS-20 systems at MIT. I tracked it +down to ITS where I found the PDP-10 assembly program. It appeared +that it had been in use at the MIT-AI lab since at least the late +1970's. I think it case from California before then. +

+I wrote the first C implementation in the spring of 1983, mostly +working from the ITS INFO file. +

+The present version was created in early 1988, and was motivated by +the desire to make it run on 80286's, and to provide a better interface +for GNU EMACS. +

+There is another widely distributed version of ispell, which was forked +from my 1983 version and has a different set of features and clever +extensions. It is available from the directory /u/public/ispell at +celray.cs.yale.edu. +

+People who have contributed to various versions of ispell include: Walt +Buehring, Mark Davies, Geoff Kuenning, Rober McQueer, Ashwin Ram, Greg +Schaffer, Perry Smith, Ken Stevens, and Andrew Vignaux. +

+Pace Willisson
+pace@ai.mit.edu pace@hx.lcs.mit.edu
+(617) 625--3452 +

+

Go to the previous section.

diff --git a/src/apps/bemail/Status.cpp b/src/apps/bemail/Status.cpp new file mode 100644 index 0000000000..3c6d2c8af5 --- /dev/null +++ b/src/apps/bemail/Status.cpp @@ -0,0 +1,205 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Status.cpp +// +//-------------------------------------------------------------------- + +#include +#include +#include + +#include + +#include "Mail.h" +#include "Status.h" + + +//==================================================================== + +TStatusWindow::TStatusWindow(BRect rect, BWindow *window, const char *status) + : BWindow(rect, "", B_MODAL_WINDOW, B_NOT_RESIZABLE) +{ + BRect r(0, 0, STATUS_WIDTH, STATUS_HEIGHT); + + r.InsetBy(-1, -1); + fView = new TStatusView(r, window, status); + Lock(); + AddChild(fView); + Unlock(); + Show(); +} + + +//==================================================================== +// #pragma mark - + + +TStatusView::TStatusView(BRect rect, BWindow *window, const char *status) + : BBox(rect, "", B_FOLLOW_ALL, B_WILL_DRAW) +{ + fWindow = window; + fString = status; + + SetViewColor(VIEW_COLOR, VIEW_COLOR, VIEW_COLOR); + + BFont font = *be_plain_font; + font.SetSize(FONT_SIZE); + SetFont(&font); +} + + +void +TStatusView::AttachedToWindow() +{ + BRect r(STATUS_FIELD_H, STATUS_FIELD_V, + STATUS_FIELD_WIDTH, STATUS_FIELD_V + STATUS_FIELD_HEIGHT); + + fStatus = new BTextControl(r, "", STATUS_TEXT, fString, new BMessage(STATUS)); + AddChild(fStatus); + + BFont font = *be_plain_font; + font.SetSize(FONT_SIZE); + fStatus->SetFont(&font); + fStatus->SetDivider(StringWidth(STATUS_TEXT) + 6); + fStatus->BTextControl::MakeFocus(true); + + r.Set(S_OK_BUTTON_X1, S_OK_BUTTON_Y1, S_OK_BUTTON_X2, S_OK_BUTTON_Y2); + BButton *button = new BButton(r, "", S_OK_BUTTON_TEXT, new BMessage(OK)); + AddChild(button); + button->SetTarget(this); + button->MakeDefault(true); + + r.Set(S_CANCEL_BUTTON_X1, S_CANCEL_BUTTON_Y1, S_CANCEL_BUTTON_X2, S_CANCEL_BUTTON_Y2); + button = new BButton(r, "", S_CANCEL_BUTTON_TEXT, new BMessage(CANCEL)); + AddChild(button); + button->SetTarget(this); +} + + +void +TStatusView::MessageReceived(BMessage *msg) +{ + char name[B_FILE_NAME_LENGTH]; + char new_name[B_FILE_NAME_LENGTH]; + int32 index = 0; + uint32 loop; + status_t result; + BDirectory dir; + BEntry entry; + BFile file; + BNodeInfo *node; + BPath path; + + switch (msg->what) + { + case STATUS: + break; + + case OK: + if (!Exists(fStatus->Text())) { + find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); + dir.SetTo(path.Path()); + if (dir.FindEntry("bemail", &entry) == B_NO_ERROR) + dir.SetTo(&entry); + else + dir.CreateDirectory("bemail", &dir); + if (dir.InitCheck() != B_NO_ERROR) + goto err_exit; + if (dir.FindEntry("status", &entry) == B_NO_ERROR) + dir.SetTo(&entry); + else + dir.CreateDirectory("status", &dir); + if (dir.InitCheck() == B_NO_ERROR) { + sprintf(name, "%s", fStatus->Text()); + if (strlen(name) > B_FILE_NAME_LENGTH - 10) + name[B_FILE_NAME_LENGTH - 10] = 0; + for (loop = 0; loop < strlen(name); loop++) { + if (name[loop] == '/') + name[loop] = '\\'; + } + strcpy(new_name, name); + while (1) { + if ((result = dir.CreateFile(new_name, &file, true)) == B_NO_ERROR) + break; + if (result != EEXIST) + goto err_exit; + sprintf(new_name, "%s_%ld", name, index++); + } + dir.FindEntry(new_name, &entry); + node = new BNodeInfo(&file); + node->SetType("text/plain"); + delete node; + file.Write(fStatus->Text(), strlen(fStatus->Text()) + 1); + file.SetSize(file.Position()); + file.WriteAttr(INDEX_STATUS, B_STRING_TYPE, 0, fStatus->Text(), + strlen(fStatus->Text()) + 1); + } + } +err_exit: + { + BMessage closeCstmMsg(M_CLOSE_CUSTOM); + closeCstmMsg.AddString("status", fStatus->Text()); + fWindow->PostMessage(&closeCstmMsg); + // will fall through + } + case CANCEL: + Window()->Quit(); + break; + } +} + + +bool +TStatusView::Exists(const char *status) +{ + BVolume volume; + BVolumeRoster().GetBootVolume(&volume); + + BQuery query; + query.SetVolume(&volume); + query.PushAttr(INDEX_STATUS); + query.PushString(status); + query.PushOp(B_EQ); + query.Fetch(); + + BEntry entry; + if (query.GetNextEntry(&entry) == B_NO_ERROR) + return true; + + return false; +} + diff --git a/src/apps/bemail/Status.h b/src/apps/bemail/Status.h new file mode 100644 index 0000000000..a7676cd073 --- /dev/null +++ b/src/apps/bemail/Status.h @@ -0,0 +1,119 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Status.h +// +//-------------------------------------------------------------------- + +#ifndef _STATUS_H +#define _STATUS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STATUS_WIDTH 220 +#define STATUS_HEIGHT 80 + +#define STATUS_TEXT "Status:" +#define STATUS_FIELD_H 10 +#define STATUS_FIELD_V 8 +#define STATUS_FIELD_WIDTH (STATUS_WIDTH - STATUS_FIELD_H) +#define STATUS_FIELD_HEIGHT 16 + +#define BUTTON_WIDTH 70 +#define BUTTON_HEIGHT 20 + +#define S_OK_BUTTON_X1 (STATUS_WIDTH - BUTTON_WIDTH - 6) +#define S_OK_BUTTON_Y1 (STATUS_HEIGHT - (BUTTON_HEIGHT + 10)) +#define S_OK_BUTTON_X2 (S_OK_BUTTON_X1 + BUTTON_WIDTH) +#define S_OK_BUTTON_Y2 (S_OK_BUTTON_Y1 + BUTTON_HEIGHT) +#define S_OK_BUTTON_TEXT "OK" + +#define S_CANCEL_BUTTON_X1 (S_OK_BUTTON_X1 - (BUTTON_WIDTH + 10)) +#define S_CANCEL_BUTTON_Y1 S_OK_BUTTON_Y1 +#define S_CANCEL_BUTTON_X2 (S_CANCEL_BUTTON_X1 + BUTTON_WIDTH) +#define S_CANCEL_BUTTON_Y2 S_OK_BUTTON_Y2 +#define S_CANCEL_BUTTON_TEXT "Cancel" + +#define INDEX_STATUS "_status" + +enum status_messages { + STATUS = 128, + OK, + CANCEL +}; + +class TStatusView; + +//==================================================================== + +class TStatusWindow : public BWindow +{ + public: + TStatusWindow(BRect, BWindow *, const char *status); + + private: + TStatusView *fView; +}; + +//-------------------------------------------------------------------- + +class TStatusView : public BBox +{ + public: + TStatusView(BRect, BWindow *, const char *); + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage *); + bool Exists(const char *); + + private: + const char *fString; + BTextControl *fStatus; + BWindow *fWindow; +}; + +#endif // #ifndef _STATUS_H diff --git a/src/apps/bemail/Utilities.cpp b/src/apps/bemail/Utilities.cpp new file mode 100644 index 0000000000..879ef5251d --- /dev/null +++ b/src/apps/bemail/Utilities.cpp @@ -0,0 +1,212 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +//-------------------------------------------------------------------- +// +// Utilities.cpp +// +//-------------------------------------------------------------------- + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include "Utilities.h" + + +status_t +WriteAttrString(BNode *node, const char *attr, const char *value) +{ + if (!value) + value = B_EMPTY_STRING; + + ssize_t size = node->WriteAttr(attr, B_STRING_TYPE, 0, value, strlen(value) + 1); + + return size >= 0 ? B_OK : size; +} + + +status_t +ReadAttrString(BNode *node, const char *attr, BString *value) +{ + attr_info attrInfo; + + value->SetTo(""); + status_t status = node->GetAttrInfo(attr, &attrInfo); + if (status < B_OK) + return status; + + ssize_t size = node->ReadAttr(attr, B_STRING_TYPE, 0, value->LockBuffer(attrInfo.size + 1), attrInfo.size); + value->UnlockBuffer(); + + return size >= 0 ? B_OK : size; +} + + +//==================================================================== +// case-insensitive version of strcmp +// + +int32 +cistrcmp(const char *str1, const char *str2) +{ + char c1; + char c2; + int32 len; + int32 loop; + + len = strlen(str1) + 1; + for (loop = 0; loop < len; loop++) + { + c1 = str1[loop]; + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + c2 = str2[loop]; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + if (c1 == c2) + { + } + else if (c1 < c2) + return -1; + else if (c1 > c2 || !c2) + return 1; + } + return 0; +} + + +//==================================================================== +// case-insensitive version of strncmp +// + +int32 +cistrncmp(const char *str1, const char *str2, int32 max) +{ + char c1; + char c2; + int32 loop; + + for (loop = 0; loop < max; loop++) + { + c1 = *str1++; + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + c2 = *str2++; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + if (c1 == c2) + { + } + else if (c1 < c2) + return -1; + else if (c1 > c2 || !c2) + return 1; + } + return 0; +} + + +//-------------------------------------------------------------------- +// case-insensitive version of strstr +// + +char * +cistrstr(const char *cs, const char *ct) +{ + char c1; + char c2; + int32 cs_len; + int32 ct_len; + int32 loop1; + int32 loop2; + + cs_len = strlen(cs); + ct_len = strlen(ct); + for (loop1 = 0; loop1 < cs_len; loop1++) + { + if (cs_len - loop1 < ct_len) + return NULL; + + for (loop2 = 0; loop2 < ct_len; loop2++) + { + c1 = cs[loop1 + loop2]; + if ((c1 >= 'A') && (c1 <= 'Z')) + c1 += ('a' - 'A'); + c2 = ct[loop2]; + if ((c2 >= 'A') && (c2 <= 'Z')) + c2 += ('a' - 'A'); + if (c1 != c2) + goto next; + } + return const_cast(&cs[loop1]); +next: + // label must be followed by a statement + ; + } + return NULL; +} + + +//-------------------------------------------------------------------- +// return length of \n terminated line +// + +int32 +linelen(char *str, int32 len, bool header) +{ + int32 loop; + + for (loop = 0; loop < len; loop++) + { + if (str[loop] == '\n') + { + if (!header || loop < 2 + || (header && str[loop + 1] != ' ' && str[loop + 1] != '\t')) + return loop + 1; + } + } + return len; +} + diff --git a/src/apps/bemail/Utilities.h b/src/apps/bemail/Utilities.h new file mode 100644 index 0000000000..122455be6e --- /dev/null +++ b/src/apps/bemail/Utilities.h @@ -0,0 +1,69 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + + +//-------------------------------------------------------------------- +// +// Utilities.h +// +//-------------------------------------------------------------------- + +#ifndef _UTILITIES_H +#define _UTILITIES_H + +#include + +class BEmailMessage; + +//==================================================================== + +#ifdef __cplusplus + +class BNode; +extern status_t WriteAttrString(BNode *node, const char *attr, const char *value); +extern status_t ReadAttrString(BNode *node, const char *attr, BString *value); + +extern "C" { +#endif + +int32 cistrcmp(const char *, const char *); +int32 cistrncmp(const char *, const char *, int32); +char *cistrstr(const char *, const char *); +int32 linelen(char*, int32, bool); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef _UTILITIES_H diff --git a/src/apps/bemail/WIndex.cpp b/src/apps/bemail/WIndex.cpp new file mode 100644 index 0000000000..0af3836c22 --- /dev/null +++ b/src/apps/bemail/WIndex.cpp @@ -0,0 +1,447 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "WIndex.h" + +#define IVERSION 1 + +static int32 kCRCTable = 0; + +int32 cmp_i_entries( const WIndexEntry *e1, const WIndexEntry *e2 ); +void gen_crc_table(); +unsigned long update_crc( unsigned long crc_accum, const char *data_blk_ptr, int data_blk_size ); + + +FileEntry::FileEntry( void ) +{ + +} + +FileEntry::FileEntry( const char *entryStr ) + : BString( entryStr ) +{ + +} + +status_t WIndex::SetTo( const char *dataPath, const char *indexPath ) +{ + BFile *dataFile; + BFile indexFile; + + dataFile = new BFile(); + + if( dataFile->SetTo( dataPath, B_READ_ONLY ) != B_OK ) + return B_ERROR; + else + { + bool buildIndex = true; + SetTo( dataFile ); + + time_t mtime; + time_t modified; + + dataFile->GetModificationTime( &mtime ); + + if( indexFile.SetTo( indexPath, B_READ_ONLY ) == B_OK ) + { + attr_info info; + if( (indexFile.GetAttrInfo( "WINDEX:version", &info ) == B_NO_ERROR) ) + { + uint32 version = 0; + indexFile.ReadAttr( "WINDEX:version", B_UINT32_TYPE, 0, &version, 4 ); + if( IVERSION == version ) + { + if( (indexFile.GetAttrInfo( "WINDEX:modified", &info ) == B_NO_ERROR) ) + { + indexFile.ReadAttr( "WINDEX:modified", B_UINT32_TYPE, 0, &modified, 4 ); + if( mtime == modified ) + { + if (UnflattenIndex( &indexFile ) == B_OK) + buildIndex = false; + } + } + } + } + indexFile.Unset(); + } + if( buildIndex ) + { + // printf( "Building Index...\n" ); + InitIndex(); + BuildIndex(); + if( indexFile.SetTo( indexPath, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE ) == B_OK ) + { + FlattenIndex( &indexFile ); + indexFile.WriteAttr( "WINDEX:modified", B_UINT32_TYPE, 0, &mtime, 4 ); + uint32 version = IVERSION; + indexFile.WriteAttr( "WINDEX:version", B_UINT32_TYPE, 0, &version, 4 ); + } + } + } + return B_OK; +} + +FileEntry::~FileEntry( void ) +{ + +} + +WIndex::WIndex( int32 count ) +{ + entryList = NULL; + dataFile = NULL; + ePerB = count; + entrySize = sizeof( WIndexEntry ); + if( !atomic_or( &kCRCTable, 1 ) ) + gen_crc_table(); +} + +WIndex::WIndex( BPositionIO *dataFile, int32 count ) +{ + entryList = NULL; + this->dataFile = dataFile; + ePerB = count; + entrySize = sizeof( WIndexEntry ); + if( !atomic_or( &kCRCTable, 1 ) ) + gen_crc_table(); +} + +WIndex::~WIndex( void ) +{ + if( entryList ) + free( entryList ); + delete dataFile; +} + +status_t WIndex::UnflattenIndex( BPositionIO *io ) +{ + if( entryList ) + free( entryList ); + WIndexHead head; + + io->Seek( 0, SEEK_SET ); + io->Read( &head, sizeof( head ) ); + io->Seek( head.offset, SEEK_SET ); + + entrySize = head.entrySize; + entries = head.entries; + maxEntries = ePerB; + blockSize = ePerB * entrySize; + blocks = entries/ePerB+1;; + isSorted = true; + + int32 size = (head.entries+1) * head.entrySize; + if( !(entryList = (uint8 *)malloc( size )) ) + return B_ERROR; + + if( entries ) + io->Read( entryList, size ); + + return B_OK; +} + +status_t WIndex::FlattenIndex( BPositionIO *io ) +{ + if( entries && !isSorted ) + SortItems(); + WIndexHead head; + + head.entries = entries; + head.entrySize = entrySize; + head.offset = sizeof( WIndexHead ); + io->Seek( 0, SEEK_SET ); + io->Write( &head, sizeof( head ) ); + if( entries ) + io->Write( entryList, head.entries * head.entrySize ); + + return B_OK; +} + +int32 WIndex::Lookup( int32 key ) +{ + if( !entries ) + return -1; + if( !isSorted ) + SortItems(); + + // Binary Search + int32 M, Lb, Ub; + Lb = 0; + Ub = entries-1; + while( true ) + { + M = (Lb + Ub)/2; + if( key < ((WIndexEntry *)(entryList+(M*entrySize)))->key ) + Ub = M - 1; + else if(key > ((WIndexEntry *)(entryList+(M*entrySize)))->key ) + Lb = M + 1; + else + return M; + if( Lb > Ub ) + return -1; + } +} + +status_t WIndex::AddItem( WIndexEntry *entry ) +{ + if( BlockCheck() == B_ERROR ) + return B_ERROR; + memcpy( ((WIndexEntry *)(entryList+(entries*entrySize))), entry, entrySize ); + entries++; + isSorted = false; + return B_OK; +} + +void WIndex::SortItems( void ) +{ + qsort( entryList, entries, entrySize, (int (*)(const void *, const void *))cmp_i_entries ); + isSorted = true; + //for( int32 i = 0; i < entries; i++ ) + // printf( "Key = %ld\n", entryList[i].key ); +} + +status_t WIndex::BlockCheck( void ) +{ + if( entries < maxEntries ) + return B_OK; + blocks = entries/ePerB+1; + entryList = (uint8 *)realloc( entryList, blockSize*blocks ); + if( !entryList ) + return B_ERROR; + return B_OK; +} + +status_t WIndex::InitIndex( void ) +{ + if( entryList ) + free( entryList ); + isSorted = 0; + entries = 0; + maxEntries = ePerB; + blockSize = ePerB * entrySize; + blocks = 1; + entryList = (uint8 *)malloc( blockSize ); + if( !entryList ) + return B_ERROR; + return B_OK; +} + +int32 WIndex::GetKey( const char *s ) +{ + + int32 key = 0; + /*int32 x; + int32 a = 84589; + int32 b = 45989; + int32 m = 217728; + while( *s ) + { + x = *s++ - 'a'; + + key ^= (a*x + b) % m; + key <<= 1; + }*/ + + key = update_crc( 0, s, strlen(s) ); + + if( key < 0 ) // No negavite values! + key = ~key; + + return key; +} + +int32 cmp_i_entries( const WIndexEntry *e1, const WIndexEntry *e2 ) +{ + return e1->key - e2->key; +} + +status_t WIndex::SetTo( BPositionIO *dataFile ) +{ + this->dataFile = dataFile; + return B_OK; +} + +void WIndex::Unset( void ) +{ + dataFile = NULL; +} + +int32 WIndex::FindFirst( const char *word ) +{ + if( !entries ) + return -1; + + int32 index; + char nword[256]; + int32 key; + + NormalizeWord( word, nword ); + key = GetKey( nword ); + + if( (index = Lookup( key )) < 0 ) + return -1; + // Find first instance of key + while( (ItemAt( index-1 ))->key == key ) + index--; + return index; +} + +FileEntry *WIndex::GetEntry( int32 index ) +{ + if( (index >= entries)||(index < 0) ) + return NULL; + WIndexEntry *ientry; + FileEntry *dentry; + char *buffer; + + dentry = new FileEntry(); + + ientry = ItemAt( index ); + + int32 size; + + dataFile->Seek( ientry->offset, SEEK_SET ); + buffer = dentry->LockBuffer( 256 ); + dataFile->Read( buffer, 256 ); + size = GetEntrySize( ientry, buffer ); + //buffer[256] = 0; + //printf( "Entry: = %s\n", buffer ); + dentry->UnlockBuffer( size ); + return dentry; +} + +size_t WIndex::GetEntrySize( WIndexEntry *entry, const char *entryData ) +{ + // eliminate unused parameter warning + (void)entry; + + return strcspn( entryData, "\n\r" ); +} + +FileEntry *WIndex::GetEntry( const char *word ) +{ + return GetEntry( FindFirst( word ) ); +} + +char *WIndex::NormalizeWord( const char *word, char *dest ) +{ + const char *src; + char *dst; + + // remove dots and copy + src = word; + dst = dest; + while( *src ) + { + if( *src != '.' ) + *dst++ = *src; + src++; + } + *dst = 0; + + // convert to lower-case + dst = dest; + while( *dst ) + *dst++ = tolower( *dst ); + return dest; +} + +/* crc32h.c -- package to compute 32-bit CRC one byte at a time using */ +/* the high-bit first (Big-Endian) bit ordering convention */ +/* */ +/* Synopsis: */ +/* gen_crc_table() -- generates a 256-word table containing all CRC */ +/* remainders for every possible 8-bit byte. It */ +/* must be executed (once) before any CRC updates. */ +/* */ +/* unsigned update_crc(crc_accum, data_blk_ptr, data_blk_size) */ +/* unsigned crc_accum; char *data_blk_ptr; int data_blk_size; */ +/* Returns the updated value of the CRC accumulator after */ +/* processing each byte in the addressed block of data. */ +/* */ +/* It is assumed that an unsigned long is at least 32 bits wide and */ +/* that the predefined type char occupies one 8-bit byte of storage. */ +/* */ +/* The generator polynomial used for this version of the package is */ +/* x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0 */ +/* as specified in the Autodin/Ethernet/ADCCP protocol standards. */ +/* Other degree 32 polynomials may be substituted by re-defining the */ +/* symbol POLYNOMIAL below. Lower degree polynomials must first be */ +/* multiplied by an appropriate power of x. The representation used */ +/* is that the coefficient of x^0 is stored in the LSB of the 32-bit */ +/* word and the coefficient of x^31 is stored in the most significant */ +/* bit. The CRC is to be appended to the data most significant byte */ +/* first. For those protocols in which bytes are transmitted MSB */ +/* first and in the same order as they are encountered in the block */ +/* this convention results in the CRC remainder being transmitted with */ +/* the coefficient of x^31 first and with that of x^0 last (just as */ +/* would be done by a hardware shift register mechanization). */ +/* */ +/* The table lookup technique was adapted from the algorithm described */ +/* by Avram Perez, Byte-wise CRC Calculations, IEEE Micro 3, 40 (1983).*/ + +#define POLYNOMIAL 0x04c11db7L + +static unsigned long crc_table[256]; + +void gen_crc_table() + /* generate the table of CRC remainders for all possible bytes */ + { register int i, j; register unsigned long crc_accum; + for ( i = 0; i < 256; i++ ) + { crc_accum = ( (unsigned long) i << 24 ); + for ( j = 0; j < 8; j++ ) + { if ( crc_accum & 0x80000000L ) + crc_accum = + ( crc_accum << 1 ) ^ POLYNOMIAL; + else + crc_accum = + ( crc_accum << 1 ); } + crc_table[i] = crc_accum; } + return; } + +unsigned long update_crc( unsigned long crc_accum, const char *data_blk_ptr, int data_blk_size ) + /* update the CRC on the data block one byte at a time */ + { register int i, j; + for ( j = 0; j < data_blk_size; j++ ) + { i = ( (int) ( crc_accum >> 24) ^ *data_blk_ptr++ ) & 0xff; + crc_accum = ( crc_accum << 8 ) ^ crc_table[i]; } + return crc_accum; } + diff --git a/src/apps/bemail/WIndex.h b/src/apps/bemail/WIndex.h new file mode 100644 index 0000000000..111aa843f8 --- /dev/null +++ b/src/apps/bemail/WIndex.h @@ -0,0 +1,106 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _WORD_INDEX_H +#define _WORD_INDEX_H + +#include +#include + +struct WIndexHead { + int32 entries; + int32 entrySize; + int32 offset; +}; + +struct WIndexEntry { + int32 key; + int32 offset; +}; + +class FileEntry : public BString { +public: + FileEntry(void); + FileEntry(const char *entryStr); + virtual ~FileEntry(void); +}; + +class WIndex { +public: + WIndex(BPositionIO *dataFile, int32 count = 100); + WIndex(int32 count = 100); + virtual ~WIndex(void); + + status_t InitIndex(void); + status_t UnflattenIndex(BPositionIO *io); + status_t FlattenIndex(BPositionIO *io); + + int32 Lookup(int32 key); + + inline WIndexEntry *ItemAt(int32 index) + { return (WIndexEntry *)(entryList+(index*entrySize)); } + status_t AddItem(WIndexEntry *entry); + inline int32 CountItems(void) + { return entries; } + void SortItems(void); + + virtual int32 GetKey(const char *s); + virtual char *NormalizeWord(const char *word, char *dest); + + status_t SetTo(BPositionIO *dataFile); + status_t SetTo(const char *dataPath, const char *indexPath); + void Unset(void); + + virtual status_t BuildIndex(void) = 0; + + virtual int32 FindFirst(const char *word); + virtual FileEntry *GetEntry(int32 index); + FileEntry *GetEntry(const char *word); + +protected: + status_t BlockCheck(void); + virtual size_t GetEntrySize(WIndexEntry *entry, const char *entryData); + + int32 entrySize; + int32 entries; + int32 maxEntries; + int32 ePerB; + int32 blockSize; + int32 blocks; + bool isSorted; + uint8 *entryList; + BPositionIO *dataFile; +}; + +#endif // #ifndef _WORD_INDEX_H diff --git a/src/apps/bemail/Words.cpp b/src/apps/bemail/Words.cpp new file mode 100644 index 0000000000..62c6b169b3 --- /dev/null +++ b/src/apps/bemail/Words.cpp @@ -0,0 +1,864 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2001, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include "Words.h" + +/* +** File METAPHON.C +*/ + +/* +** MAXMETAPH is the length of the Metaphone code. +** +** Four is a good compromise value for English names. For comparing words +** which are not names or for some non-English names, use a longer code +** length for more precise matches. +** +** The default here is 5. +*/ + +#define MAXMETAPH 6 + +static const char *gCmpKey; +static int word_cmp( BString **firstArg, BString **secondArg ); + +static int word_cmp( BString **firstArg, BString **secondArg ) +{ + return word_match( gCmpKey, (*firstArg)->String() ) - word_match( gCmpKey, (*secondArg)->String() ); +} + +Words::Words( bool useMetaphone ) + : fUseMetaphone( useMetaphone ) +{ + +} + +Words::Words( BPositionIO *thes, bool useMetaphone ) + : WIndex( thes ), + fUseMetaphone( useMetaphone ) +{ + +} + +Words::~Words( void ) +{ + +} + +Words::Words( const char *dataPath, const char *indexPath, bool useMetaphone ) + : fUseMetaphone( useMetaphone ) +{ + if( !useMetaphone ) + entrySize = sizeof( uint32 ); + SetTo( dataPath, indexPath ); +} + +enum +{ + FIND_WORD, + GET_WORD, + GET_FLAGS +}; + +// Parse the Words file... +status_t Words::BuildIndex( void ) +{ + // Buffer Stuff + char buffer[16384]; + char *nptr, *eptr; + int64 blockOffset; + int32 blockSize; + + // The Word Entry + WIndexEntry entry; + char entryName[256], *namePtr = entryName; + char suffixName[256]; + char flags[32], *flagsPtr = flags; + + // State Info + int32 state = FIND_WORD; + + // Make sure we are at start of file + dataFile->Seek( 0, SEEK_SET ); + entry.offset = -1; + + // Read blocks from thes until eof + while( true ) + { + // Get next block + blockOffset = dataFile->Position(); + if( (blockSize = dataFile->Read( buffer, 16384 )) == 0 ) + break; + + // parse block + for( nptr = buffer, eptr = buffer + blockSize; nptr < eptr; nptr++ ) + { + // Looking for start of word? + if( state == FIND_WORD ) + { + // Is start of word? + if( isalpha(*nptr) ) + { + state = GET_WORD; + *namePtr++ = *nptr; // copy word + entry.offset = blockOffset + (nptr - buffer); + } + else + entry.offset++; + } + // End of word? + else if( (*nptr == '\n')||(*nptr == '\r') ) + { + if( namePtr != entryName ) + { + // Add previous entry to word index + *namePtr = 0; // terminate word + *flagsPtr = 0; // terminate flags + NormalizeWord( entryName, entryName ); + // Add base word + entry.key = GetKey( entryName ); + AddItem( &entry ); + + // Add suffixed words if any + if( flagsPtr != flags ) + { + //printf( "Base: %s, flags: %s\n", entryName, flags ); + for( flagsPtr=flags; *flagsPtr != 0; flagsPtr++ ) + { + if( suffix_word( suffixName, entryName, *flagsPtr ) ) + { + //printf( "Suffix: %s\n", suffixName ); + entry.key = GetKey( suffixName ); + AddItem( &entry ); + } + } + } + } + // Init new entry + state = FIND_WORD; + namePtr = entryName; + flagsPtr = flags; + } + else if( state == GET_WORD ) // Are we looking for a word? + { + // Start of flags? + if( *nptr == '/' ) + { + *namePtr = 0; // terminate word + //printf( "Found word: %s\n", entryName ); + + // Set state to get flags + state = GET_FLAGS; + } + else + *namePtr++ = *nptr; // copy word + } + else if( state == GET_FLAGS ) // Are we getting the flags? + *flagsPtr++ = *nptr; // copy flag + } // End for( nptr = buffer, eptr = buffer + blockSize; nptr < eptr; nptr++, entry.size++ ) + } // End while( true ) + + SortItems(); + return B_OK; +} + +/* +** Character coding array +*/ + +static char vsvfn[26] = { + 1,16,4,16,9,2,4,16,9,2,0,2,2,2,1,4,0,2,4,4,1,0,0,0,8,0}; +/* 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 */ + +int32 Words::GetKey( const char *s ) +{ + if( fUseMetaphone ) + { + char Metaph[12]; + const char *sPtr; + int32 key = 0; + int32 offset; + char c; + + metaphone( s, Metaph, GENERATE ); + // Compact Metaphone from 6 bytes to 4 + + // printf( "%s -> %s: \n", s, Metaph ); + + for( sPtr = Metaph, offset = 25; *sPtr; sPtr++, offset -= 5 ) + { + c = *sPtr - 'A'; + // printf( "%d,", int16(c) ); + key |= int32(c) << offset; + } + for( ; offset >= 0; offset -= 5 ) + key |= int32(31) << offset; + // printf( ": %ld\n", key ); + return key; + } + else + return WIndex::GetKey( s ); +} + +/* +** Macros to access the character coding array +*/ + +#define vowel(x) (vsvfn[(x) - 'A'] & 1) /* AEIOU */ +#define same(x) (vsvfn[(x) - 'A'] & 2) /* FJLMNR */ +#define varson(x) (vsvfn[(x) - 'A'] & 4) /* CGPST */ +#define frontv(x) (vsvfn[(x) - 'A'] & 8) /* EIY */ +#define noghf(x) (vsvfn[(x) - 'A'] & 16) /* BDH */ +#define NUL '\0' +/* +** metaphone() +** +** Arguments: 1 - The word to be converted to a metaphone code. +** 2 - A MAXMETAPH+1 char field for the result. +** 3 - Function flag: +** If 0: Compute the Metaphone code for the first argument, +** then compare it to the Metaphone code passed in +** the second argument. +** If 1: Compute the Metaphone code for the first argument, +** then store the result in the area pointed to by the +** second argument. +** +** Returns: If function code is 0, returns Success_ for a match, else Error_. +** If function code is 1, returns Success_. +*/ + +bool metaphone(const char *Word, char *Metaph, metaphlag Flag) +{ + char *n, *n_start, *n_end; /* Pointers to string */ + char *metaph = NULL, *metaph_end; /* Pointers to metaph */ + char ntrans[512]; /* Word with uppercase letters */ + char newm[MAXMETAPH + 4]; /* New metaph for comparison */ + int KSflag; /* State flag for X translation */ + + /* + ** Copy word to internal buffer, dropping non-alphabetic characters + ** and converting to upper case. + */ + + for (n = ntrans + 1, n_end = ntrans + sizeof(ntrans) - 2; + *Word && n < n_end; ++Word) + { + if (isalpha(*Word)) + *n++ = toupper(*Word); + } + + if (n == ntrans + 1) + return false; /* Return if zero characters */ + else n_end = n; /* Set end of string pointer */ + + /* + ** Pad with NULs, front and rear + */ + + *n++ = NUL; + *n = NUL; + n = ntrans; + *n++ = NUL; + + /* + ** If doing comparison, redirect pointers + */ + + if (COMPARE == Flag) + { + metaph = Metaph; + Metaph = newm; + } + + /* + ** Check for PN, KN, GN, WR, WH, and X at start + */ + + switch (*n) + { + case 'P': + case 'K': + case 'G': + if ('N' == *(n + 1)) + *n++ = NUL; + break; + + case 'A': + if ('E' == *(n + 1)) + *n++ = NUL; + break; + + case 'W': + if ('R' == *(n + 1)) + *n++ = NUL; + else if ('H' == *(n + 1)) + { + *(n + 1) = *n; + *n++ = NUL; + } + break; + + case 'X': + *n = 'S'; + break; + } + + /* + ** Now loop through the string, stopping at the end of the string + ** or when the computed Metaphone code is MAXMETAPH characters long. + */ + + KSflag = false; /* State flag for KStranslation */ + for (metaph_end = Metaph + MAXMETAPH, n_start = n; + n <= n_end && Metaph < metaph_end; ++n) + { + if (KSflag) + { + KSflag = false; + *Metaph++ = *n; + } + else + { + /* Drop duplicates except for CC */ + + if (*(n - 1) == *n && *n != 'C') + continue; + + /* Check for F J L M N R or first letter vowel */ + + if (same(*n) || (n == n_start && vowel(*n))) + *Metaph++ = *n; + else switch (*n) + { + case 'B': + if (n < n_end || *(n - 1) != 'M') + *Metaph++ = *n; + break; + + case 'C': + if (*(n - 1) != 'S' || !frontv(*(n + 1))) + { + if ('I' == *(n + 1) && 'A' == *(n + 2)) + *Metaph++ = 'X'; + else if (frontv(*(n + 1))) + *Metaph++ = 'S'; + else if ('H' == *(n + 1)) + *Metaph++ = ((n == n_start && + !vowel(*(n + 2))) || + 'S' == *(n - 1)) ? 'K' : 'X'; + else *Metaph++ = 'K'; + } + break; + + case 'D': + *Metaph++ = ('G' == *(n + 1) && frontv(*(n + 2))) ? + 'J' : 'T'; + break; + + case 'G': + if ((*(n + 1) != 'H' || vowel(*(n + 2))) && + (*(n + 1) != 'N' || ((n + 1) < n_end && + (*(n + 2) != 'E' || *(n + 3) != 'D'))) && + (*(n - 1) != 'D' || !frontv(*(n + 1)))) + { + *Metaph++ = (frontv(*(n + 1)) && + *(n + 2) != 'G') ? 'J' : 'K'; + } + else if ('H' == *(n + 1) && !noghf(*(n - 3)) && + *(n - 4) != 'H') + { + *Metaph++ = 'F'; + } + break; + + case 'H': + if (!varson(*(n - 1)) && (!vowel(*(n - 1)) || + vowel(*(n + 1)))) + { + *Metaph++ = 'H'; + } + break; + + case 'K': + if (*(n - 1) != 'C') + *Metaph++ = 'K'; + break; + + case 'P': + *Metaph++ = ('H' == *(n + 1)) ? 'F' : 'P'; + break; + + case 'Q': + *Metaph++ = 'K'; + break; + + case 'S': + *Metaph++ = ('H' == *(n + 1) || ('I' == *(n + 1) && + ('O' == *(n + 2) || 'A' == *(n + 2)))) ? + 'X' : 'S'; + break; + + case 'T': + if ('I' == *(n + 1) && ('O' == *(n + 2) || + 'A' == *(n + 2))) + { + *Metaph++ = 'X'; + } + else if ('H' == *(n + 1)) + *Metaph++ = 'O'; + else if (*(n + 1) != 'C' || *(n + 2) != 'H') + *Metaph++ = 'T'; + break; + + case 'V': + *Metaph++ = 'F'; + break; + + case 'W': + case 'Y': + if (vowel(*(n + 1))) + *Metaph++ = *n; + break; + + case 'X': + if (n == n_start) + *Metaph++ = 'S'; + else + { + *Metaph++ = 'K'; + KSflag = true; + } + break; + + case 'Z': + *Metaph++ = 'S'; + break; + } + } + + /* + ** Compare new Metaphone code with old + */ + + if (COMPARE == Flag && + *(Metaph - 1) != metaph[(Metaph - newm) - 1]) + { + return false; + } + } + + /* + ** If comparing, check if Metaphone codes were equal in length + */ + + if (COMPARE == Flag && metaph[Metaph - newm]) + return false; + + *Metaph = NUL; + return true; +} + +int word_match( const char *reference, const char *test ) +{ + const char *s1, *s2; + int32 x = 0; + char c1, c2; + s1 = test; + s2 = reference; + + bool a, b; + + while( *s2 || *s1 ) + { + c1 = tolower(*s1); + c2 = tolower(*s2); + + if( *s2 && *s1 ) + { + if( c1 != c2 ) + { + a = (tolower(s1[1]) == c2); + b = (tolower(s2[1]) == c1); + // Reversed pair + if( a && b ) + { + x += 1; + s1++; + s2++; + } + // Extra character + if( a ) + { + x += 1; + s1++; + } + // Missing Character + else if( b ) + { + x += 1; + s2++; + } + // Equivalent Character + else if( vsvfn[c1] == vsvfn[c2] ) + x++; + // Unrelated Character + else + x += 3; + } + } + else + x += 1; + if( *s2 ) + s2++; + if( *s1 ) + s1++; + } + + return x; +} + +int32 suffix_word( char *dst, const char *src, char flag ) +{ + char *end; + + end = stpcpy( dst, src ); + flag = toupper( flag ); + switch( flag ) + { + case 'V': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "ive" ); + break; + default: + end = stpcpy( end, "ive" ); + break; + } + break; + case 'N': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "ion" ); + break; + case 'y': + end = stpcpy( end-1, "ication" ); + break; + default: + end = stpcpy( end, "en" ); + break; + } + break; + case 'X': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "ions" ); + break; + case 'y': + end = stpcpy( end-1, "ications" ); + break; + default: + end = stpcpy( end, "ens" ); + break; + } + break; + case 'H': + switch( end[-1] ) + { + case 'y': + end = stpcpy( end-1, "ieth" ); + break; + default: + end = stpcpy( end, "th" ); + break; + } + break; + case 'Y': + end = stpcpy( end, "ly" ); + break; + case 'G': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "ing" ); + break; + default: + end = stpcpy( end, "ing" ); + break; + } + break; + case 'J': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "ings" ); + break; + default: + end = stpcpy( end, "ings" ); + break; + } + break; + case 'D': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "ed" ); + break; + case 'y': + if( !strchr( "aeiou", end[-2] ) ) + { + end = stpcpy( end-1, "ied" ); + break; + } + // Fall through + default: + end = stpcpy( end, "ed" ); + break; + } + break; + case 'T': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "est" ); + break; + case 'y': + if( !strchr( "aeiou", end[-2] ) ) + { + end = stpcpy( end-1, "iest" ); + break; + } + // Fall through + default: + end = stpcpy( end, "est" ); + break; + } + break; + case 'R': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "er" ); + break; + case 'y': + if( !strchr( "aeiou", end[-2] ) ) + { + end = stpcpy( end-1, "ier" ); + break; + } + // Fall through + default: + end = stpcpy( end, "er" ); + break; + } + break; + case 'Z': + switch( end[-1] ) + { + case 'e': + end = stpcpy( end-1, "ers" ); + break; + case 'y': + if( !strchr( "aeiou", end[-2] ) ) + { + end = stpcpy( end-1, "iers" ); + break; + } + // Fall through + default: + end = stpcpy( end, "ers" ); + break; + } + break; + case 'S': + switch( end[-1] ) + { + case 's': + case 'x': + case 'z': + case 'h': + end = stpcpy( end, "es" ); + break; + case 'y': + if( !strchr( "aeiou", end[-2] ) ) + { + end = stpcpy( end-1, "ies" ); + break; + } + // Fall through + default: + end = stpcpy( end, "s" ); + break; + } + break; + case 'P': + switch( end[-1] ) + { + case 'y': + if( !strchr( "aeiou", end[-2] ) ) + { + end = stpcpy( end-1, "iness" ); + break; + } + // Fall through + default: + end = stpcpy( end, "ness" ); + break; + } + break; + case 'M': + end = stpcpy( end, "'s" ); + break; + default: + return 0; + } + return end-dst; +} + +int32 Words::FindBestMatches( BList *matches, const char *s ) +{ + int32 index; + // printf( "*** Looking for %s: ***\n", s ); + + if( (index = FindFirst( s )) >= 0 ) + { + BString srcWord( s ); + FileEntry *entry; + WIndexEntry *indexEntry; + + int32 key = (ItemAt( index ))->key; + int32 suffixLength; + char word[128], suffixWord[128]; + const char *src, *testWord; + const char *suffixFlags; + char *dst; + + gCmpKey = srcWord.String(); + + uint8 hashTable[32]; + uint8 hashValue, highHash, lowHash; + for( int32 i=0; i<32; i++ ) + hashTable[i] = 0; + + do + { + indexEntry = ItemAt( index ); + // Hash the entry offset; we use this to make sure we don't add + // the same word file entry twice; + // It is possible for the same entry in the words file to have + // multiple entries in the index. + + hashValue = indexEntry->offset % 256; + highHash = hashValue >> 3; + lowHash = 0x01 << (hashValue & 0x07); + + //printf( "Testing Entry: %ld: hash=%d, highHash=%d, lowHash=%d\n", indexEntry->offset, hashValue, (uint16)highHash, (uint16)lowHash ); + + // Has this entry offset been seen before? + if( !(hashTable[highHash] & lowHash) ) + { + //printf( "New Entry\n" ); + hashTable[highHash] |= lowHash; // Mark this offset so we don't add it twice + + entry = GetEntry( index ); + src = entry->String(); + while( *src && !isalpha(*src) ) + src++; + dst = word; + while( *src && *src != '/' ) + *dst++ = *src++; + *dst = 0; + if( *src == '/' ) + suffixFlags = src+1; + else + suffixFlags = src; + + //printf( "Base Word: %s\n", word ); + //printf( "Flags: %s\n", suffixFlags ); + testWord = word; // Test the base word first + do + { + //printf( "Testing: %s\n", testWord ); + // Does this word match the key + if( (GetKey( testWord ) == key) && + // And does it look close enough to the compare key? + //word_match( gCmpKey, testWord ) <= int32((strlen( gCmpKey )-1)/2) ) + word_match( gCmpKey, testWord ) <= int32(float(strlen( gCmpKey )-1)*.75) ) + { + //printf( "Added: %s\n", testWord ); + matches->AddItem( (void *)(new BString( testWord )) ); // Add it to the list + } + + // If suffix, transform and test + if( *suffixFlags ) + { + // Repeat until valid suffix found or end is reached + suffixLength = 0; + while( *suffixFlags && !(suffixLength=suffix_word( suffixWord, word, *suffixFlags++ )) ) {} + if( suffixLength ) + testWord = suffixWord; + else + testWord = NULL; + } + else + testWord = NULL; + }while( testWord ); + delete entry; + } + //else + //printf( "Redundant entry\n" ); + index++; + }while( key == (ItemAt( index ))->key ); + + return matches->CountItems(); + } + else + return 0; +} + +void sort_word_list( BList *matches, const char *reference ) +{ + if( matches->CountItems() > 0 ) + { + BString srcWord( reference ); + gCmpKey = srcWord.String(); + matches->SortItems( (int (*)(const void *, const void *))word_cmp ); + } +} diff --git a/src/apps/bemail/Words.h b/src/apps/bemail/Words.h new file mode 100644 index 0000000000..0e3e442b80 --- /dev/null +++ b/src/apps/bemail/Words.h @@ -0,0 +1,69 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ +#ifndef _WORDS_H +#define _WORDS_H + +#include + +#include "WIndex.h" + +typedef enum { + COMPARE, + GENERATE +} metaphlag; + +class Words; + +bool metaphone(const char *Word, char *Metaph, metaphlag Flag); +int word_match(const char *reference, const char *test); +int32 suffix_word(char *dst, const char *src, char flag); +void sort_word_list(BList *matches, const char *reference); + +class Words : public WIndex { +public: + Words(bool useMetaphone = true); + Words(BPositionIO *thes, bool useMetaphone = true); + Words(const char *dataPath, const char *indexPath, bool useMetaphone); + virtual ~Words(void); + + virtual status_t BuildIndex(void); + virtual int32 GetKey(const char *s); + + int32 FindBestMatches(BList *matches, const char *word); + +protected: + bool fUseMetaphone; +}; + +#endif // #ifndef _WORDS_H diff --git a/src/apps/bemail/geekspeak b/src/apps/bemail/geekspeak new file mode 100644 index 0000000000..8e09c9fe00 --- /dev/null +++ b/src/apps/bemail/geekspeak @@ -0,0 +1,427 @@ +ACLU +ARPA +ARPAnet +Al/M +Allan +Arve +Barta +Be's +BeBook +BeBox +BeBoxen +BeEurope +BeMail +BeNews +BeOS/M +Beaulieu +Benoit +Berkey +Boudreau +Budko +CISC +CNN/M +CPUs +Celeron +Chinn +CodeWarrior +Cruz +Darwinism +Debian +Dilbert +Dmitriy +Dvorak +Ethernet +FAQ/S +FTP/S +Ficus +FreeBSD/M +GNUPro +Gamelsky +Gassee +GeekPort +Geiselbrecht +Giampaolo +Haberlach +Herold +Hjonnevag +HoHos +Intel/M +Internet/M +Jedi +Jews +Katz/M +Kerberos +Landrum +Libertarians +Linux/M +LinuxPPC +Lockheimer +Luddite/S +MIME +MacOS +Macs +Mafia +Mani +Maui +Meghan +Metrowerks +Meurillon +Michelle +Microbrew +Microsoft/M +Microworkz +Mikol +Mitnick/M +Morressey +NetBIOS +NetBSD/M +NetPositive +Netscape/M +Netware +Novell/M +OSes +OpenGL +Oreos +Owen +PC +PCs +Patel +Pavel +Perl +Playboy/SM +Polic +PowerPC +Puritans +Queru +RISC +Raynaud +Riva +Rupa +Ryon +Sackett +Sakoman +Sams +Schillings +Schitzophrenia +Schott +Schrenk +Seidenfeld +Serbian +Siggraph +Silverado +SimCity +Slackware +Slashdot/GDRZM +Solaris/M +SoundBlaster +Stallman/M +Swetland +Switkin +Taiwan's +TokenRing +Torvalds +Treckers +Treckies +Trevor +Trey +Trimble +Tsou +Twinkies +USB +Utilitarianisim +Varadarajan +Veyne +Watte +WinAmp +WinNT +XFree +Xenix +ZDNet +Ziff/M +admin/S +app/S +asshole/S +backdoor/S +beerware +binaries +bios +bitched +bitching +blah +boxen +brainer +browsed +browser/S +bumpy +cached +caching +casinos +cgi +children's +clueless/P +com +conferencing +conglomerates +constricting +coworkers +crappy +credentials +criminalize/GDS +criminology +crypto +cyber +cyberspace +daft +daftness +daiquiri +debunked +debunker/S +debunking +defamation +defamatory +defame +defector/S +demo/GD +demonize/GRZS +desktop +despotism +developer's +dibs +disinformation +disrepute +doh +dork/YGDTSP +dorm +downgraded +download/GDRZS +downtime +drivel +editable +edu +elitist/S +encroaches +encroaching +entrails +eon/S +espresso +evangelisim +evangelize +exp +extradition +fascists +fax/GDRS +fed/S +fileserver/S +flashback/S +freeware +ftp +ftpd +fuck/GDRZ +gameplay +gamers +garnering +gcc +geek/YTRSPM +genocidal +genocide +gif/S +gigabit/S +gigabyte/S +glitzy +gloated +gloating +gloats +gov +gov't +grainy +grasshopper +grokking +guestbook/S +gurus +hallucination/S +hardball +herbal +homogenize/GDS +htm +html +http +httpd +hype/GDS +hyperbole +idealism +idealist/S +ideologies +impairment +impeached +impeaching +indecency +indecently +inetd +infidelity +info +intro/S +irresponsibility +isolationism +javascript/S +jocky/S +jpeg/S +judgement/VS +kiddies +kudos +laptop/S +lawmakers +lemming/S +lethargic +login/S +logo/S +loonies +loons +makefile/S +malloc/S +manageably +marketshare +megabit/S +megaphone/S +messageboard/MS +messaging +meta +microkernel/SM +micrometer/S +microwave/S +mindshare +misbehaved +misinterpret/VNXGD +misstate/GDS +misstatement/S +moderator/S +moebius +monoculture +moronic +morons +multimedia +multitask/GDS +multithread/GD +multiuser +mutex +namespace +nanokernel/SM +nanometer/S +neo +nerd/YTRSPM +netizen/S +newsgroup/S +nudity +nuke/GDS +numbingly +nuptials +nutcase/S +obsessed +offensives +offline +org +overblown +overdose/GDS +overgeneralization/S +oversimplification/S +painless +perlscript/S +pgp +phreak/YGDP +phsychopath/S +picometer/S +pinging +pings +pissed +pissing +plagiarize/GDRZ +plaintext +plugin/S +plutocracy +plutocrat/S +plutocratic +polarization +polarize/GDS +popcorn +porn +ported +pre +provoking +psycological +rapist/S +reboot/GDS +reconstitution +redirect/VGDS +refrigeration +regurgitate/NXGDS +reinstall/SDG +resend +residency/S +scalability +scalper/GSD +schitzoid/S +scripting +shapeshift/GDRS +shareware +shit/YGDTRSP +shootout/S +shtml +sig/S +snacks +snowcrash +soapbox/S +sodas +spam/GDRZ +spec/S +spinlock/S +src +stat/S +su +submitter +symlink +sync/GDS +sys +sysadmin +tamagotchi +techies +techno +techs +telecom +telnet +telnetd +toothless +touted +touting +traceroute +trolling +turd/S +tweaked +tweaking +txt +uber +uh +unconstitutional/Y +understate/NGDS +understatement/S +unethical/Y +unices +unprepared +upcoming +upload/GDRZS +uptime +usenet/M +vampire/S +videos +vs +warez +website/S +weenie/S +whiteboard +workstation/S +www +xenophobes +xenophobic +yadda +yahoo/GDSM +zealots +ziped +zipping diff --git a/src/apps/bemail/words b/src/apps/bemail/words new file mode 100644 index 0000000000..88dc2aac77 --- /dev/null +++ b/src/apps/bemail/words @@ -0,0 +1,28137 @@ +ABA +AC +ACM +AMA +ANSI +ARPA +ASTM +Aaron +Ababa +Abbott +Abe +Abel +Abelian +Abelson +Aberdeen +Abernathy +Abidjan +Abigail +Abner +Abo +Abraham +Abram +Abramson +Abyssinia +Acadia +Acapulco +Accra +Ackerman +Ackley +Actaeon +Acton +Ada +Adair +Adam/S +Adamson +Addis +Addison +Addressograph +Adelaide +Adele +Adelia +Aden +Adirondack +Adkins +Adler +Adolph +Adolphus +Adonis +Adrian +Adriatic +Adrienne +Aegean +Aeneas +Aeneid +Aeolus +Aerobacter +Aeschylus +Agamemnon +Agatha +Agee +Agnes +Agnew +Agricola +Agway +Ahmadabad +Aida +Aiken +Ainu +Aires +Aitken +Ajax +Akers +Akron +Alameda +Alamo +Albany +Alberich +Albert +Alberta +Alberto +Albrecht +Albright +Albuquerque +Alcestis +Alcmena +Alcoa +Alcott +Aldebaran +Aldrich +Alec +Aleck +Alex +Alexander +Alexandra +Alexandre +Alexandria +Alexei +Alexis +Alfred +Alfredo +Algenib +Alger +Algiers +Algonquin +Alhambra +Alice +Alicia +Alison +Alistair +Allegheny +Allegra +Allen +Allentown +Allis +Allison +Allstate +Allyn +Almaden +Alpert +Alpheratz +Alphonse +Alsatian +Alsop +Altair +Alton +Alva +Alvarez +Alvin +Amadeus +Amarillo +Amerada +Americanism +Ames +Amherst +Amman +Ammerman +Amoco +Amos +Ampex +Anabel +Anaheim +Anatole +Andean +Andersen +Anderson +Andes +Andover +Andre +Andrea +Andrei +Andrew/S +Andromache +Andromeda +Andy +Angela +Angeles +Angelica +Angelina +Angeline +Angelo +Angie +Anglo +Angora +Angus +Anheuser +Anita +Ankara +Ann +Anna +Annale +Annalen +Annapolis +Anne +Annie +Anselm +Anselmo +Antaeus +Antares +Anthony +Antietam +Antigone +Antioch +Antoine +Antoinette +Anton +Antonio +Antony +Appian +Appleby +Appleton +Aquila +Aquinas +Araby +Arachne +Arcadia +Archibald +Archimedes +Arcturus +Arden +Arequipa +Ares +Argive +Argonne +Argus +Ariadne +Aristotelean +Arkansan +Arlen +Arlene +Arlington +Armata +Armco +Armonk +Arnold +Arragon +Arrhenius +Arthur +Artie +Arturo +Aruba +Asheville +Ashland +Ashley +Ashmolean +Asilomar +Assam +Assyria +Astarte +Astor +Astoria +Asuncion +Atalanta +Atchison +Athabascan +Atkins +Atkinson +Atlanta +Atlantica +Atlantis +Atreus +Atropos +Attica +Atwater +Atwood +Auberge +Aubrey +Audrey +Auerbach +Aug +Augean +Augustan +Augustine +Augustus +Aurelius +Auriga +Auschwitz +Australis +Aventine +Avery +Avesta +Avis +Aviv +Avogadro +Avon +Ayers +Aylesbury +Azerbaijan +Aztec +Aztecan +BEMA +BMW +BP +BSTJ +BTL +Babcock +Babylon +Babylonian +Bacchus +Baden +Baffin +Baghdad +Bagley +Bahama +Bahrein +Bailey +Baird +Bakelite +Bakersfield +Bakhtiari +Baku +Balboa +Baldwin +Balfour +Bali +Balinese +Ballard +Baltimore +Baltimorean +Balzac +Bamako +Bamberger +Bambi +Banbury +Bangor +Bangui +Baptiste +Barbara +Barbour +Barcelona +Barclay +Barlow +Barnabas +Barnard +Barnes +Barnet +Barnett +Barney +Barnhard +Barr +Barrett +Barrington +Barry +Barrymore +Barstow +Barth +Bartholomew +Bartlett +Bartok +Barton +Basel +Bassett +Batavia +Batchelder +Bateman +Bates +Bathurst +Bator +Battelle +Baudelaire +Bauer +Bauhaus +Bausch +Bavaria +Baxter +Bayda +Bayesian +Baylor +Bayonne +Bayport +Bayreuth +Beardsley +Beatrice +Beaujolais +Beaumont +Beauregard +Bechtel +Becker +Beckman +Becky +Bedford +Beebe +Beecham +Beirut +Bela +Belfast +Belgrade +Bella +Bellamy +Bellatrix +Bellingham +Bellini +Belmont +Beloit +Belshazzar +Beltsville +Ben +Bendix +Benelux +Benjamin +Bennett +Bennington +Benny +Benson +Bentham +Bentley +Benton +Benz +Beowulf +Berea +Berenices +Bergen +Bergland +Berglund +Bergman +Bergson +Bergstrom +Berkeley +Berkowitz +Berkshire +Berlioz +Berlitz +Berman +Bern +Bernadine +Bernard +Bernardino +Bernardo +Bernet +Bernhard +Bernice +Bernie +Berniece +Bernini +Bernoulli +Bernstein +Berra +Bert +Bertha +Bertie +Bertram +Bertrand +Berwick +Bess +Bessemer +Bessie +Betelgeuse +Bethesda +Bethlehem +Betsey +Betsy +Bette +Betty +Beverly +Bhutan +Bigelow +Biggs +Billie +Billiken +Biltmore +Bimini +Bingham +Binghamton +Bini +Biometrika +Birgit +Birmingham +Bismarck +Bismark +Bissau +Bizet +Blackburn +Blackfeet +Blackman +Blackstone +Blackwell +Blaine +Blair +Blake +Blanchard +Blanche +Blatz +Bleeker +Blenheim +Blinn +Bloch +Blomberg +Blomquist +Bloomfield +Bloomington +Blum +Blumenthal +Blythe +Bobbie +Boca +Bodleian +Boeing +Boeotian +Bogota +Bohemia +Bohr +Bois +Boise +Bolshevist +Bolshoi +Bolton +Boltzmann +Bombay +Bonaparte +Bonaventure +Boniface +Bonn +Bonneville +Bonnie +Boone +Bootes +Bordeaux +Borden +Borealis +Boreas +Borg +Boris +Borroughs +Bosch +Bose +Boswell +Boucher +Bowditch +Bowdoin +Bowen +Boyce +Boyd +Boyle +Boylston +Bradbury +Bradford +Bradley +Bradshaw +Brady +Bragg +Brahmaputra +Brahms +Brahmsian +Brainard +Brandeis +Brandenburg +Brandon +Brandt +Braniff +Brasilia +Braun +Brazzaville +Bremen +Brenda +Brendan +Brennan +Brenner +Brent +Brest +Breton +Brett +Brewster +Brian +Brice +Bridgeport +Bridget +Bridgetown +Bridgewater +Briggs +Brigham +Brighton +Brillouin +Brindisi +Brisbane +Bristol +Britannic +Britannica +Brittany +Britten +Broadway +Brock +Broglie +Bromfield +Bromley +Bronx +Brooke +Brookhaven +Brookline +Brooklyn +Browne +Brownell +Brownian +Bruce +Bruckner +Bruegel +Brumidi +Brunhilde +Bruno +Brunswick +Brussels +Bryan +Bryant +Bryce +Bryn +Buchanan +Bucharest +Buchenwald +Buchwald +Buckley +Bucknell +Budapest +Budd +Buddha +Buddhism +Buddhist +Budweiser +Buena +Buenos +Buick +Bujumbura +Bulgaria +Bundestag +Bunsen +Bunyan +Burch +Burgundian +Burgundy +Burke +Burlington +Burma +Burmese +Burnett +Burnham +Burnside +Burroughs +Burt +Burton +Burtt +Burundi +Busch +Bushnell +Butterfield +Buttrick +Buxtehude +Buxton +Byers +Byrd +Byrne +Byron +Byronic +Byzantine +Byzantium +CACM +CBS +CDC +CERN +CIA +CPA +CRT +CUNY +Cabot +Cadillac +Cady +Caesar +Cahill +Cain +Caine +Cairo +Cal +Calais +Calcutta +Calder +Caldwell +Caleb +Calgary +Calhoun +Calkins +Callaghan +Callahan +Callisto +Calumet +Calvary +Calvert +Calvin +Calvinist +Cambodia +Camden +Camelot +Cameron +Cameroun +Camille +Camino +Campbell +Canaan +Canadian +Canaveral +Canberra +Candide +Canfield +Canis +Canoga +Canterbury +Cantonese +Capetown +Capistrano +Capitoline +Capricorn +Caputo +Caracas +Carboloy +Carbondale +Carbone +Carey +Cargill +Carib +Caribbean +Carl +Carla +Carleton +Carlin +Carlisle +Carlo +Carlson +Carlton +Carlyle +Carmela +Carmen +Carmichael +Carnegie +Caroline +Carolingian +Carolinian +Carolyn +Carpathia +Carr +Carrara +Carrie +Carroll +Carruthers +Carson +Carthage +Caruso +Casanova +Casey +Cassandra +Cassiopeia +Cassius +Castillo +Castro +Catalina +Catherine +Catherwood +Catholicism +Cathy +Catskill +Caucasian +Caucasus +Cauchy +Cavendish +Caviness +Cayley +Cayuga +Cecil +Cecilia +Cecropia +Cedric +Celanese +Celebes +Celia +Celsius +Celtic +Cenozoic +Cepheus +Cerberus +Ceres +Cervantes +Cesare +Cessna +Cetus +Ceylon +Cezanne +Chablis +Chad +Chadwick +Chalmers +Champlain +Chang +Chantilly +Chao +Chaplin +Chapman +Charles +Charleston +Charley +Charlie +Charlotte +Charlottesville +Charon +Charta +Chartres +Charybdis +Chatham +Chattanooga +Chaucer +Chauncey +Chautauqua +Chen +Cheney +Cherokee +Chesapeake +Cheshire +Chesterton +Chevrolet +Cheyenne +Chiang +Chicago +Chicagoan +Chicano +Chile +Chimique +Chinaman +Chinamen +Chinatown +Chinook +Chippendale +Chisholm +Choctaw +Chopin +Chou +Chris +Christ +Christendom +Christensen +Christenson +Christiana +Christianson +Christie +Christina +Christine +Christlike +Christoffel +Christopher +Christy +Chrysler +Chungking +Churchill +Churchillian +Cicero +Ciceronian +Cincinnati +Cinderella +Cinerama +Circe +Citroen +Claire +Clapeyron +Clara +Clare +Claremont +Clarence +Clarendon +Clark +Clarke +Claude +Claudia +Claudio +Claus/N +Clausius +Clayton +Clearwater +Clemson +Cleveland +Clifford +Clifton +Clint +Clinton +Clio +Clive +Clotho +Clyde +Clytemnestra +Coates +Cobb +Cochran +Cochrane +Coddington +Cody +Coffey +Coffman +Cohen +Cohn +Colby +Cole +Coleman +Coleridge +Colette +Colgate +Collins +Colombia +Colombo +Colosseum +Columbia +Columbus +Comanche +Cominform +Compagnie +Compton +Conakry +Conant +Conestoga +Confucian +Confucianism +Confucius +Congo +Congolese +Conklin +Conley +Connally +Connecticut +Conner +Connie +Connors +Conrad +Conrail +Constance +Constantine +Constantinople +Convair +Conway +Cooke +Cooley +Coolidge +Coors +Copeland +Copenhagen +Copernican +Copernicus +Copperfield +Corbett +Corcoran +Corey +Corinth +Corinthian +Coriolanus +Cornelia +Cornelius +Cornell +Cornwall +Coronado +Corp +Cortland +Corvus +Cosgrove +Cossack +Costello +Cottrell +Coulter +Courtney +Coventry +Cowan +Craig +Cramer +Crandall +Cranford +Cranston +Crawford +Creole +Creon +Crestview +Cretaceous +Cretan +Crete +Crimea +Crispin +Crockett +Croix +Cromwell +Cromwellian +Crosby +Crowley +Cruickshank +Crusoe +Cuba +Culbertson +Culver +Cumberland +Cummings +Cummins +Cunard +Cunningham +Cupid +Curran +Curtis +Cushing +Cushman +Custer +Cyanamid +Cyclades +Cyclops +Cygnus +Cynthia +Cyprian +Cypriot +Cyprus +Cyril +Cyrus +Czechoslovakia +Czerniak +DC +DNA +DOD +Dacca +Dadaism +Dadaist +Dade +Daedalus +Dahl +Dahomey +Dailey +Daimler +Dairylea +Dakar +Dakota +Daley +Dallas +Dalton +Daly +Dalzell +Damascus +Damon +Dan +Dana +Danbury +Dane +Daniel +Danielson +Danish +Danny +Dante +Danube +Danubian +Danzig +Daphne +Dar +Darius +Darlene +Darrell +Dartmouth +Darwin +Darwinian +Datsun +Daugherty +Dave +David +Davidson +Davis +Davison +Davy/S +Dawson +Dayton +Daytona +De +Deane +Deanna +Dearborn +Debbie +Debby +Deborah +Debra +Debussy +Dec +Decatur +Decca +Decker +Dee +Deere +Del +Delaney +Delano +Delhi +Delia +Delilah +Della +Delmarva +Delphi +Delphic +Delphinus +Dempsey +Deneb +Denebola +Dennis +Denny +Denton +Denver +Derbyshire +Derek +Des +Descartes +Desmond +Detroit +Devon +Devonshire +Dewey +Dewitt +Dhabi +Diana +Diane +Dianne +Dickerson +Dickinson +Dickson +Dido +Diebold +Diego +Dietrich +Dietz +Dillon +Dinah +Dionysian +Dionysus +Dirac +Dirichlet +Dis +Disney +Disneyland +Dixie +Dixon +Djakarta +Dnieper +Dobbin +Dobbs +Dodd +Dodson +Doge +Doherty +Dolan +Dolores +Domenico +Domesday +Domingo +Dominic +Dominican +Dominick +Dominique +Donahue +Donald +Donaldson +Doneck +Donna +Donnelly +Donner +Donovan +Dooley +Doolittle +Doppler +Dora +Dorado +Dorcas +Dorchester +Doreen +Doria +Doric +Doris +Dorothea +Dorothy +Dorset +Dostoevsky +Doubleday +Doug +Dougherty +Douglas +Douglass +Dow +Dowling +Downey +Doyle +Dr +Draco +Drexel +Dreyfuss +Driscoll +Drummond +Drury +Dryden +DuPont +Duane +Dubhe +Dublin +Dudley +Duffy +Dugan +Duluth +Duma +Dumpty +Dunbar +Duncan +Dunedin +Dunham +Dunkirk +Dunlap +Dunlop +Dunn +Durango +Durer +Durham +Durkee +Durkin +Durrell +Durward +Dusenberg +Dusenbury +Dusseldorf +Dutch +Dutchman +Dutchmen +Dutton +Dwight +Dwyer +Dyke +Dylan +EDT +EEOC +EPA +ERDA +EST +Eagan +Eastland +Eastman +Eastwood +Eaton +Eben +Eccles +Ecole +Econometrica +Ecuador +Ed/N +Eddie +Edgar +Edgerton +Edinburgh +Edison +Edith +Edmonds +Edmondson +Edmonton +Edmund +Edna +Edward/S +Edwardian +Edwin +Edwina +Effie +Egan +Egypt +Egyptian +Ehrlich +Eileen +Einstein +Einsteinian +Eire +Eisenhower +Eisner +Ekstrom +Ektachrome +Elaine +Elba +Eldon +Eleanor +Eleazar +Electra +Elena +Elgin +Eli +Elijah +Elinor +Eliot +Elisabeth +Elisha +Elizabeth +Elizabethan +Elkhart +Ella +Ellen +Elliott +Ellis +Ellison +Ellsworth +Ellwood +Elmhurst +Elmira +Elmsford +Eloise +Elsevier +Elsie +Elsinore +Elton +Ely +Elysee +Emanuel +Emerson +Emil +Emile +Emilio +Emily +Emmanuel +Emmett +Emory +Endicott +Enfield +Eng +Engel +Engle +Englewood +Englishman +Englishmen +Enid +Enoch +Enos +Enrico +Eocene +Ephesian +Ephesus +Ephraim +Epicurean +Epiphany +Episcopalian +Epsom +Epstein +Erasmus +Erastus +Erato +Eratosthenes +Eric +Erich +Erickson +Ericsson +Erie +Erlenmeyer +Ernest +Ernestine +Ernie +Ernst +Eros +Errol +Erskine +Ervin +Erwin +Eskimo +Esmark +Esposito +Essen +Essex +Estella +Estes +Esther +Ethan +Ethel +Ethiopia +Etruscan +Eucharist +Euclid +Euclidean +Eugene +Eugenia +Euler +Eulerian +Eumenides +Eunice +Euphrates +Eurasia +Euridyce +Euripides +Europa +Eurydice +Euterpe +Eva +Evans +Evanston +Evansville +Evelyn +Eveready +Everett +Everglades +Everhart +Ewing +Exeter +Exxon +Ezekiel +Ezra +FAA +FBI +FCC +FDA +FM +FMC +FPC +FTC +Faber +Fabian +Fafnir +Fahey +Fahrenheit +Fairchild +Fairfax +Fairfield +Fairport +Falmouth +Falstaff +Fanny +Faraday +Farber +Fargo +Farkas +Farley +Farmington +Farnsworth +Farrell +Fatima +Faulkner +Faust +Faustian +Faustus +Fayette +Fayetteville +Fe +Feb +Fedders +Fedora +Feeney +Feldman +Felice +Felicia +Felix +Fenton +Ferber +Ferdinand +Ferguson +Fermat +Fermi +Fernando +Ferrer +Fiberglas +Fibonacci +Filipino +Finland +Finley +Finn +Finnegan +Finnish +Firestone +Fischbein +Fischer +Fisk +Fiske +Fitch +Fitchburg +Fitzgerald +Fitzpatrick +Fitzroy +Fizeau +Flagler +Flagstaff +Flanagan +Flanders +Fleming +Fletcher +Flo +Florence +Florentine +Floridian +Floyd +Flynn +Foley +Fomalhaut +Fontaine +Fontainebleau +Foote +Forbes +Fordham +Formica +Formosa +Forrest +Forsythe +Fortescue +Foss +Foxhall +Francis +Franciscan +Francisco +Frankfort +Frankfurt +Franz +Fraser +Frau +Frazier +Fred +Freddie +Freddy +Frederic +Frederick/S +Fredericksburg +Fredericton +Fredholm +Fredrickson +Freedman +Freeport +Freetown +Frenchman +Frenchmen +Fresnel +Fresno +Freud +Freudian +Frey +Freya +Frick +Friedman +Frigga +Frigidaire +Fritz +Fruehauf +Frye +Fuchs +Fuchsia +Fuji +Fujitsu +Fullerton +Fulton +Furman +GAO +GE +GM +GMT +GNP +GOP +GPO +GS +GSA +Gaberones +Gabon +Gabriel +Gabrielle +Gaelic +Gail +Gaines +Gainesville +Gaithersburg +Galapagos +Galatea +Galatia +Galbreath +Galen +Galilee +Gallagher +Galloway +Gallup +Galois +Galt +Galveston +Galway +Gambia +Ganges +Gannett +Ganymede +Garcia +Gardner +Garfield +Garibaldi +Garrett +Garrisonian +Garry +Garth +Garvey +Gary +Gascony +Gaspee +Gaston +Gatlinburg +Gauguin +Gaul +Gaulle +Gaussian +Gavin +Gaylord +Gegenschein +Geiger +Geigy +Gemini +Gemma +Genesco +Geneva +Genevieve +Genoa +Geoffrey +George +Georgetown +Georgia +Gerald +Geraldine +Gerard +Gerber +Gerhard +Gerhardt +Germanic +Germantown +Gerry +Gershwin +Gertrude +Gestapo +Getty +Gettysburg +Ghana +Ghent +Giacomo +Gibbons +Gibbs +Gibraltar +Gibson +Gideon +Gifford +Gil +Gilbertson +Gilchrist +Gilead +Giles +Gillespie +Gillette +Gilligan +Gilmore +Gimbel +Gina +Ginn +Gino +Ginsberg +Ginsburg +Giovanni +Giuliano +Giuseppe +Gladstone +Gladys +Glasgow +Glaswegian +Gleason +Glenda +Glendale +Glenn +Glidden +Gloria +Gloriana +Gloucester +Goa +Goddard +Godfrey +Godwin +Goethe +Goff +Gogh +Goldberg +Goldman +Goldstein +Goldstine +Goldwater +Goleta +Goliath +Gonzales +Gonzalez +Goode +Goodman +Goodrich +Goodwin +Goodyear +Gordian +Gordon +Goren +Gorham +Gorky +Gorton +Gotham +Gottfried +Gould +Grady +Graff +Granville +Grayson +Grecian +Greece +Greenbelt +Greenberg +Greenblatt +Greenbriar +Greene +Greenfield +Greenland +Greensboro +Greenwich +Greer +Greg +Gregg +Gregory +Grendel +Grenoble +Gresham +Greta +Gretchen +Griffith +Grimaldi +Grimes +Grimm +Griswold +Grosset +Grossman +Grosvenor +Groton +Grumman +Guam +Guardia +Guatemala +Guenther +Guggenheim +Guiana +Guilford +Gullah +Gunderson +Gunther +Gurkha +Gus +Gustafson +Gustav +Gustave +Gustavus +Gutenberg +Guthrie +Guyana +Gwen +Gwyn +Haag +Haas +Haberman +Habib +Hackett +Hadamard +Haddad +Hades +Hadley +Hadrian +Hagen +Hager +Hagstrom +Hague +Hahn +Haifa +Haines +Haiti +Haitian +Hal +Haley +Halifax +Halley +Halloween +Halsey +Halstead +Halverson +Hamal +Hamburg +Hamilton +Hamiltonian +Hamlin +Hammond +Hampshire +Hampton +Han/S +Hancock +Handel +Haney +Hanford +Hankel +Hanley +Hanlon +Hanna +Hannah +Hannibal +Hanoi +Hanover +Hanoverian +Hans/N +Hansel +Hanson +Hanukkah +Harbin +Harcourt +Hardin +Harding +Harlan +Harlem +Harley +Harmon +Harold +Harpy +Harriet +Harriman +Harrington +Harris +Harrisburg +Harrison +Hartford +Hartley +Hartman +Harvard +Harvey +Hatfield +Hathaway +Hatteras +Hattie +Haugen +Havana +Havilland +Hawaii +Hawaiian +Hawkins +Hawley +Hawthorne +Hayden +Haydn +Hayes +Haynes +Healey +Healy +Hearst +Heathkit +Hebe +Hebraic +Hebrew +Hecate +Heckman +Hecuba +Hegelian +Heidelberg +Heine +Heinz +Heisenberg +Helen +Helena +Helene +Hellenic +Helmholtz +Helmut +Helsinki +Helvetica +Hemingway +Hempstead +Henderson +Hendrick/S +Hendrickson +Henley +Henning +Henri +Henrietta +Hepburn +Hera +Heraclitus +Herbert +Herculean +Hercules +Hereford +Herkimer +Herman +Hermes +Hermite +Hermosa +Herodotus +Herr +Herschel +Hershel +Hershey +Hertzog +Hesperus +Hess +Hessian +Hester +Hetman +Hettie +Hetty +Heublein +Heusen +Heuser +Hewett +Hewitt +Hewlett +Hiatt +Hiawatha +Hibbard +Hibernia +Hickey +Hickman +Hicks +Hieronymus +Higgins +Hilbert +Hildebrand +Hillcrest +Hillel +Hilton +Himalaya +Hindu +Hinduism +Hines +Hinman +Hiram +Hiroshi +Hiroshima +Hirsch +Hitachi +Hitchcock +Hitler +Hoagland +Hobart +Hobbes +Hobbs +Hoboken +Hodges +Hodgkin +Hoff +Hoffman +Hokan +Holbrook +Holcomb +Hollandaise +Hollerith +Hollingsworth +Hollister +Holloway +Hollywood +Holm +Holman +Holmdel +Holmes +Holocene +Holst +Holstein +Holyoke +Homeric +Honda +Honduras +Honeywell +Honolulu +Honshu +Hoosier +Hoover +Hopkins +Hopkinsian +Horace +Horatio +Hornblower +Horowitz +Horton +Horus +Houdaille +Houdini +Houghton +Houston +Howard +Howe +Howell +Hoyt +Hrothgar +Hubbard +Hubbell +Huber +Hubert +Hudson +Huffman +Huggins +Hugh/S +Hugo +Humboldt +Hummel +Humphrey +Hun +Hungarian +Hungary +Huntington +Huntley +Huntsville +Hurd +Huron +Hurst +Hurwitz +Huston +Hutchins +Hutchinson +Hutchison +Huxley +Huxtable +Hyades +Hyannis +Hyde +IBM +ICC +IEEE +IQ +IR +IRS +ITT +Iberia +Ibn +Icarus +Icelandic +Ida +Idaho +Ifni +Ike +Iliad +Ilona +Ilyushin +Imbrium +Inc +Inca +Indianapolis +Indies +Indira +Indochina +Indonesia +Indonesian +Informatica +Ingersoll +Ingram +Injun +Inman +Interpol +Inverness +Io +Iowa +Ira +Iran +Iraq +Irene +Irish +Irishman +Irishmen +Irma +Iroquois +Irrawaddy +Irvin +Irvine +Irving +Irwin +Isaac +Isaacson +Isabel +Isabella +Isaiah +Isfahan +Ising +Isis +Islam +Islamabad +Islamic +Isolde +Israeli +Israelite +Istanbul +Italy +Ithaca +Ito +Ivan +Ivanhoe +Iverson +Izvestia +JACM +Jablonsky +Jackie +Jackman +Jackson +Jacksonian +Jacksonville +Jacky +Jacob/S +Jacobean +Jacobi +Jacobian +Jacobite +Jacobs/N +Jacobson +Jacobus +Jacqueline +Jacques +Jaeger +Jakarta +Jamaica +James +Jamestown +Jan +Jane +Janeiro +Janet +Janice +Janos +Jansenist +Janus +Jarvin +Jason +Java +Jeannie +Jed +Jeff +Jefferson +Jeffersonian +Jeffrey +Jehovah +Jenkins +Jennie +Jennifer +Jennings +Jensen +Jeremiah +Jeremy +Jeres +Jericho +Jeroboam +Jerome +Jerusalem +Jesse +Jessica +Jessie +Jesuit +Jesus +Jew +Jewell +Jewett +Jewish +Jim +Jimenez +Jimmie +Jo +Joan +Joanna +Joanne +Joaquin +Joe +Joel +Johann +Johannes +Johannesburg +Johansen +Johanson +John/S +Johnny +Johns/N +Johnson +Johnston +Johnstown +Joliet +Jolla +Jon +Jonas +Jonathan +Jones +Jordan +Jorge +Jorgensen +Jorgenson +Jose +Josef +Joseph +Josephine +Josephson +Josephus +Joshua +Josiah +Jovanovich +Jove +Jovian +Joyce +Jr +Juan +Juanita +Judaism +Judas +Judd +Jude +Judith +Judson +Judy +Jukes +Jules +Julia +Julie +Juliet +Julio +Julius +Juneau +Juno +Jupiter +Jura +Justine +Justinian +Jutish +Kabuki +Kabul +Kaddish +Kafka +Kafkaesque +Kahn +Kajar +Kalamazoo +Kalmuk +Kamchatka +Kampala +Kane +Kankakee +Kansas +Kant +Kaplan +Karachi +Karamazov +Karen +Karl +Karol +Karp +Kaskaskia +Kate +Katharine +Katherine +Kathleen +Kathy +Katie +Katmandu +Katowice +Katz +Kauffman +Kaufman +Kay +Keaton +Keats +Keenan +Keith +Keller +Kelley +Kellogg +Kelsey +Kelvin +Kemp +Kendall +Kennan +Kennecott +Kennedy +Kenneth +Kenney +Kensington +Kent +Kenton +Kentucky +Kenya +Kenyon +Kepler +Kermit +Kerr +Kessler +Kettering +Kevin +Keyes +Keynes +Keynesian +Khartoum +Khmer +Khrushchev +Kidde +Kieffer +Kiev +Kiewit +Kigali +Kikuyu +Kilgore +Kim +Kimball +Kimberly +Kingsbury +Kingsley +Kingston +Kinney +Kinshasha +Kiowa +Kipling +Kirby +Kirchner +Kirchoff +Kirkland +Kirkpatrick +Kirov +Kitakyushu +Kiwanis +Klan +Klaus +Klein +Kline +Klux +Knapp +Knauer +Knightsbridge +Knott +Knowles +Knowlton +Knox +Knoxville +Knudsen +Knudson +Knutsen +Knutson +Koch +Kochab +Kodachrome +Kodiak +Koenig +Koenigsberg +Kong +Koppers +Koran +Korea +Kowalewski +Kowalski +Krakatoa +Krakow +Kramer +Krause +Kremlin +Kresge +Krieger +Krishna +Kristin +Kronecker +Krueger +Kruger +Kruse +Ku +Kuhn +Kurd +Kurt +Kuwait +Kyle +Kyoto +LIFO +LSI +LTV +Laban +Labrador +Lacerta +Lachesis +Lafayette +Lagos +Lagrange +Laguerre +Lahore +Laidlaw +Lakehurst +Lamar +Lana +Lancashire +Lancaster +Landis +Lang +Lange +Langley +Langmuir +Lanka +Lansing +Lao/S +Laocoon +Laotian +Laplace +Laramie +Laredo +Lares +Larkin +Larry +Lars/N +Larson +Lateran +Lathrop +Latin +Latinate +Latrobe +Lauderdale +Laue +Laughlin +Lauren +Laurence +Laurent +Laurentian +Laurie +Lausanne +Lavoisier +Lawrence +Lawson +Layton +Lazarus +Leander +Lear +Leavenworth +Lebanese +Lebanon +Lebesgue +Leeds +Legendre +Lehigh +Lehman +Leigh +Leighton +Leila +Leland +Lemuel +Len +Lena +Lenin +Leningrad +Leninism +Leninist +Lennox +Lenny +Leo +Leon +Leona +Leonard +Leonardo +Leone +Leonid +Leopold +Leroy +Lesbian +Leslie +Lesotho +Lethe +Letitia +Levi/S +Levin +Levine +Leviticus +Levitt +Lexington +Leyden +Liberia +Libreville +Libya +Liechtenstein +Ligget +Liggett +Lila +Lilian +Lillian +Lilliputian +Lilly +Lima +Limerick +Lin +Lincoln +Lind +Linda +Lindberg +Lindbergh +Lindholm +Lindquist +Lindsay +Lindsey +Lindstrom +Linotype +Linus +Lionel +Lippincott +Lipschitz +Lipscomb +Lipton +Lisa +Lisbon +Lise +Lissajous +Littleton +Litton +Livermore +Liverpool +Livingston +Liz +Lizzie +Lloyd +Locke +Lockhart +Lockheed +Lockian +Lockwood +Lodowick +Loeb +Logan +Loire +Lois +Loki +Lola +Lomb +Lombard +Lombardy +Lome +London +Longfellow +Loomis +Lopez +Lorelei +Loren +Lorinda +Lorraine +Los +Lotte +Lottie +Lou +Louis +Louisa +Louise +Louisiana +Louisville +Lounsbury +Lourdes +Louvre +Lovelace +Loveland +Lowe +Lowell +Lowry +Lubbock +Lubell +Lucas +Lucerne +Lucia +Lucian +Lucifer +Lucille +Lucius +Lucretia +Lucretius +Ludlow +Ludwig +Lufthansa +Luftwaffe +Luis +Lumpur +Lund +Lundberg +Lundquist +Lura +Lusaka +Luther +Lutheran +Lutz +Luxembourg +Luzon +Lydia +Lykes +Lyle +Lyman +Lynchburg +Lynn +Lyon/S +Lyra +MBA +MPH +Mabel +Mac +MacArthur +MacDonald +MacGregor +MacKenzie +MacMillan +Macassar +Macbeth +Macedon +Macedonia +Mach +Machiavelli +Mackey +Mackinac +Mackinaw +Macon +Madagascar +Madame +Maddox +Madeira +Madeleine +Madeline +Madison +Madonna +Madrid +Madsen +Mae +Maelstrom +Magdalene +Maggie +Magnuson +Magog +Magruder +Mahayana +Mahayanist +Mahoney +Maier +Maine +Malabar +Malagasy +Malawi +Malay +Malaysia +Malcolm +Malden +Maldive +Mali +Mallory +Malone +Maloney +Malraux +Malta +Maltese +Malton +Managua +Manama +Manchester +Manfred +Manhattan +Manitoba +Manley +Mann +Mansfield +Manuel +Manville +Mao +Maori +Marc +Marceau +Marcel +Marcello +Marcia +Marco +Marcus +Marcy +Mardi +Margaret +Margery +Margo +Marguerite +Marie +Marietta +Marilyn +Marin +Marino +Mario +Marion +Marjorie +Marjory +Markham +Markov +Markovian +Marlboro +Marlborough +Marlene +Marlowe +Marquette +Marrietta +Marriott +Marseilles +Marsha +Marshall +Martha +Martian +Martinez +Martinique +Martinson +Marty +Marvin +Marx +Mary +Maseru +Masonic +Masonite +Massey +Mateo +Mathematik +Mathews +Mathewson +Mathias +Mathieu +Matilda +Matisse +Matson +Matthew/S +Mattson +Maureen +Maurice +Maurine +Mauritania +Mauritius +Mavis +Mawr +Maximilian +Maxine +Maxwellian +Maya +Mayer +Mayfair +Mayflower +Maynard +Mayo +Mazda +Mbabane +McAdams +McAllister +McBride +McCabe +McCall +McCann +McCarthy +McCarty +McCauley +McClain +McClellan +McClure +McCluskey +McConnel +McConnell +McCormick +McCoy +McCracken +McCullough +McDaniel +McDermott +McDonald +McDonnell +McDougall +McDowell +McElroy +McFadden +McFarland +McGee +McGill +McGinnis +McGovern +McGowan +McGrath +McGraw +McGregor +McGuire +McHugh +McIntosh +McIntyre +McKay +McKee +McKenna +McKenzie +McKeon +McKesson +McKinley +McKinney +McKnight +McLaughlin +McLean +McLeod +McMahon +McMillan +McMullen +McNally +McNaughton +McNeil +McPherson +Mecca +Medea +Medici +Mediterranean +Meg +Meier +Meistersinger +Mekong +Mel +Melanesia +Melanie +Melbourne +Melcher +Melinda +Melissa +Mellon +Melpomene +Melville +Melvin +Memphis +Mendelssohn +Menelaus +Menlo +Mennonite +Menzies +Mephistopheles +Mercator +Mercedes +Merck +Meredith +Merle +Merriam +Merrill +Merrimack +Merritt +Mervin +Mesozoic +Messrs +Metcalf +Methodism +Methuen +Methuselah +Metzler +Mexican +Mexico +Meyer/S +Miami +Michael +Michaelangelo +Michelangelo +Michelin +Michelson +Mickelson +Mickey +Micky +Micronesia +Midas +Middlebury +Middlesex +Middleton +Middletown +Midwestern +Miguel +Milan +Mildred +Millard +Millie +Millikan +Milton +Miltonic +Milwaukee +Mimi +Mindanao +Minerva +Minneapolis +Minnie +Minoan +Minos +Minsky +Miocene +Mira +Miranda +Mirfak +Miriam +Mississippi +Mississippian +Missoula +Missouri +Missy +Mitchell +Mizar +Mobil +Modesto +Moe +Moen +Mogadiscio +Mohammedan +Mohawk +Mohr +Moines +Moiseyev +Moliere +Moline +Moll +Mollie +Molly +Moloch +Moluccas +Mona +Monaco +Mongolia +Monica +Monmouth +Monoceros +Monongahela +Monroe +Monrovia +Monsanto +Mont +Montague +Montclair +Montenegrin +Monterey +Monteverdi +Montevideo +Montgomery +Monticello +Montmartre +Montpelier +Montrachet +Montreal +Monty +Mooney +Moore +Moorish +Moran +Moravia +Moreland +Moresby +Morgan +Moriarty +Morley +Mormon +Moroccan +Morocco +Morrill +Morrison +Morrissey +Morristown +Morse +Morton +Moscow +Moser +Moses +Moslem +Motorola +Moulton +Mouton +Moyer +Mozart +Mrs +Mudd +Mueller +Muir +Mukden +Mullen +Mumford +Muncie +Munich +Munson +Muong +Muriel +Murphy +Murray +Muscat +Muscovite +Muscovy +Muskegon +Muzak +Muzo +Mycenae +Mycenaean +Myers +Mynheer +Myra +Myron +NAACP +NASA +NATO +NBC +NBS +NC +NCAA +NCR +NH +NIH +NIMH +NJ +NM +NOAA +NRC +NSF +NTIS +NY +NYC +NYU +Nabisco +Nadine +Nagasaki +Nagoya +Nagy +Nair +Nairobi +Nan +Nancy +Nanette +Nanking +Nantucket +Naomi +Naples +Napoleon +Napoleonic +Narbonne +Narragansett +Nash +Nashua +Nashville +Nassau +Nat +Natalie +Natchez +Nathan +Nathaniel +Navajo +Nazarene +Nazareth +Nazism +Ndjamena +Neal +Neanderthal +Neapolitan +Nebraska +Ned +Neff +Negroid +Nehru +Neil +Nell +Nellie +Nelsen +Nelson +Neptune +Nero +Ness +Nestor +Neumann +Neva +Nevada +Nevins +Newark +Newbold +Newcastle +Newell +Newfoundland +Newman +Newport +Newsweek +Nguyen +Niagara +Niamey +Nibelung +Nicaragua +Nicholas +Nicholls +Nichols +Nicholson +Nicodemus +Nicosia +Nielsen +Nielson +Nietzsche +Niger +Nigeria +Nikko +Nikolai +Nile +Nina +Nineveh +Niobe +Nippon +Nixon +Noah +Nobel +Noel +Nolan +Noll +Nordhoff +Nordstrom +Noreen +Norfolk +Norma +Norman +Normandy +Norris +Northampton +Northrop +Northrup +Northumberland +Norton +Norwalk +Norway +Norwegian +Norwich +Nostradamus +Nostrand +Nottingham +Nouakchott +Nov +Novak +Novosibirsk +Nubia +Numerische +Nyquist +O'Brien +O'Connell +O'Connor +O'Dell +O'Donnell +O'Dwyer +O'Hare +O'Leary +O'Shea +O'Sullivan +OPEC +Oakland +Oakley +Oberlin +Oceania +Oct +Octavia +Odessa +Odin +Odysseus +Odyssey +Oedipal +Offenbach +Ogden +Okinawa +Oklahoma +Olaf +Oldenburg +Oldsmobile +Olga +Olin +Oliver +Olivetti +Olivia +Olsen +Olson +Olympia +Olympic +Omaha +Oman +Oneida +Onondaga +Ontario +Opel +Ophiucus +Oppenheimer +Oregon +Oresteia +Orestes +Orin +Orinoco +Orion +Orkney +Orlando +Orleans +Orono +Orpheus +Orphic +Orr +Ortega +Orville +Orwell +Orwellian +Osaka +Osborn +Osborne +Oscar +Osgood +Oshkosh +Osiris +Oslo +Ostrander +Oswald +Othello +Otis +Ott +Ottawa +Otto +Ottoman +Ouagadougou +Ovid +Owens +Oxford +Oxnard +Ozark +PBS +PM +PTA +PVC +Pablo +Pabst +Packard +Paine +Pakistan +Pakistani +Palatine +Paleolithic +Paleozoic +Palermo +Palestine +Palladian +Palmolive +Palmyra +Palo +Palomar +Pam +Pamela +Pancho +Pandanus +Pandora +Paoli +Pappas +Papua +Paraguay +Paramus +Pareto +Paris +Parisian +Parke +Parkinson +Parr +Parrish +Parsifal +Parthenon +Pasadena +Paso +Passaic +Passover +Pasteur +Patagonia +Paterson +Patrice +Patricia +Patrick +Patsy +Patterson +Patti +Patton +Paul +Paula +Paulette +Pauli +Pauline +Paulo +Paulsen +Paulson +Paulus +Pavlov +Pawtucket +Payne +Paz +Peabody +Peachtree +Peale +Pearce +Pearson +Pease +Pecos +Pedro +Pegasus +Peggy +Peking +Pelham +Pembroke +Penelope +Penh +Penn +Penrose +Pensacola +Pentecost +Peoria +Pepsi +PepsiCo +Percival +Percy +Perez +Pergamon +Periclean +Pericles +Perilla +Perkins +Perle +Permian +Perry +Perseus +Pershing +Persia +Persian +Perth +Peru +Peruvian +Pete/RZ +Petersburg +Peterson +Peugeot +Pfizer +PhD +Phelps +Phil +Philadelphia +Philip +Philippine +Philistine +Phillips +Phipps +Phoenicia +Phyllis +Physik +Picasso +Piccadilly +Pickering +Pickett +Pickford +Pickman +Piedmont +Pierre +Pierson +Pilate +Pillsbury +Pinehurst +Pinsky +Piraeus +Piscataway +Pisces +Pitney +Pitt +Pittsburgh +Pittsfield +Pittston +Pius +Plainfield +Planck +Plato +Platonism +Platonist +Platte +Pleiades +Pleistocene +Plexiglas +Pliny +Pliocene +Plutarch +Pluto +Plymouth +Po +Pocono +Poe +Poincare +Poisson +Polaris +Polaroid +Politburo +Polk +Pollard +Pollux +Polyhymnia +Polyphemus +Pomona +Pompeii +Ponce +Ponchartrain +Pontiac +Poole +Porte +Portia +Porto +Portsmouth +Portugal +Portuguese +Poseidon +Potomac +Potts +Poughkeepsie +Powell +Poynting +Prado +Prague +Pratt +Pravda +Precambrian +Prentice +Presbyterian +Prescott +Preston +Pretoria +Priam +Priestley +Princeton +Principia +Priscilla +Pritchard +Procrustes +Procter +Procyon +Prof +Prokofieff +Promethean +Prometheus +Proserpine +Protozoa +Proust +Prussia +Ptolemaic +Ptolemy +Puccini +Puerto +Pugh +Pulaski +Pulitzer +Pullman +Punic +Purcell +Purdue +Purina +Puritan +Pusan +Pusey +Putnam +Pygmalion +Pyhrric +Pyle +Pyongyang +Pyrex +Pythagoras +Pythagorean +QED +Qatar +Quakeress +Quantico +Quebec +Quezon +Quinn +Quirinal +Quito +Quixote +RCA +ROTC +RPM +RSVP +Rabin +Rachel +Rachmaninoff +Radcliffe +Rae +Rafael +Rafferty +Raleigh +Ralph +Ralston +Ramada +Raman +Ramo +Ramsey +Rand +Randall +Randolph +Rangoon +Ranier +Rankin +Raoul +Raphael +Raritan +Rasmussen +Rastus +Rawlinson +Rayleigh +Raymond +Raytheon +Reagan +Rebecca +Recife +Redmond +Redstone +Reese +Reeves +Regina +Reginald +Regis +Regulus +Reich +Reid +Reilly +Reinhold +Rembrandt +Remington +Remus +Rena +Renault +Renoir +Rensselaer +Reub/N +Reuters +Rex +Reykjavik +Reynolds +Rhea +Rhenish +Rhine +Rhoda +Rhode/S +Rhodesia +Rica +Richard/S +Richardson +Richfield +Richmond +Richter +Rickettsia +Rico +Ridgway +Riemann +Rigel +Riggs +Riley +Rinehart +Rio +Riordan +Ripley +Ritchie +Ritter +Ritz +Riviera +Riyadh +Robbins +Robert/S +Roberta +Roberto +Robertson +Robinson +Rochester +Rockefeller +Rockford +Rockland +Rockwell +Rodgers +Rodney +Rodriguez +Roentgen +Roger/S +Roland +Rollins +Roman +Romano +Rome +Romeo +Romulus +Ron +Ronald +Ronnie +Roosevelt +Rooseveltian +Rosa +Rosalie +Roseland +Rosen +Rosenberg +Rosenblum +Rosenthal +Rosenzweig +Ross +Rotarian +Roth +Rousseau +Rowe +Rowena +Rowland +Rowley +Roy +Royce +Ruanda +Rube +Ruben +Rudolf +Rudolph +Rudy +Rudyard +Rufus +Rumania +Rumford +Runge +Runnymede +Runyon +Rushmore +Russ +Russell +Russia +Russo +Rutgers +Ruth +Rutherford +Rutland +Rutledge +Rwanda +Ryan +Rydberg +Ryder +SC +SCM +SD +SIAM +SST +SUNY +Sabina +Sabine +Sacramento +Sadie +Sadler +Saginaw +Sagittarius +Sahara +Saigon +Sal +Salaam +Salem +Salerno +Salesian +Salina +Salisbury +Salish +Salk +Salle +Salvador +Salvatore +Sam +Sammy +Samoa +Sampson +Samson +Samuel +Samuelson +San +Sana +Sanborn +Sanchez +Sancho +Sandburg +Sanderson +Sandia +Sandra +Sandusky +Sanford +Sanhedrin +Santa +Santayana +Santiago +Santo +Sao +Sara +Saracen +Sarah +Saran +Sarasota +Saratoga +Sargent +Saskatchewan +Saturn +Saturnalia +Saud +Saudi +Saul +Sault +Saunders +Savannah +Saviour +Savonarola +Savoyard +Saxon +Saxony +Scala +Scandinavia +Scarborough +Scarsdale +Schaefer +Schafer +Schantz +Schenectady +Schiller +Schlesinger +Schlitz +Schloss +Schmidt +Schmitt +Schnabel +Schneider +Schoenberg +Schofield +Schottky +Schroeder +Schroedinger +Schubert +Schultz +Schulz +Schumacher +Schumann +Schuster +Schuyler +Schuylkill +Schwab +Schwartz +Schweitzer +Scorpio +Scot +Scotia +Scotsman +Scotsmen +Scott +Scottish +Scottsdale +Scotty +Scranton +Scribners +Scripps +Scylla +Scythia +Seagram +Sean +Seattle +Sebastian +Segovia +Segundo +Seidel +Selectric +Selena +Selfridge +Selkirk +Selma +Selwyn +Seminole +Semiramis +Semite +Semitic +Seneca +Senegal +Senora +Seoul +Sepoy +Sequoia +Sergei +Serpens +Seth +Seton +Severn +Seville +Seward +Sextans +Seymour +Shafer +Shaffer +Shakespeare +Shakespearean +Shakespearian +Shanghai +Shannon +Shantung +Shapiro +Shari +Sharon +Sharpe +Shasta +Shattuck +Shawnee +Shea +Shedir +Sheehan +Sheffield +Sheila +Shelby +Sheldon +Shelley +Shelton +Shenandoah +Shepard +Sheppard +Sheraton +Sheridan +Sherlock +Sherman +Sherrill +Sherwin +Sherwood +Shiloh +Shinto +Shipley +Shirley +Shockley +Shreveport +Shu +Shulman +Shylock +Siamese +Sian +Siberia +Sibley +Sicilian +Sicily +Sidney +Siegel +Siegfried +Sieglinda +Siegmund +Siemens +Sigmund +Signora +Sikorsky +Silas +Silverman +Simmons +Simon/S +Simonson +Simpson +Sims +Sinai +Sinclair +Singapore +Sioux +Sirius +Sistine +Sisyphean +Sisyphus +Skippy +Skye +Slav +Slavic +Sloan +Sloane +Slocum +Smalley +Smithfield +Smithson +Smithsonian +Smucker +Smyrna +Smythe +Snider +Snyder +Societe +Socrates +Socratic +Sofia +Sol +Solomon +Solon +Somali +Somers +Somerset +Somerville +Sommerfeld +Sonoma +Sonora +Sony +Sophie +Sophoclean +Sophocles +Sorensen +Sorenson +Sousa +Southampton +Southey +Spaniard +Sparkman +Sparta +Spartan +Spaulding +Spector +Spencerian +Sperry +Spica +Spiegel +Spiro +Spokane +Sprague +Springfield +Sproul +Squibb +Sri +St. +Stacy +Stafford +Stahl +Staley +Stalin +Stamford +Stan +Standish +Stanford +Stanhope +Stanley +Stanton +Stapleton +Starkey +Starr +Staten +Statler +Stauffer +Staunton +Stearns +Steele +Steen +Stefan +Steinberg +Steiner +Stephanie +Stephen/S +Stephenson +Sterno +Stetson +Steuben +Steve +Steven/S +Stevenson +Stewart +Stirling +Stockholm +Stockton +Stokes +Stonehenge +Storey +Stratford +Stratton +Strauss +Strickland +Strom +Stromberg +Stuart +Studebaker +Sturbridge +Sturm +Stuttgart +Stuyvesant +Stygian +Styrofoam +Styx +Sudan +Sudanese +Suez +Suffolk +Sullivan +Sumatra +Sumerian +Sumner +Sumter +Sunnyvale +Sus +Susan +Susanne +Susie +Sussex +Sutherland +Sutton +Suzanne +Suzuki +Swahili +Swanson +Swarthmore +Swarthout +Swaziland +Swede +Sweden +Swedish +Sweeney +Swenson +Switzer +Switzerland +Sybil +Sydney +Sykes +Sylvania +Sylvester +Sylvia +Syracuse +Syria +TNT +TRW +TTL +TTY +TV +TVA +TWA +TWX +Tacitus +Tacoma +Tahiti +Tahoe +Taipei +Taiwan +Tallahassee +Talmud +Tammany +Tampa +Tanaka +Tananarive +Tantalus +Tanya +Tanzania +Taoist +Taos +Tarbell +Tarrytown +Tartary +Tarzan +Tasmania +Tass +Taurus +Taylor +Teddy +Tegucigalpa +Teheran +Tehran +Tektronix +Teledyne +Telefunken +Telex +Templeton +Tenneco +Tenney +Tennyson +Teresa +Terpsichore +Terra +Terre +Tess +Teutonic +Texaco +Texan +Textron +Thai +Thailand +Thalia +Thayer +Thea +Thebes +Thelma +Theodore +Theodosian +Theresa +Thermofax +Theseus +Thetis +Thimbu +Thomas +Thomistic +Thompson +Thomson +Thor +Thoreau +Thornton +Thorpe +Thorstein +Thruway +Thuban +Thule +Thurman +Tiber +Tibetan +Tientsin +Tiffany +Tigris +Tim +Timex +Timon +Tioga +Tipperary +Tirana +Titan +Titus +Tobago +Todd +Togo +Tokyo +Toledo +Tolstoy +Tom +Tomlinson +Tommie +Tompkins +Toni +Topeka +Topsy +Toronto +Torrance +Toshiba +Townsend +Toyota +Tracy +Transite +Trastevere +Travis +Trenton +Trevelyan +Triangulum +Trianon +Trichinella +Trinidad +Triplett +Tristan +Trojan +Troutman +Trudy +Truman +Trumbull +Tucson +Tudor +Tulane +Tulsa +Tunis +Tunisia +Turin +Turkish +Tuscaloosa +Tuscan +Tuscany +Tuscarora +Tuskegee +Tuttle +Twombly +Tyburn +Tyler +Typhon +Tyson +UCLA +UK +UN +UNESCO +USA +USAF +USC +USDA +USGS +USIA +USN +USPS +USSR +Uganda +Ukrainian +Ulan +Ullman +Ulster +Ulysses +Uniroyal +Univac +Unix +Upton +Uranus +Urbana +Uris +Ursa +Ursula +Ursuline +Uruguay +Utica +Vaduz +Vail +Valerie +Valery +Valhalla +Valkyrie +Valletta +Valois +Vance +Vancouver +Vandenberg +Vanderbilt +Vanderpoel +Varian +Varitype +Vatican +Vaudois +Vaughan +Vaughn +Veda +Vega +Velasquez +Vella +Venetian +Veneto +Venezuela +Venice +Venus +Venusian +Vera +Verde +Verdi +Verlag +Vermont +Verna +Verne +Vernon +Verona +Veronica +Versailles +Vichy +Vicksburg +Vicky +Victoria +Victorian +Vida +Vienna +Viennese +Vientiane +Viet +Vietnam +Vietnamese +Viking +Vincent +Vinson +Virgil +Virginian +Virgo +Vishnu +Visigoth +Vito +Vivaldi +Vivian +Vladimir +Vladivostok +Vogel +Volkswagen +Volstead +Volta +Voltaire +Volterra +Volvo +Voss +Vought +Vreeland +Vulcan +WAC +WECo +Waals +Wabash +Waco +Wadsworth +Wagner +Wahl +Wainwright +Waite +Wakefield +Walcott +Walden +Waldo +Waldorf +Waldron +Walgreen +Wallace +Waller +Wallis +Walpole +Walsh +Walt/RZ +Waltham +Walton +Wang +Wappinger +Waring +Warsaw +Warwick +Washburn +Wasserman +Waterbury +Watergate +Waterhouse +Waterloo +Waterman +Watertown +Watkins +Watson +Watts +Wayne +Webb +Webster +Wehr +Wei +Weierstrass +Weinberg +Weinstein +Weiss +Welch +Weldon +Weller +Welles +Wellesley +Wendell +Wendy +Werner +Werther +Wesley +Wesleyan +Westchester +Westfield +Westinghouse +Westminster +Weston +Weyerhauser +Whalen +Wharton +Whatley +Wheatstone +Whelan +Wheller +Whippany +Whipple +Whitaker +Whitcomb +Whitehall +Whitehorse +Whitlock +Whitman +Whitney +Whittaker +Whittier +Wichita +Wier +Wiggins +Wilbur +Wilcox +Wiley +Wilfred +Wilhelm +Wilhelmina +Wilkes +Wilkins +Wilkinson +Willa +Willard +William/S +Williamsburg +Williamson +Willie +Willis +Willoughby +Wilma +Wilmington +Wilshire +Wilson +Wilsonian +Winchester +Windsor +Winfield +Winifred +Winnetka +Winnie +Winnipeg +Winnipesaukee +Winslow +Winston +Winthrop +Wisconsin +Witt +Wolcott +Wolfe +Wolff +Wolfgang +Wong +Woodard +Woodbury +Woodlawn +Woolworth +Wooster +Worcester +Wordsworth +Worthington +Wotan +Wrigley +Wronskian +Wu +Wuhan +Wyandotte +Wyatt +Wyeth +Wylie +Wyman +Wyner +Wyoming +Xavier +Xerox +Xerxes +YMCA +YWCA +Yakima +Yale +Yalta +Yamaha +Yankee +Yankton +Yaounde +Yaqui +Yarmouth +Yates +Yeager +Yeats +Yellowknife +Yemen +Yiddish +Yoder +Yokohama +Yokuts +Yonkers +Yorktown +Yosemite +Yost +Youngstown +Ypsilanti +Yucatan +Yugoslav +Yugoslavia +Yuki +Yukon +Yves +Yvette +Zachary +Zaire +Zambia +Zan +Zanzibar +Zealand +Zeiss +Zellerbach +Zen +Zeus +Ziegler +Zimmerman +Zion +Zionism +Zoe +Zomba +Zoroaster +Zoroastrian +Zurich +a's +aback +abaft +abalone +abandon/DGS +abandonment +abase/DGS +abasement/S +abash/DGS +abate/DGRS +abatement/S +abbas +abbe +abbey/MS +abbot/MS +abbreviate/DGNSX +abc +abdicate +abdomen/MS +abdominal +abduct/DS +abduction/MS +abductor/MS +abed +aberrant +aberrate/NX +abet/S +abetted +abetter +abetting +abeyance +abeyant +abhor/S +abhorred +abhorrent +abhorrer +abhorring +abide/DGS +ability/MS +abject/PY +abjection/S +abjure/DGS +ablate/DGNSV +ablaze +able/RT +ablute/N +ably +abnormal/Y +abnormality/S +aboard +abode/MS +abolish/DGRSZ +abolishment/MS +abolition +abolitionist/S +abominable +abominate +aboriginal +aborigine/MS +aborning +abort/DGSV +abortion/MS +abortive/Y +abound/DGS +about +above +aboveboard +aboveground +abovementioned +abrade/DGS +abrasion/MS +abrasive +abreact +abreaction/S +abreast +abridge/DGS +abridgment +abroad +abrogate/DGS +abrupt/PY +abscess/DS +abscissa/MS +abscissae +abscond/DGS +absence/MS +absent/DGSY +absentee/MS +absenteeism +absentia +absentminded +absinthe +absolute/NPSY +absolve/DGS +absorb/DGRS +absorbency +absorbent +absorption/MS +absorptive +abstain/DGRS +abstention/S +abstinence +abstinent +abstract/DGPSY +abstraction/MS +abstractionism +abstractionist +abstractor/MS +abstruse/P +absurd/Y +absurdity/MS +abuilding +abundance +abundant/Y +abusable +abuse/DGSV +abut/S +abutment +abutted +abutter/MS +abutting +abysmal/Y +abyss/MS +acacia +academia +academic/S +academically +academician +academy/MS +acanthus +accede/DS +accelerate/DGNSX +accelerator/S +accelerometer/MS +accent/DGS +accentual +accentuate/DGNS +accept/DGRSZ +acceptability +acceptable +acceptably +acceptance/MS +acceptant +acceptor/MS +access/DGS +accessibility +accessible +accessibly +accession/MS +accessor/MS +accessory/MS +accident/SY +accidental/Y +accipiter +acclaim/DGS +acclamation +acclimate/DGS +acclimatization +acclimatize/D +accolade/S +accommodate/DGNSX +accompaniment/MS +accompanist/MS +accompany/DGS +accomplice/S +accomplish/DGRSZ +accomplishment/MS +accord/DGRSZ +accordance +accordant +according/Y +accordion/MS +accost/DGS +account/DGS +accountability +accountable +accountably +accountancy +accountant/MS +accouter +accoutrement/S +accredit/D +accreditate/NX +accretion/MS +accrual +accrue/DGS +acculturate/DGNS +accumulate/DGNSX +accumulator/MS +accuracy/S +accurate/PY +accursed +accusal +accusation/MS +accusative +accuse/DGRS +accusing/Y +accustom/DGS +ace/MS +acetate +acetic +acetone +acetylene +ache/DGS +achievable +achieve/DGRSZ +achievement/MS +achilles +achromatic +acid/SY +acidic +acidity/S +acidulous +acknowledge/DGRSZ +acknowledgeable +acknowledgment/MS +acme +acne +acolyte/S +acorn/MS +acoustic/S +acoustical/Y +acoustician +acquaint/DGS +acquaintance/MS +acquiesce/DGS +acquiescence +acquiescent +acquirable +acquire/DGS +acquisition/MS +acquisitive/P +acquit/S +acquittal +acquitted +acquitter +acquitting +acre/MS +acreage +acrid +acrimonious +acrimony +acrobacy +acrobat/MS +acrobatic/S +acronym/MS +acropolis +across +acrylate +acrylic +act/DGSV +actinic +actinide +actinium +actinolite +actinometer/S +action/MS +activate/DGNSX +activator/MS +active/Y +activism +activist/MS +activity/MS +actor/MS +actress/MS +actual/SY +actuality/S +actualization +actuarial/Y +actuate/DGS +actuator/MS +acuity +acumen +acute/PY +acyclic +acyclically +ad +adage/S +adagio/S +adamant/Y +adapt/DGRSVZ +adaptability +adaptable +adaptation/MS +adaptive/Y +adaptor/S +add/DGRSZ +addend +addenda +addendum +addict/DGS +addiction/MS +addition/MS +additional/Y +additive/MS +additivity +addle +address/DGRSZ +addressability +addressable +addressee/MS +adduce/DGS +adducible +adduct/DGS +adduction +adductor +adenoma +adept +adequacy/S +adequate/Y +adhere/DGRSZ +adherence +adherent/MS +adhesion/S +adhesive/MS +adiabatic +adiabatically +adieu +adipic +adjacency +adjacent +adject/V +adjectival +adjective/MS +adjoin/DGS +adjoint +adjourn/DGS +adjournment +adjudge/DGS +adjudicate/DGNSX +adjudication/M +adjunct/MS +adjure/DGS +adjust/DGRSZ +adjustable +adjustably +adjustment/MS +adjustor/MS +adjutant/S +administer/DGJS +administrable +administrate/NVX +administration/M +administrative/Y +administrator/MS +administratrix +admirable +admirably +admiral/MS +admiralty +admiration/S +admire/DGRSZ +admiring/Y +admissibility +admissible +admission/MS +admit/S +admittance +admitted/Y +admitter/S +admitting +admix/DS +admixture +admonish/DGS +admonishment/MS +admonition/MS +ado +adobe +adolescence +adolescent/MS +adopt/DGRSVZ +adoption/MS +adorable +adoration +adore/DS +adorn/DS +adornment/MS +adrenal +adrenaline +adrift +adroit/P +ads +adsorb/DGS +adsorbate +adsorption +adsorptive +adulate/N +adult/MS +adulterate/DGS +adulterer/MS +adulterous/Y +adultery +adulthood +adumbrate/DGS +advance/DGS +advancement/MS +advantage/DS +advantageous/Y +advent +adventist/S +adventitious +adventure/DGRSZ +adventurous +adverb/MS +adverbial +adversary/MS +adverse/Y +adversity/S +advert +advertise/DGRSZ +advertisement/MS +advice +advisability +advisable +advisably +advise/DGRSZ +advised/Y +advisee/MS +advisement/S +advisor/MS +advisory +advocacy +advocate/DGS +aegis +aeolian +aerate/DGNS +aerator/S +aerial/MS +aeroacoustic +aerobic/S +aerodynamic/S +aerogene +aeronautic/S +aeronautical +aerosol/S +aerosolize +aerospace +aesthete +aesthetic/MS +aesthetically +afar +affable +affair/MS +affect/DGSV +affectate/NX +affectation/M +affecting/Y +affection/MS +affectionate/Y +affector +afferent +affiance/D +affidavit/MS +affiliate/DGNSX +affine +affinity/MS +affirm/DGS +affirmation/MS +affirmative/Y +affix/DGS +afflict/DGSV +affliction/MS +affluence +affluent +afford/DGS +affordable +afforest +afforestation +affricate/S +affright +affront/DGS +afghan/S +afghanistan +aficionado +afield +afire +aflame +afloat +afoot +afore +aforementioned +aforesaid +aforethought +afoul +afraid +afresh +africa +african/S +afro +aft/R +aftereffect +afterglow +afterimage +afterlife +aftermath +aftermost +afternoon/MS +aftershock/S +afterthought/S +afterward/S +again +against +agape +agar +agate/S +agave +age/DGRSZ +ageless +agency/MS +agenda/MS +agent/MS +agglomerate/DNS +agglutinate/DGNS +agglutinin/S +aggravate/DNS +aggregate/DGNSXY +aggression/MS +aggressive/PY +aggressor/S +aggrieve/DGS +aghast +agile/Y +agility +agitate/DGNSX +agitator/MS +agleam +aglow +agnomen +agnostic/MS +ago +agog +agone +agonize/DGS +agony/S +agouti +agrarian +agree/DRSZ +agreeable/P +agreeably +agreeing +agreement/MS +agricultural/Y +agriculture +agrimony +ague +ah +ahead +ahem +ahoy +aid/DGS +aide/DGS +ail/G +ailanthus +aile/G +aileron/S +ailment/MS +aim/DGRSZ +aimless/Y +ain't +air/DGJRSZ +airbag/S +airborne +aircraft +airdrop/S +airedale +airfare +airfield/MS +airflow +airfoil/S +airframe/S +airily +airless +airlift/MS +airline/RS +airlock/MS +airmail/S +airman +airmass +airmen +airpark +airplane/MS +airport/MS +airship/MS +airspace +airspeed +airstrip/MS +airtight +airway/MS +airy +aisle +ajar +akimbo +akin +ala/S +alabama +alabamian +alabaster +alacrity +alai +alan +alarm/DGS +alarming/Y +alarmist +alaska +alb +alba +albacore +albania +albanian/S +albatross +albeit +album/S +albumin +alchemy +alcibiades +alcohol/MS +alcoholic/MS +alcoholism +alcove/MS +aldehyde +alden +alder +alderman/M +aldermen +aldrin +ale/V +alee +aleph +alert/DGPRSYZ +alerted/Y +alewife +alfalfa +alfonso +alfresco +alga +algae +algaecide +algal +algebra/MS +algebraic +algebraically +algeria +algerian +alginate +algol +algorithm/MS +algorithmic +algorithmically +alia/S +alias/DGS +alibi/MS +alien/MS +alienate/DGNS +alight +align/DGS +alignment/S +alike +aliment/S +alimony +aliphatic +aliquot +alizarin +alkali/MS +alkaline +alkaloid/MS +alkyl +all +allah/M +allay/DGS +allegate/NX +allegation/M +allege/DGS +alleged/Y +allegiance/MS +allegiant +allegoric +allegorical/Y +allegory/MS +allegretto/MS +allegro/MS +allele/S +allemand +allemande +allergic +allergy/MS +alleviate/DGNRSZ +alley/MS +alleyway/MS +alliance/MS +alligator/MS +alliterate/NVX +alliteration/M +allocable +allocate/DGNSX +allocator/MS +allophone/S +allophonic +allot/S +allotment/MS +allotropic +allotted +allotter +allotting +allow/DGS +allowable +allowably +allowance/MS +alloy/MS +allspice +allude/DGS +allure/G +allurement +allusion/MS +allusive/P +alluvial +alluvium +ally/DGS +allyl +alma +almagest +almanac/MS +almighty +almond/MS +almoner +almost +alms +almsman +alnico +aloe/S +aloft +aloha +alone/P +along +alongside +aloof/P +aloud +alp/S +alpenstock +alpha +alphabet/MS +alphabetic/S +alphabetical/Y +alphabetize/DGS +alphanumeric +alpine +already +also +altar/MS +alter/DGRSZ +alterable +alterate/NX +alteration/M +altercate/NX +altercation/M +alterman +altern +alternate/DGNSVXY +alternative/SY +alternator/MS +althea +although +altimeter +altitude +alto/MS +altogether +altruism +altruist +altruistic +altruistically +alum +alumina +aluminate +aluminum +alumna/M +alumnae +alumni +alumnus +alundum +alveolar +alveoli +alveolus +alway/S +alyssum +am/N +amain +amalgam/MS +amalgamate/DGNS +amanita +amanuensis +amaranth +amass/DGS +amateur/MS +amateurish/P +amateurism +amatory +amaze/DGRSZ +amazed/Y +amazement +amazing/Y +amazon/MS +ambassador/MS +amber +ambiance +ambidextrous/Y +ambient +ambiguity/MS +ambiguous/Y +ambition/MS +ambitious/Y +ambivalence +ambivalent/Y +amble/DGRS +ambrose +ambrosia +ambrosial +ambulance/MS +ambulant +ambulatory +ambuscade +ambush/DS +amelia +ameliorate/DG +amenable +amend/DGS +amende/DG +amendment/MS +amenity/S +amenorrhea +america/MS +american/MS +americana +americium +amethyst +amethystine +ami +amiable +amicable +amicably +amid +amide +amidst +amigo +amino +aminobenzoic +amiss +amity +ammeter +ammo +ammonia +ammoniac +ammonium +ammunition +amnesty +amoeba/MS +amoebae +amok +among +amongst +amoral +amorality +amorist +amorous +amorphous/Y +amort +amortize/DGS +amount/DGRSZ +amour +amp/SY +amperage +ampere/S +ampersand/MS +amphetamine/S +amphibian/MS +amphibious/Y +amphibole +amphibology +amphitheater/MS +ample +amplify/DGNRSZ +amplitude/MS +ampoule/MS +amputate/DGS +amra +amsterdam +amtrak +amulet/S +amuse/DGRSZ +amused/Y +amusement/MS +amusing/Y +amy +amygdaloid +amyl +an +ana +anabaptist/MS +anachronism/MS +anachronistic +anachronistically +anaconda/S +anaerobic +anaesthesia +anaglyph +anagram/MS +anal +analeptic +analgesic +analog +analogical +analogous/Y +analogue/MS +analogy/MS +analyses +analysis +analyst/MS +analytic +analytical/Y +analyticity/S +analyzable +analyze/DGRSZ +anamorphic +anaphora +anaphoric +anaphorically +anaplasmosis +anarch +anarchic +anarchical +anarchist/MS +anarchy +anastigmat +anastigmatic +anastomoses +anastomosis +anastomotic +anathema +anatomic +anatomical/Y +anatomy +ancestor/MS +ancestral +ancestry +anchor/DGS +anchorage/MS +anchorite +anchoritism +anchovy/S +ancient/SY +ancillary +and/GZ +andesine +andesite +andorra +anecdotal +anecdote/MS +anechoic +anemia +anemic +anemometer/MS +anemometry +anemone +anent +anesthesia +anesthetic/MS +anesthetically +anesthetize/DGS +anew +angel/MS +angelfish +angelic +anger/DGS +angiography +angiosperm +angle/DGRSZ +anglican/S +anglicanism +anglophilia +anglophobia +angola +angrily +angry/RT +angst +angstrom +anguish/D +angular/Y +anharmonic +anhydride +anhydrite +anhydrous/Y +ani +aniline +animadversion +animadvert +animal/MS +animate/DGNPSXY +animated/Y +animator/MS +animism +animized +animosity +anion/MS +anionic +anise +aniseikonic +anisotropic +anisotropy +ankle/MS +annal/S +anneal +annex/DGS +annexation +annihilate/DGNS +anniversary/MS +annotate/DGNSX +announce/DGRSZ +announcement/MS +annoy/DGRSZ +annoyance/MS +annoying/Y +annual/SY +annuity +annul/S +annular +annuli +annulled +annulling +annulment/MS +annulus +annum +annunciate/DGS +annunciator/S +anode/MS +anodic +anodize/DS +anoint/DGS +anomalous/Y +anomaly/MS +anomic +anomie +anon +anonymity +anonymous/Y +anorexia +anorthic +anorthite +anorthosite +another/M +answer/DGRSZ +answerable +ant/MS +antacid +antagonism/S +antagonist/MS +antagonistic +antagonistically +antagonize/DGS +antarctic +antarctica +ante +anteater/MS +antebellum +antecedent/MS +antedate +antelope/MS +antenna/MS +antennae +anterior +anthem/MS +anther +anthology/S +anthracite +anthracnose +anthropogenic +anthropological/Y +anthropologist/MS +anthropology +anthropomorphic +anthropomorphically +anti +antibacterial +antibiotic/S +antibody/S +antic/MS +anticipate/DGNSX +anticipatory +anticoagulation +anticompetitive +antidisestablishmentarianism +antidote/MS +antiformant +antifundamentalist +antigen/MS +antigorite +antihistorical +antimicrobial +antimony +antinomian +antinomy +antipasto +antipathy +antiperspirant +antiphonal +antipode/MS +antiquarian/MS +antiquary +antiquate/D +antique/MS +antiquity/S +antiredeposition +antiresonance +antiresonator +antisemitic +antisemitism +antiseptic +antisera +antiserum +antislavery +antisocial +antisubmarine +antisymmetric +antisymmetry +antithesis +antithetic +antithetical +antithyroid +antitoxin/MS +antitrust +antler/D +anus +anvil/MS +anxiety/S +anxious/Y +any +anybody +anybody'd +anyhow +anymore +anyone +anyplace +anything +anytime +anyway +anywhere +aorta +apace +apache +apart +apartheid +apartment/MS +apathetic +apathy +apatite +ape/DGS +aperiodic +aperiodicity +aperture +apex +aphasia +aphasic +aphelion +aphid/MS +aphonic +aphorism/MS +aphrodite +apiary/S +apical +apices +apiece +apish +aplenty +aplomb +apocalypse +apocalyptic +apocrypha +apocryphal +apogee/S +apollo +apollonian +apologetic +apologetically +apologia +apologist/MS +apologize/DGS +apology/MS +apostate +apostle/MS +apostolic +apostrophe/S +apothecary +apothegm +apotheoses +apotheosis +appalachia +appalachian/S +appall/DG +appalling/Y +appanage +apparatus +apparel/D +apparent/Y +apparition/MS +appeal/DGRSZ +appealing/Y +appear/DGRSZ +appearance/S +appeasable +appease/DGS +appeasement +appellant/MS +appellate +append/DGRSZ +appendage/MS +appendices +appendicitis +appendix/MS +appertain/S +appetite/MS +appetizer +appetizing +applaud/DGS +applause +apple/MS +applejack +appliance/MS +applicability +applicable +applicant/MS +applicate/NVX +application/M +applicative/Y +applicator/MS +applique +apply/DGNRSXZ +appoint/DGRSVZ +appointe/DGRVZ +appointee/MS +appointment/MS +apport +apportion/DGS +apportionment/S +apposite/N +appraisal/MS +appraise/DGRSZ +appraising/Y +appreciable +appreciably +appreciate/DGNSVX +appreciative/Y +apprehend/D +apprehensible +apprehension/MS +apprehensive/PY +apprentice/DS +apprenticeship +apprise/DGS +approach/DGRSZ +approachability +approachable +approbate/N +appropriable +appropriate/DGNPSXY +appropriator/MS +approval/MS +approve/DGRSZ +approving/Y +approximable +approximant +approximate/DGNSXY +appurtenance/S +apricot/MS +april +apron/MS +apropos +apse +apsis +apt/PY +aptitude/S +aqua +aquaria +aquarium +aquarius +aquatic +aqueduct/MS +aqueous +aquifer/S +arab/MS +arabesque +arabia +arabian/S +arabic +arable +arachnid/MS +arbiter/MS +arbitrage +arbitrarily +arbitrary/P +arbitrate/DGNS +arbitrator/MS +arbor/MS +arboreal +arboretum +arbutus +arc/DGS +arcade/DMS +arcana +arcane +arccos +arccosine +arch/DGRSVYZ +archae +archaeological +archaeologist/MS +archaeology +archaic/P +archaically +archaism +archaize +archangel/MS +archbishop +archdiocese/S +archenemy +archeological +archeologist +archeology +archery +archetype +archetypical +archfool +archipelago +archipelagoes +architect/MS +architectonic +architectural/Y +architecture/MS +archival +archive/DGRSZ +archivist +arclike +arcsin +arcsine +arctan +arctangent +arctic +ardency +ardent/Y +ardor +arduous/PY +are +area/MS +areaway +areawide +aren't +arena/MS +arenaceous +argentina +argillaceous +argo/S +argon +argonaut/S +argot +arguable +arguably +argue/DGRSZ +argument/MS +argumentation +argumentative +arhat +arianism +arianist/S +arid +aridity +aries +aright +arise/GJRS +arisen +aristocracy +aristocrat/MS +aristocratic +aristocratically +aristotelian +aristotle +arithmetic/S +arithmetical/Y +arithmetize/DS +arizona +ark +arkansas +arm/DGRSZ +armada +armadillo/S +armageddon +armament/MS +armature +armchair/MS +armenian +armful +armhole +armillaria +armistice +armload +armoire +armor/DR +armory +armour +armpit/MS +armstrong +army/MS +aroma/S +aromatic +arose +around +arousal +arouse/DGS +arpeggio/MS +arrack +arraign/DGS +arraignment/MS +arrange/DGRSZ +arrangeable +arrangement/MS +arrant +array/DS +arrear/S +arrest/DGRSZ +arresting/Y +arrestor/MS +arrival/MS +arrive/DGS +arrogance +arrogant/Y +arrogate/DGNS +arrow/DS +arrowhead/MS +arrowroot +arroyo/S +arsenal/MS +arsenate +arsenic +arsenide +arsine +arson +art/MS +artemis +artemisia +arterial +arteriolar +arteriole/MS +arteriolosclerosis +arteriosclerosis +artery/MS +artful/PY +arthogram +arthritis +arthropod/MS +artichoke/MS +article/MS +articulate/DGNPSXY +articulator/S +articulatory +artifact/MS +artifactually +artifice/RS +artificial/PY +artificiality/S +artillerist +artillery +artisan/MS +artist/MS +artistic +artistically +artistry +artless +artwork +arty +arum +aryan +aryl +as +asbestos +ascend/DGRSZ +ascendancy +ascendant +ascendency +ascendent +ascension/S +ascent +ascertain/DGS +ascertainable +ascetic/MS +asceticism +ascii +ascomycetes +ascot +ascribable +ascribe/DGS +ascription +aseptic +ash/NRS +ashame/D +ashamed/Y +ashman +ashmen +ashore +ashtray/MS +ashy +asia +asian/S +asiatic +aside +asinine +ask/DGRSZ +askance +askew +asleep +asocial +asp/N +asparagus +aspect/MS +asperity +aspersion/MS +asphalt +aspheric +asphyxia +asphyxiate +aspic +aspidistra +aspirant/MS +aspirate/DGNSX +aspiration/M +aspirator/S +aspire/DGS +aspirin/S +asplenium +ass/MS +assai +assail/DGS +assailant/MS +assassin/MS +assassinate/DGNSX +assault/DGS +assay/DG +assemblage/MS +assemble/DGRSZ +assembly/MS +assent/DGRS +assert/DGRSVZ +assertion/MS +assertive/PY +assess/DGS +assessment/MS +assessor/S +asset/MS +assiduity +assiduous/Y +assign/DGRSZ +assignable +assignation +assignee/MS +assignment/MS +assimilable +assimilate/DGNSX +assist/DGS +assistance/S +assistant/MS +assistantship/S +associable +associate/DGNSVX +associational +associative/Y +associativity +associator/MS +assonance +assonant +assort/DS +assortment/MS +assuage/DS +assume/DGS +assumption/MS +assurance/MS +assure/DGRSZ +assured/Y +assuring/Y +assyrian +assyriology +astatine +aster/MS +asteria +asterisk/MS +asteroid/MS +asteroidal +asthma +astigmat +astigmatic +astigmatism +astonish/DGS +astonishing/Y +astonishment +astound/DGS +astraddle +astral +astray +astride +astringency +astringent +astronaut/MS +astronautic/S +astronomer/MS +astronomic +astronomical/Y +astronomy +astrophysical +astrophysics +astute/P +asunder +asylum +asymmetric +asymmetrically +asymmetry +asymptomatically +asymptote/MS +asymptotic +asymptotically +asynchronism +asynchronous/Y +asynchrony +at +atavism +atavistic +ate +atemporal +atheist/MS +atheistic +athena +athenian/S +athens +atherosclerosis +athlete/MS +athletic/S +athleticism +athwart +atlantes +atlantic +atlas +atmosphere/MS +atmospheric +atoll/MS +atom/MS +atomic/S +atomically +atomization +atomize/DGS +atonal/Y +atone/DS +atonement +atop +atrocious/Y +atrocity/MS +atrophic +atrophy/DGS +attach/DGRSZ +attache/DGRSZ +attachment/MS +attack/DGRSZ +attackable +attain/DGRSZ +attainable +attainably +attainder +attainment/MS +attempt/DGRSZ +attend/DGRSZ +attendance/MS +attendant/MS +attendee/MS +attention/MS +attentional +attentionality +attentive/PY +attenuate/DGNS +attenuator/MS +attest/DGS +attestation +attic/MS +attire/DGS +attitude/MS +attitudinal +attorney/MS +attract/DGSV +attraction/MS +attractive/PY +attractor/MS +attributable +attribute/DGNSVX +attributive/Y +attrition +attune/DGS +atypic +atypical/Y +auburn +auckland +auction +auctioneer/MS +audacious/PY +audacity +audible +audibly +audience/MS +audio +audiogram/MS +audiological +audiologist/MS +audiology +audiometer/S +audiometric +audiometry +audiotape +audiovisual +audit/DGS +audition/DGMS +auditor/MS +auditorium +auditory +audubon +auger/MS +aught +augite +augment/DGS +augmentation +augur/S +august/PY +augusta +auk +aunt/MS +auntie +aura/MS +aural/Y +aureole +aureomycin +auric +aurora +auscultate/DGNSX +auspice/S +auspicious/Y +austere/Y +austerity +austin +australia +australian +australite +austria +austrian +authentic +authentically +authenticate/DGNSX +authenticator/S +authenticity +author/DGMS +authoritarian +authoritarianism +authoritative/Y +authority/MS +authorization/MS +authorize/DGRSZ +authorship +autism +autistic +auto/MS +autobiographic +autobiographical +autobiography/MS +autoclave +autocollimate +autocollimator +autocorrelate/N +autocracy/S +autocrat/MS +autocratic +autocratically +autofluorescence +autograph/DG +autographs +automat/DG +automata +automate/DGNS +automatic +automatically +automaton +automobile/MS +automotive +autonavigator/MS +autonomic +autonomous/Y +autonomy +autopilot/MS +autopsy/DS +autoregressive +autosuggestibility +autosuggestible +autotransformer +autumn/MS +autumnal +auxiliary/S +avail/DGRSZ +availability/S +available +availably +avalanche/DGS +avant +avarice +avaricious/Y +ave/RZ +avenge/DGRS +avenue/MS +average/DGS +averred +averrer +averring +averse/NX +aversion/M +avert/DGSV +avian +aviary/S +aviate/N +aviator/MS +aviatrix +avid/Y +avidity +avionic/S +avocado/S +avocate/NX +avocation/M +avocet +avoid/DGRSZ +avoidable +avoidably +avoidance +avouch +avow/DS +await/DGS +awake/GS +awaken/DGS +award/DGRSZ +aware/P +awash +away +awe/D +awesome +awful/PY +awhile +awkward/PY +awl/MS +awn/GJ +awning/M +awoke +awry +ax/DGRSZ +axe/DGRSZ +axial/Y +axiological +axiology +axiom/MS +axiomatic +axiomatically +axiomatization/MS +axiomatize/DGS +axis +axisymmetric +axle/MS +axolotl/MS +axon/MS +aye/S +azalea/MS +azimuth/M +azimuthal +azimuths +azure +b's +babbitt +babble/DGS +babe/MS +babel/M +baboon +baby/DGS +babyhood +babyish +babysat +babysit +babysitting +baccalaureate +baccarat +bach/M +bachelor/MS +bacilli +bacillus +back/DGRSZ +backache/MS +backarrow/S +backbend/MS +backboard +backbone/MS +backdrop/MS +backfill +background/MS +backhand +backlash +backlog/MS +backorder +backpack/MS +backplane/MS +backplate +backpointer/MS +backscatter/DGS +backside +backslash/S +backspace/DS +backstage +backstairs +backstitch/DGS +backstop +backtrack/DGRSZ +backup/S +backward/PS +backwater/MS +backwood/S +backyard/MS +bacon +bacteria +bacterial +bacterium +bad/PY +bade +badge/RSZ +badger/DGM +badinage +badland/S +badminton +baffle/DGRZ +bag/MS +bagatelle/MS +bagel/MS +baggage +bagged +bagger/MS +bagging +baggy +bagpipe/MS +bah +bail/G +bailiff/MS +bait/DGRS +bake/DGRSZ +bakery/MS +baklava +balalaika/MS +balance/DGRSZ +balcony/MS +bald/GPY +baldpate +baldy +bale/RS +baleen +baleful +balk/DGS +balkan/S +balkanize/DG +balky/P +ball/DGRSZ +ballad/MS +ballast/MS +ballerina/MS +ballet/MS +balletomane +ballfield +ballgown/MS +ballistic/S +balloon/DGRSZ +ballot/MS +ballpark/MS +ballplayer/MS +ballroom/MS +ballyhoo +balm/MS +balmy +balsa +balsam +baltic +balustrade/MS +bam +bamboo +ban/MS +banal/Y +banana/MS +band/DGS +bandage/DGS +bandgap +bandit/MS +bandlimit/DGS +bandpass +bandstand/MS +bandstop +bandwagon/MS +bandwidth +bandwidths +bandy/DGS +bane +baneberry +baneful +bang/DGS +bangkok +bangladesh +bangle/MS +banish/DGS +banishment +banister/MS +banjo/MS +bank/DGRSZ +bankrupt/DGS +bankruptcy/MS +banned +banner/MS +banning +banquet/GJS +banshee/MS +bantam +banter/DGS +bantu/S +baptism/MS +baptismal +baptist/MS +baptistery +baptistry/MS +baptize/DGS +bar/DGMRST +barb/DRS +barbados +barbarian/MS +barbaric +barbarism +barbarity/S +barbarous/Y +barbecue/DS +barbecueing +barbell/MS +barberry +barbital +barbiturate/S +barbudo +bard/MS +bare/DGPRSTY +barefaced +barefoot/D +barfly/MS +bargain/DGS +barge/GS +baritone/MS +barium +bark/DGRSZ +barkeep +barley +barn/MS +barnacle +barnstorm/DGS +barnyard/MS +barometer/MS +barometric +baron/MS +baroness +baronet +baronial +barony/MS +baroque/P +barrack/S +barracuda +barrage/MS +barre/DG +barrel/MS +barrelled +barrelling +barren/P +barrette +barricade/MS +barrier/MS +barring/R +barrow +bartend/RZ +bartender/M +barter/DGS +barycentric +bas/DGRS +basal +basalt +base/DGPRSY +baseball/MS +baseband +baseboard/MS +baseless +baseline/MS +baseman +basemen +basement/MS +baseplate +bash/DGS +bashaw +bashful/P +basic/S +basically +basidiomycetes +basil +basilar +basilisk +basin/MS +basis +bask/DG +basket/MS +basketball/MS +basophilic +bass/MS +basset +bassi +bassinet/MS +basso +basswood +bastard/MS +baste/DGNSX +bastion/M +bat/MRS +batch/DS +bate/R +bateau +bath/DGRSZ +bathe/DGRSZ +bathos +bathrobe/MS +bathroom/MS +baths +bathtub/MS +batik +baton/MS +batt/DGNRXZ +battalion/MS +batter/DG +battery/MS +battle/DGRSZ +battlefield/MS +battlefront/MS +battleground/MS +battlement/MS +battleship/MS +batwing +bauble/MS +baud +bauxite +bawd +bawdy +bawl/DGS +bay/DGS +bayberry +bayonet/MS +bayou/MS +bazaar/MS +be/GHTY +beach/DGS +beachhead/MS +beacon/MS +bead/DGS +beadle/MS +beady +beagle/MS +beak/DRSZ +beam/DGRSZ +bean/DGRSZ +bear/GJRSZ +bearable +bearably +bearberry +beard/DS +beardless +bearish +beast/SY +beat/GJNRSZ +beatable +beatably +beatific +beatify/N +beatitude/MS +beatnik/MS +beau/MS +beauteous/Y +beautiful/Y +beautify/DGRSXZ +beauty/MS +beaux +beaver/MS +bebop +becalm/DGS +became +because +beck +becket +beckon/DGS +become/GS +becoming/Y +bed/MS +bedazzle/DGS +bedazzlement +bedbug/MS +bedded +bedder/MS +bedding +bedevil/DGS +bedfast +bedim +bedimmed +bedimming +bedlam +bedpost/MS +bedraggle/D +bedridden +bedrock/M +bedroom/MS +bedside +bedspread/MS +bedspring/MS +bedstead/MS +bedstraw +bedtime +bee/GJRSZ +beebread +beech/NR +beechwood +beef/DGRSZ +beefsteak +beefy +beehive/MS +been +beep/S +beet/MS +beethoven +beetle/DGMS +befall/GNS +befell +befit/MS +befitted +befitting +befog +befogged +befogging +before +beforehand +befoul/DGS +befriend/DGS +befuddle/DGS +beg/S +began +beget/S +begetting +beggar/SY +beggary +begged +begging +begin/S +beginner/MS +beginning/MS +begonia +begot +begotten +begrudge/DGS +begrudging/Y +beguile/DGS +begun +behalf +behave/DGS +behavior/S +behavioral/Y +behaviorism +behavioristic +behead/G +beheld +behest +behind +behold/GNRSZ +behoove/S +beige +bel/Y +belabor/DGS +belate/D +belated/Y +belay/DGS +belch/DGS +belfry/MS +belgian/MS +belgium +belie/DS +belief/MS +believable +believably +believe/DGRSZ +belittle/DGS +bell/MS +belladonna +bellboy/MS +belle/MS +bellflower +bellhop/MS +bellicose +bellicosity +belligerence +belligerent/MSY +bellman +bellmen +bellow/DGS +bellum +bellwether/MS +belly/MS +bellyache +bellyfull +belong/DGJS +belove/D +below +belt/DGS +belvedere +belvidere +bely/DGS +bemadden +beman +bemoan/DGS +bemuse +bench/DS +benchmark/MS +bend/GRSZ +bendable +beneath +benedict +benedictine +benediction/MS +benefactor/MS +benefice +beneficence/S +beneficent +beneficial/Y +beneficiary/S +benefit/DGS +benefitted +benefitting +benevolence +benevolent +bengal +bengali +benight/D +benign/Y +bent +benthic +benzedrine +benzene +beplaster +bequeath/DG +bequeathal +bequeaths +bequest/MS +berate/DGS +bereave/DGS +bereavement/S +bereft +beret/MS +berg +bergamot +beribbon/D +beriberi +berkelium +berlin/RZ +bermuda +berne +berry/MS +berserk +berth +berths +beryl +beryllium +beseech/GS +beset/S +besetting +beside/S +besiege/DGRZ +besmirch/DGS +besotted +besotter +besotting +besought +bespeak/S +bespectacled +bespoke +bessel +best/DGS +bestial +bestir +bestirring +bestow/D +bestowal +bestseller/MS +bestselling +bestubble +bet/MS +beta +betatron +betel +bethel +bethought +betide +betoken +betony +betray/DGRS +betrayal +betroth/D +betrothal +better/DGS +betterment/S +betting +bettor +between +betwixt +bevel/DGS +beverage/MS +bevy +bewail/DGS +beware +bewhisker/D +bewilder/DGS +bewildering/Y +bewilderment +bewitch/DGS +bey +beyond +bezel +bhoy +bianco +biannual +bias/DGS +biaxial +bib/MS +bibb/DG +bible/MS +biblical/Y +bibliographic +bibliographical +bibliography/MS +bibliophile +bicameral +bicarbonate +bicentennial +bicep/MS +bichromate +bicker/DGS +biconcave +biconnected +biconvex +bicycle/DGRSZ +bid/MS +biddable +bidden +bidder/MS +bidding +biddy/S +bide +bidiagonal +bidirectional +bien +biennial +biennium +bifocal/S +bifurcate +big/P +bigger +biggest +bight/MS +bigot/DMS +bigotry +biharmonic +bijection/MS +bijective/Y +bijouterie +bike/GMS +bikini/MS +bilabial +bilateral/Y +bilayer +bile +bilge/MS +bilharziasis +bilinear +bilingual +bilk/DGS +bill/DGJRSZ +billboard/MS +billet/DGS +billiard/S +billion/HS +billow/DS +billy +bimetallic +bimetallism +bimodal +bimolecular +bimonthly/S +bin/MS +binary +binaural +bind/GJRSZ +bindery +bindle +bindweed +binge/S +bingle +bingo +binocular/S +binomial +binuclear +biochemic +biochemical +biochemist +biochemistry +biofeedback +biograph/RZ +biographer/M +biographic +biographical/Y +biography/MS +biological/Y +biologist/MS +biology +biomass +biomedical +biomedicine +biometric +biometry +biophysic +biophysical +biophysicist +biopsy/S +bioscience +biosphere +biostatistic +biosynthesize +biota +biotic +biotite +bipartisan +bipartite +biped/S +biplane/MS +bipolar +biracial +birch/NS +bird/MS +birdbath/M +birdbaths +birdie/DS +birdlike +birdseed +birdwatch +birefringence +birefringent +birth/D +birthday/MS +birthplace/S +birthright/MS +births +biscuit/MS +bisect/DGS +bisection/MS +bisector/MS +bishop/MS +bishopric +bismuth +bison/MS +bisque/S +bistable +bistate +bit/DGMRSZ +bitch/MS +bite/DGRSZ +biting/Y +bitt/NRZ +bitter/PRTY +bittern +bitternut +bitterroot +bittersweet +bitumen +bituminous +bitwise +bivalve/MS +bivariate +bivouac/S +biweekly +biz +bizarre +blab/S +blabbed +blabbermouth +blabbermouths +blabbing +black/DGNPRSTXY +blackball +blackberry/MS +blackbird/MS +blackboard/MS +blackbody +blacken/DG +blackjack/MS +blacklist/DGS +blackmail/DGRSZ +blackout/MS +blacksmith +blacksmiths +bladder/MS +bladdernut +bladderwort +blade/MS +blamable +blame/DGRSZ +blameless/P +blameworthy +blanc +blanch/DGS +bland/PY +blank/DGPRSTY +blanket/DGRSZ +blare/DGS +blase +blaspheme/DGS +blasphemous/PY +blasphemy/S +blast/DGRSZ +blat +blatant/Y +blather +blatting +blaze/DGRSZ +blazon +bleach/DGRSZ +bleak/PY +blear +bleary +bleat/GS +bled +bleed/GJRS +blemish/MS +blend/DGS +bless/DGJ +blest +blew +blight/D +blimp/MS +blind/DGPRSYZ +blindfold/DGS +blinding/Y +blink/DGRSZ +blip/MS +bliss +blissful/Y +blister/DGS +blithe/Y +blitz/MS +blitzkrieg +blizzard/MS +bloat/DGRS +blob/MS +bloc/MS +block/DGMRSZ +blockade/DGS +blockage/MS +blockhouse/S +blocky +bloke/MS +blond/MS +blonde/MS +blood/DS +bloodbath +bloodhound/MS +bloodless +bloodroot +bloodshed +bloodshot +bloodstain/DMS +bloodstone +bloodstream +bloody/DT +bloom/DGSZ +bloop +blossom/DS +blot/MS +blotch +blotted +blotting +blouse/MS +blow/GRSZ +blowfish +blown +blowup +blubber +bludgeon/DGS +blue/GPRST +blueback +blueberry/MS +bluebill +bluebird/MS +bluebonnet/MS +bluebook +bluebush +bluefish +bluegill +bluegrass +bluejacket +blueprint/MS +bluestocking +bluet +bluff/GS +bluish +blunder/DGJS +blunt/DGPRSTY +blur/MS +blurb +blurred +blurring +blurry +blurt/DGS +blush/DGS +bluster/DGS +blustery +blutwurst +boa +boar +board/DGRSZ +boardinghouse/MS +boast/DGJRSZ +boastful/Y +boat/GRSZ +boathouse/MS +boatload/MS +boatman +boatmen +boatsman +boatsmen +boatswain/MS +boatyard/MS +bob/MS +bobbed +bobbin/MS +bobbing +bobble +bobby +bobcat +bobolink/MS +bobwhite/MS +bock +bode/S +bodhisattva +bodice +bodily +body/DS +bodybuilder/MS +bodybuilding +bodyguard/MS +bodyweight +bog/MS +bogey +bogeymen +bogged +bogging +boggle/DGS +boggy +bogus +bogy +boil/DGRSZ +boilerplate +boisterous/Y +bold/PRTY +boldface +bole +boletus +bolivar +bolivia +boll +bolo +bologna +bolometer +bolshevik/MS +bolshevism +bolster/DGS +bolt/DGS +bomb/DGJRSZ +bombard/DGS +bombardment +bombast +bombastic +bombproof +bon/DGRZ +bona +bonanza/MS +bond/DGRSZ +bondage +bondsman +bondsmen +bone/DGRSZ +bonfire/MS +bong +bongo +bonito +bonnet/DS +bonny +bonus/MS +bony +bonze +boo/HS +boob +booboo +booby +boogie +book/DGJRSZ +bookbind +bookcase/MS +bookend +bookie/MS +bookish +bookkeep/GRZ +bookkeeper/M +booklet/MS +bookplate +bookseller/MS +bookshelf/M +bookshelves +bookstore/MS +booky/S +boolean +boom/DGS +boomerang/MS +boomtown/MS +boon +boor/MS +boorish +boost/DGRS +boot/DGS +booths +bootleg/RS +bootlegged +bootlegger/MS +bootlegging +bootstrap/MS +bootstrapped +bootstrapping +booty +booze +bop +bopping +borate/S +borax +bordello/MS +border/DGJS +borderland/MS +borderline +bore/DGRS +boredom +boric +born +borne +borneo +boron +borosilicate +borough +boroughs +borrow/DGRSZ +bosom/MS +boson +boss/DS +boston +bostonian/MS +bosun +botanic +botanical +botanist/MS +botany +botch/DGRSZ +botfly +both/RZ +bother/DG +bothersome +botswana +bottle/DGRSZ +bottleneck/MS +bottom/DGS +bottomless +bottommost +botulin +botulinus +botulism +bouffant +bough/M +boughs +bought +boulder/MS +boulevard/MS +bounce/DGRS +bouncy +bound/DGNS +boundary/MS +boundless/P +bounteous/Y +bounty/MS +bouquet/MS +bourbon +bourgeois +bourgeoisie +bourn +boustrophedon +bout/MS +boutique +bovine/S +bow/DGRSZ +bowdlerize/DGS +bowel/MS +bowfin +bowie +bowl/DGRSZ +bowline/MS +bowman +bowmen +bowstring/MS +box/DGRSZ +boxcar/MS +boxtop/MS +boxwood +boxy +boy/MS +boyar +boycott/DS +boyfriend/MS +boyhood +boyish/P +bra/MS +brace/DGS +bracelet/MS +bracken +bracket/DGS +brackish +bract +brad +brae/MS +brag/S +bragged +bragger +bragging +braid/DGS +braille +brain/DGS +brainchild/M +brainstem/MS +brainstorm/MS +brainwash/DGS +brainy +brake/DGS +brakeman +bramble/MS +brambly +bran +branch/DGJS +brand/DGS +brandish/GS +brandy +brandywine +brant +brash/PY +brass/S +brassiere +brassy +brat/MS +bratwurst +bravado +brave/DGPRSTY +bravery +bravo/S +bravura +brawl/GR +brawn +bray/DGRS +braze/DGS +brazen/PY +brazier/MS +brazil +brazilian +breach/DGRSZ +bread/DGHS +breadboard/MS +breadbox/MS +breadfruit +breadroot +breadwinner/MS +break/GRSZ +breakable/S +breakage +breakaway +breakdown/MS +breakfast/DGRSZ +breakoff +breakpoint/MS +breakthrough/MS +breakthroughs +breakup +breakwater/MS +bream +breast/DS +breastplate +breastwork/MS +breath/DGRSZ +breathable +breathe/DGRSZ +breathless/Y +breaths +breathtaking/Y +breathy +breccia +bred +breech/MS +breed/GRS +breeze/MS +breezily +breezy +bremsstrahlung +brethren +breve +brevet/DGS +brevity +brew/DGRSZ +brewery/MS +briar/MS +bribe/DGRSZ +bribery +brick/DRS +brickbat +bricklayer/MS +bricklaying +bridal +bride/MS +bridegroom +bridesmaid/MS +bridge/DGS +bridgeable +bridgehead/MS +bridgework/M +bridle/DGS +brief/DGJPRSTY +briefcase/MS +briefing/M +brier +brig/MS +brigade/MS +brigadier/MS +brigantine +bright/NPRTXY +brighten/DGRZ +brilliance +brilliancy +brilliant/Y +brim +brimful +brimmed +brimming +brimstone +brindle/D +brine +bring/DGRSZ +brink +brinkmanship +briny +brisk/PRY +bristle/DGS +britain +britches +british/R +briton/MS +brittle/P +broach/DGS +broad/NPRTXY +broadband +broadcast/GJRSZ +broaden/DGJRZ +broadloom +broadside +brocade/D +broccoli +brochure/MS +brockle +broil/DGRSZ +broke/RZ +broken/PY +brokerage +bromide/MS +bromine +bronchi +bronchial +bronchiolar +bronchiole/MS +bronchitis +bronchus +bronco +bronze/DS +bronzy +brooch/MS +brood/GRS +broody +brook/DS +brookside +broom/MS +broomcorn +broomstick/MS +broth/RZ +brothel/MS +brother/MY +brotherhood +brotherly/P +brought +brouhaha +brow/MS +browbeat/GNS +brown/DGPRST +brownie/MS +brownish +brows/G +browse/G +brucellosis +bruise/DGS +bruit +brunch/S +brunette +brunt +brush/DGS +brushfire/MS +brushlike +brushwork +brushy +brusque/Y +brutal/Y +brutality/S +brutalize/DGS +brute/MS +brutish +bryophyta +bryophyte +bryozoa +bub +bubble/DGS +bubbly +buck/DGS +buckaroo +buckboard/MS +bucket/MS +bucketfull +buckeye +buckhorn +buckle/DGRS +buckshot +buckskin/S +buckthorn +buckwheat +bucolic +bud/MS +budded +budding +buddy/MS +budge/DGS +budget/DGRSZ +budgetary +buff/MRSZ +buffalo +buffaloes +buffer/DGM +bufferrer/MS +buffet/DGJS +bufflehead +buffoon/MS +bug/MS +bugaboo +bugeyed +bugged +bugger/MS +bugging +buggy/MS +bugle/DGRS +build/GJRSZ +buildup/MS +built +builtin +bulb/MS +bulblet +bulge/DG +bulk/DS +bulkhead/MS +bulky +bull/DGS +bulldog/MS +bulldoze/DGRS +bullet/MS +bulletin/MS +bullfinch +bullfrog +bullhead +bullhide +bullion +bullish +bullock +bullseye +bullwhack +bully/DGS +bullyboy +bulrush +bulwark +bum/MS +bumble/DGRSZ +bumblebee/MS +bummed +bumming +bump/DGRSZ +bumptious/PY +bun/MS +bunch/DGS +bundle/DGS +bundy +bungalow/MS +bungle/DGRSZ +bunion/MS +bunk/RSZ +bunker/DM +bunkhouse/MS +bunkmate/MS +bunny/MS +bunt/DGRSZ +buoy/DS +buoyancy +buoyant +burbank +burden/DGS +burdensome +burdock +bureau/MS +bureaucracy/MS +bureaucrat/MS +bureaucratic +buret +burette +burg +burgeon/DG +burgess/MS +burgher/MS +burglar/MS +burglarize/DGS +burglarproof/DGS +burglary/MS +burial +burl +burlap +burlesque/S +burley +burly +burn/DGJRSZ +burning/Y +burnish/DGS +burnt/PY +burp/DGS +burr/MS +burro/MS +burrow/DGRS +bursa +bursitis +burst/GS +bursty +bury/DGS +bus/DGS +busboy/MS +bush/GS +bushel/MS +bushmaster +bushwhack/DGS +bushy +busily +business/MS +businesslike +businessman +businessmen +buss/DGS +bust/DRS +bustard/MS +bustle/G +busy/DPRT +but +butadiene +butane +butch/RZ +butcher/D +butchery +butene +buteo +butler/MS +butt/DGMRSZ +butte/DGRSZ +butter/DGRZ +butterball +buttercup +butterfat +butterfly/MS +buttermilk +butternut +buttery +buttock/MS +button/DGS +buttonhole/MS +buttonweed +buttress/DGS +butyl +butyrate +buxom +buy/GRSZ +buyer/M +buzz/DGRS +buzzard/MS +buzzword/MS +buzzy +by/R +bye +bygone +bylaw/MS +byline/MS +bypass/DGS +bypath +byproduct/MS +byroad +bystander/MS +byte/MS +byway/S +byword/MS +c's +cab/MS +cabal +cabana +cabaret +cabbage/MS +cabdriver +cabin/MS +cabinet/MS +cabinetmake +cabinetry +cable/DGS +cacao +cachalot +cache/MS +cackle/DGRS +cacophonist +cacophony +cacti +cactus +cadaver +cadaverous +caddis +caddy +cadence/D +cadent +cadenza +cadet +cadmium +cadre +cafe/MS +cafeteria +cage/DGRSZ +cagey +cahoot +caiman +cairn +cajole/DGS +cake/DGS +calamitous +calamity/MS +calamus +calcareous +calcify +calcite +calcium +calculable +calculate/DGNSVX +calculator/MS +calculi +calculus +caldera +calendar/MS +calendrical +calf +calfskin +caliber/S +calibrate/DGNSX +calibre +calico +california +californium +caliper +caliph +caliphate +caliphs +calisthenic +call/DGRSZ +calla +calligraph +calligraphy +calliope +callous/DPY +callus +calm/DGPRSTY +calming/Y +caloric +calorie/MS +calorimeter +calorimetric +calorimetry +calumniate +calumny +calve/S +calypso +cam +camaraderie +camber +cambric +cambridge +came +camel/MS +camelback +camellia +camelopard +cameo +camera/MS +cameraman +cameramen +camilla +camouflage/DGS +camp/DGRSZ +campaign/DGRSZ +campanile +campfire +campground +campion +campsite +campus/MS +campusses +can't +can/MRS +canada +canal/MS +canary/MS +cancel/DGS +cancellate/NX +cancellation/M +cancer/MS +cancerous +candela +candelabra +candid/PY +candidacy +candidate/MS +candle/RS +candlelight +candlestick/MS +candlewick +candor +candy/DS +cane/R +canine +canister +canker +cankerworm +canna +cannabis +canned +cannel +canner/MS +cannery +cannibal/MS +cannibalize/DGS +canning +cannister/MS +cannon/MS +cannonball +cannot +canny +canoe/MS +canon/MS +canonic +canonical/SY +canonicalization +canonicalize/DGS +canopy +cant +cantaloupe +cantankerous/Y +canteen +canterelle +canticle +cantilever +cantle +canto +canton/MS +cantor/MS +canvas/MS +canvasback +canvass/DGRSZ +canyon/MS +cap/MRSZ +capability/MS +capable +capably +capacious/PY +capacitance/S +capacitate +capacitive +capacitor/MS +capacity/S +cape/RSZ +capella +capillary/S +capita +capital/SY +capitalism +capitalist/MS +capitalization/S +capitalize/DGRSZ +capitol/MS +capitulate +capo +capped +capping +caprice +capricious/PY +capstan +capstone +capsule +captain/DGS +captaincy +caption/MS +captious +captivate/DGNS +captive/MS +captivity +captor/MS +capture/DGRSZ +capybara +car/DGMS +carabao +caramel +caravan/MS +caraway +carbide +carbine +carbohydrate +carbolic +carbon/MS +carbonaceous +carbonate/NS +carbonic +carbonization +carbonize/DGRSZ +carbonyl +carborundum +carboxy +carboy +carbuncle +carcass/MS +carcinogen +carcinogenic +carcinoma +card/RS +cardamom +cardboard +cardiac +cardinal/SY +cardinality/MS +cardiology +cardiovascular +care/DGS +careen +career/MS +carefree +careful/PY +careless/PY +caress/DGRS +caret +caretaker +careworn +cargo +cargoes +caribou +caricature +carload +carmine +carnage +carnal +carnation +carne +carney +carnival/MS +carnivorous/Y +carob +carol/MS +carolina/MS +carouse +carp +carpenter/MS +carpentry +carpet/DGS +carport +carrageen +carrel +carriage/MS +carrion +carrot/MS +carry/DGRSZ +carryover/S +cart/DGRSZ +carte/DGRZ +cartel +cartesian +cartilage +cartographer +cartographic +cartography +carton/MS +cartoon/MS +cartridge/MS +cartwheel +carve/DGJRS +carven +casbah +cascade/DGS +cascara +case/DGJS +casebook +casein +casement/MS +casework +cash/DGRSZ +cashew +cashier/MS +cashmere +casino +cask/MS +casket/MS +casserole/MS +cassette +cassock +cast/DGMRSZ +castanet +caste/DGHRSZ +castigate +castle/DS +castor +casual/PSY +casualty/MS +cat/MRSZ +cataclysmic +catalog/DGRS +catalogue/DS +catalpa +catalysis +catalyst/MS +catalytic +catapult +cataract +catastrophe +catastrophic +catatonia +catatonic +catawba +catbird +catch/DGRSZ +catchable +catchup +catchword +catchy +catechism +categoric +categorical/Y +categorization +categorize/DGRSZ +category/MS +catenate +cater/DGR +caterpillar/MS +catfish +catharsis +cathedral/MS +catheter/S +cathode/MS +cathodic +catholic/MS +cation +cationic +catkin +catlike +catnip +catsup +cattail +cattle +cattleman +cattlemen +caucus +caught +cauliflower +caulk +causal/Y +causality +causate/NX +causation/M +cause/DGRS +causeway/MS +caustic/SY +caution/DGJRSZ +cautionary +cautious/PY +cavalcade +cavalier/PY +cavalry +cave/DGS +caveat/MS +caveman +cavemen +cavern/MS +cavernous +caviar +cavil +cavilling +cavity/MS +cavort +caw/G +cayenne +cdr +cease/DGS +ceaseless/PY +cedar +cede +cedilla +ceil/GJ +ceiling/M +celandine +celebrant +celebrate/DGNSX +celebrity/MS +celerity +celery +celesta +celestial/Y +cell/DS +cellar/MS +cellist/MS +cellophane +cellular +cellulose +cement/DGS +cemetery/MS +censor/DGS +censorial +censorship +censure/DRS +census/MS +cent/RSZ +centaur +centenary +centennial +center/DG +centerline +centerpiece/MS +centigrade +centimeter/S +centipede/MS +central/Y +centralization +centralize/DGS +centrex +centric +centrifugal +centrifugate +centrifuge +centripetal +centrist +centroid +centum +century/MS +ceramic +ceramium +cereal/MS +cerebellum +cerebral +cerebrate +ceremonial/PY +ceremonious +ceremony/MS +cereus +cerise +cerium +certain/Y +certainty/S +certifiable +certificate/NSX +certify/DGNRSXZ +certiorari +certitude +cerulean +cesium +cessation/MS +cession +cetera +chafe/GR +chaff/GR +chagrin +chain/DGS +chair/DGS +chairlady +chairman +chairmen +chairperson/MS +chairwoman +chairwomen +chaise +chalcedony +chalcocite +chalice/MS +chalk/DGS +chalkline +chalky +challenge/DGRSZ +chamber/DS +chamberlain/MS +chambermaid +chameleon +chamfer +chamois +chamomile +champ +champagne +champaign +champion/DGS +championship/MS +chance/DGS +chancel +chancellor +chancery +chancy +chandelier/MS +chandler +change/DGRSZ +changeability +changeable +changeably +changeover +channel/DGS +channelled +channeller/MS +channelling +chanson +chant/DGRS +chantey +chanticleer/MS +chantry +chaos +chaotic +chap/MS +chaparral +chapel/MS +chaperon/D +chaperone/D +chaplain/MS +chapping +chapter/MS +char/S +character/MS +characteristic/MS +characteristically +characterizable +characterization/MS +characterize/DGRSZ +charcoal/D +chard +charge/DGRSZ +chargeable +chariot/MS +charisma +charismatic +charitable/P +charity/MS +charm/DGRSZ +charming/Y +charring +chart/DGJRSZ +chartable +charter/DG +chartreuse +chartroom +chase/DGRSZ +chasm/MS +chassis +chaste/PY +chastise/DGRSZ +chastity +chat +chateau/MS +chateaux +chattel +chatter/DGRS +chattererz +chatting +chatty +chauffeur/D +chaw +cheap/NPRTXY +cheapen/DG +cheat/DGRSZ +check/DGRSZ +checkable +checkbook/MS +checkerberry +checkerboard +checklist +checkout +checkpoint/MS +checksum/MS +checksummed +checksumming +checkup +cheek/MS +cheekbone +cheeky +cheer/DGRS +cheerful/PY +cheerily +cheerleader +cheerless/PY +cheery/P +cheese/MS +cheesecloth +cheesy +cheetah +chef/MS +chelate +chemic +chemical/SY +chemise +chemisorb +chemisorption +chemist/MS +chemistry/S +chenille +cherish/DGS +cherry/MS +chert +cherub/MS +cherubim +chess +chest/RS +chestnut/MS +chevalier +chevron +chevy +chew/DGRSZ +chi +chianti +chic +chicanery +chick/NSX +chickadee/MS +chickweed +chicory +chide/DGS +chief/SY +chiefdom +chieftain/MS +chiffon +chigger +chignon +chilblain +child +childbirth +childhood +childish/PY +childlike +children +chili +chill/DGRSZ +chilling/Y +chilly/PR +chime/MS +chimera +chimeric +chimney/MS +chimpanzee +chin/MS +china +chinch +chinchilla +chine +chinese +chink/DS +chinned +chinner/S +chinning +chinquapin +chintz +chip/MS +chipboard +chipmunk/MS +chipping +chiropractor +chirp/DGS +chisel/DRS +chit +chiton +chivalrous/PY +chivalry +chive +chlorate +chlordane +chloride +chlorine +chloroform +chlorophyll +chloroplast/MS +chloroplatinate +chock/MS +chocolate/MS +choice/ST +choir/MS +choirmaster +choke/DGRSZ +chokeberry +cholera +cholesterol +cholinesterase +chomp +choose/GRSZ +choosy +chop/S +chopped +chopper/MS +chopping +choppy +choral +chorale +chord/MS +chordal +chordata +chordate +chore/GS +choreograph +choreography +chorine +chortle +chorus/DS +chose +chosen +chow +chowder +christen/DGS +christian/MS +christmas +chromate +chromatic +chromatogram +chromatograph +chromatography +chrome +chromic +chromium +chromosphere +chronic +chronicle/DRSZ +chronograph +chronography +chronological/Y +chronology/MS +chrysanthemum +chrysolite +chub +chubby/PRT +chuck/MS +chuckle/DS +chuckwalla +chuff +chug +chugging +chum +chumming +chummy +chump +chunk/MS +chunky +church/SY +churchgoer +churchgoing +churchman +churchmen +churchwoman +churchwomen +churchyard/MS +churn/DGS +chute/MS +chutney +cicada +cider +cigar/MS +cigarette/MS +cilia +ciliate +cimcumvention +cinch +cinder/MS +cinema +cinematic +cinnabar +cinnamon +cinquefoil +cipher/MS +circa +circle/DGS +circlet +circuit/MS +circuitous/Y +circuitry +circulant +circular/Y +circularity +circulate/DGNS +circulatory +circumcircle +circumcise/N +circumference +circumferential +circumflex +circumlocution/MS +circumpolar +circumscribe +circumscription +circumspect/Y +circumsphere +circumstance/MS +circumstantial/Y +circumvent/DGS +circumventable +circumvention +circus/MS +cirmcumferential +cistern/MS +citadel/MS +citation/MS +cite/DGS +citizen/MS +citizenry +citizenship +citrate +citric +citron +citrus +city/MS +cityscape +citywide +civet +civic/S +civil/Y +civilian/MS +civility +civilization/MS +civilize/DGS +clad +cladding +cladophora +claim/DGS +claimable +claimant/MS +clairvoyant/Y +clam/MS +clamber/DGS +clamming +clammy +clamor/DGS +clamorous +clamp/DGS +clamshell +clan +clandestine +clang/DGS +clank +clannish +clap/S +clapboard +clapping +claret +clarify/DGNSX +clarinet +clarity +clash/DGS +clasp/DGS +class/DS +classic/S +classical/Y +classifiable +classificatory +classify/DGNRSXZ +classmate/MS +classroom/MS +classy +clatter/DG +clattery +clause/MS +claustrophobia +claustrophobic +claw/DGS +clay/MS +clean/DGPRTYZ +cleaner/M +cleanly/P +cleans/DGRSZ +cleanse/DGRSZ +cleanup +clear/DGJPRSTY +clearance/MS +clearheaded +clearing/M +cleat +cleavage +cleave/DGRSZ +cleft/MS +clement +clench/DS +clergy +clergyman +clergymen +cleric +clerical +clerk/DGS +clever/PRTY +cliche/MS +click/DGS +client/MS +clientele +cliff/MS +cliffhang +climactic +climate/MS +climatic +climatically +climatology +climax/DS +climb/DGRSZ +clime/MS +clinch/DRS +cling/GS +clinic/MS +clinical/Y +clinician +clink/DR +clip/MS +clipboard +clipped +clipper/MS +clipping/MS +clique/MS +cloak/MS +cloakroom +clobber/DGS +clock/DGJRSZ +clockwatcher +clockwise +clockwork +clod/MS +cloddish +clog/MS +clogged +clogging +cloister/MS +clomp +clone/DGS +clonic +close/DGPRSTYZ +closeness/S +closet/DS +closeup +closure/MS +clot +cloth/DGS +clothbound +clothe/DGS +clothesbrush +clotheshorse +clothesline +clothesman +clothesmen +clothier +clotting +cloture +cloud/DGS +cloudburst +cloudless +cloudy/PRT +clout +clove/RS +clown/GS +cloy +club/MS +clubbed +clubbing +clubhouse +clubroom +cluck/DGS +clue/MS +clump/DGS +clumsily +clumsy/P +clung +cluster/DGJS +clutch/DGS +clutter/DGS +coach/DGRS +coachman +coachmen +coachwork +coadjutor +coagulable +coagulate +coal/S +coalesce/DGS +coalescent +coalition +coarse/PRTY +coarsen/D +coast/DGRSZ +coastal +coastline +coat/DGJS +coattail +coauthor +coax/DGRS +coaxial +cobalt +cobble/RZ +cobbler/M +cobblestone +cobol +cobra +cobweb/MS +coca +cocaine +coccidiosis +cochineal +cochlea +cock/DGS +cockatoo +cockcrow +cockeye +cockle +cocklebur +cockleshell +cockpit +cockroach +cocksure +cocktail/MS +cocky +coco +cocoa +coconut/MS +cocoon/MS +cod/DGJRZ +coda +coddle +code/DGJRSZ +codebreak +codeposit +codetermine +codeword/MS +codfish +codicil +codification/M +codifier/M +codify/DGNRSXZ +codpiece +coed +coeditor +coeducation +coefficient/MS +coequal +coerce/DGNSV +coercible +coexist/DGS +coexistence +coexistent +coextensive +cofactor +coffee/MS +coffeecup +coffeepot +coffer/MS +coffin/MS +cog +cogent/Y +cogitate/DGNS +cognac +cognate +cognition +cognitive/Y +cognizable +cognizance +cognizant +cohabitate/NX +cohere/DGS +coherence +coherent/Y +cohesion +cohesive/PY +cohort +cohosh +coiffure +coil/DGS +coin/DGRS +coinage +coincide/DGS +coincidence/MS +coincident +coincidental +coke/S +col +cola +colander +colatitude +cold/PRSTY +coleus +colicky +coliform +coliseum +collaborate/DGNSVX +collaborator/MS +collage +collagen +collapse/DGS +collapsible +collar/DGS +collarbone +collard +collate +collateral +colleague/MS +collect/DGSV +collectible +collection/MS +collective/SY +collector/MS +college/MS +collegian +collegiate +collet +collide/DGS +collie/RS +collimate +collinear +collision/MS +collocation +colloidal +colloquia +colloquial +colloquium +colloquy +collude +collusion +cologne +colon/MS +colonel/MS +colonial/SY +colonist/MS +colonization +colonize/DGRSZ +colonnade +colony/MS +color/DGJRSZ +colorado +colorate +coloratura +colorful +colorimeter +colorimetry +colorless +colossal +colossi +colossus +colt/MS +coltish +coltsfoot +columbine +column/MS +columnar +columnate/DGNS +columnize/DGS +colza +coma +comatose +comb/DGJRSZ +combat/DGSV +combatant/MS +combatted +combinate/NX +combination/M +combinational +combinator/MS +combinatorial/Y +combinatoric/S +combine/DGS +combustible +combustion +come/GHJRSYZ +comeback +comedian/MS +comedic +comedy/MS +comely/P +comestible +comet/MS +cometary +comfort/DGRSZ +comfortability/S +comfortable +comfortably +comforting/Y +comic/MS +comical/Y +comma/MS +command/DGMRSZ +commandant/MS +commandeer +commanding/Y +commandment/MS +commando +commemorate/DGNSV +commence/DGS +commencement/MS +commend/DGS +commendation/MS +commendatory +commensurable +commensurate +comment/DGS +commentary/MS +commentator/MS +commerce +commercial/PSY +commingle +commiserate +commissariat +commissary +commission/DGRSZ +commit/S +commitment/MS +committable +committal +committed +committee/MS +committeeman +committeemen +committeewoman +committeewomen +committing +commodious +commodity/MS +commodore/MS +common/PRSTYZ +commonality/S +commoner/M +commonplace/S +commonweal/H +commonwealths +commotion +communal/Y +commune/NS +communicable +communicant/MS +communicate/DGNSVX +communicator/MS +communique +communist/MS +community/MS +commutate/V +commutativity +commute/DGRSZ +compact/DGPRSTY +compactor/MS +companion/MS +companionable +companionship +companionway +company/MS +comparability +comparable +comparably +comparative/SY +comparator/MS +compare/DGS +comparison/MS +compartment/DS +compartmentalize/DGS +compass +compassion +compassionate/Y +compatibility/MS +compatible +compatibly +compatriot +compel/S +compellable +compelled +compelling/Y +compendia +compendium +compensable +compensate/DGNSX +compensatory +compete/DGS +competence +competent/Y +competition/MS +competitive/Y +competitor/MS +compilation/MS +compile/DGRSZ +compiler/M +complacent +complain/DGRSZ +complainant +complaint/MS +complaisant +complement/DGRSZ +complementarity +complementary +complementation +complete/DGNPSXY +complex/SY +complexion +complexity/S +compliance +compliant +complicate/DGNSX +complicator/MS +complicity +compliment/DGRSZ +complimentary +compline +comply/DGNX +component/MS +componentry +componentwise +comport +compose/DGRSZ +composed/Y +composite/NSX +compositional +compositor +compost +composure +compote +compound/DGS +comprehend/DGS +comprehensibility +comprehensible +comprehension +comprehensive/Y +compress/DGSV +compressible +compression +compressor +comprise/DGS +compromise/DGRSZ +compromising/Y +comptroller/MS +compulsion/MS +compulsive +compulsory +compunction +computability +computable +computation/MS +computational/Y +compute/DGRSZ +computer/M +computerize/DGS +comrade/SY +comradeship +con/S +concatenate/DGNSX +concave +conceal/DGRSZ +concealment +concede/DGS +conceit/DS +conceivable +conceivably +conceive/DGS +concentrate/DGNSX +concentrator/S +concentric +concept/MS +conception/MS +conceptual/Y +conceptualization/MS +conceptualize/DGS +concern/DGS +concerned/Y +concert/DS +concerti +concertina +concertmaster +concerto +concession/MS +concessionaire +conch +concierge +conciliate +conciliatory +concise/NPY +conclave +conclude/DGS +conclusion/MS +conclusive/Y +concoct +concomitant +concord +concordant +concourse +concrete/NPSY +concubine +concur/S +concurred +concurrence +concurrency/S +concurrent/Y +concurring +concussion +condemn/DGRSZ +condemnate/NX +condemnatory +condensate/N +condense/DGRS +condensible +condescend/G +condescension +condiment +condition/DGRSZ +conditional/SY +condolence +condone/DGS +conduce/V +conduct/DGSV +conductance +conduction +conductivity +conductor/MS +conduit +cone/MS +coneflower +coney +confabulate +confect +confectionery +confederacy +confederate/NSX +confer/S +conferee +conference/MS +conferrable +conferred +conferrer/MS +conferring +confess/DGS +confession/MS +confessor/MS +confidant/MS +confidante +confide/DGS +confidence/S +confident/Y +confidential/Y +confidentiality +confiding/Y +configurable +configuration/MS +configure/DGS +confine/DGRS +confinement/MS +confirm/DGS +confirmation/MS +confirmatory +confiscable +confiscate/DGNSX +confiscatory +conflagrate +conflict/DGS +confluent +confocal +conform/DGS +conformal +conformance +conformation +conformity +confound/DGS +confrere +confront/DGRSZ +confrontation/MS +confuse/DGNRSXZ +confusing/Y +confute +congeal +congener +congenial/Y +congenital +congest/DV +congestion +conglomerate +congratulate/DNX +congratulatory +congregate/DGNSX +congress/MS +congressional/Y +congressman +congressmen +congresswoman +congresswomen +congruence +congruent +conic +conifer +coniferous +conjectural +conjecture/DGS +conjoin/D +conjoint +conjugal +conjugate +conjunct/DSV +conjunction/MS +conjunctive/Y +conjuncture +conjure/DGRS +conn/V +connect/DGSV +connected/P +connection/MS +connective/MS +connectivity +connector/MS +connivance +connoisseur/MS +connotation +connotative +connote/DGS +connubial +conquer/DGRSZ +conquerable +conqueror/MS +conquest/MS +conquistador +consanguine +consanguineous +conscience/MS +conscientious/Y +conscionable +conscious/PY +conscript +conscription +consecrate/N +consecutive/Y +consensus +consent/DGRSZ +consequence/MS +consequent/SY +consequential +consequentiality/S +conservation/MS +conservationist/MS +conservatism +conservative/SY +conservator +conservatory +conserve/DGS +consider/DGS +considerable +considerably +considerate/NXY +consign/DGS +consignee +consignor +consist/DGS +consistency +consistent/Y +consolable +consolation/MS +console/DGRSZ +consolidate/DGNS +consoling/Y +consonant/MS +consonantal +consort/DGS +consortium +conspicuous/Y +conspiracy/MS +conspirator/MS +conspiratorial +conspire/DS +constable/MS +constancy +constant/SY +constellate/NX +constellation/M +consternate/N +constituency/MS +constituent/MS +constitute/DGNSVX +constitutional/Y +constitutionality +constrain/DGS +constraint/MS +constrict +constrictor +construct/DGSV +constructibility +constructible +construction/MS +constructive/Y +constructor/MS +construe/DG +consul/MS +consular +consulate/MS +consult/DGS +consultant/MS +consultation/MS +consultative +consumable +consume/DGRSZ +consumer/M +consummate/DNY +consumption/MS +consumptive/Y +contact/DGS +contagion +contagious/Y +contain/DGRSZ +containable +containment/MS +contaminant +contaminate/DGNS +contemplate/DGNSVX +contemporaneous +contemporary/PS +contempt +contemptible +contemptuous/Y +contend/DGRSZ +content/DGSY +contention/MS +contentious +contentment +contest/DGRSZ +contestable +contestant +context/MS +contextual/Y +contiguity +contiguous/Y +continent/MS +continental/Y +contingency/MS +contingent/MS +continua +continual/Y +continuance/MS +continuant +continuation/MS +continue/DGS +continuity/S +continuo +continuous/Y +continuum +contort +contour/DGMS +contraband +contrabass +contraception +contraceptive +contract/DGS +contraction/MS +contractor/MS +contractual/Y +contradict/DGS +contradiction/MS +contradictory +contradistinct +contradistinction/S +contradistinguish +contralateral +contralto +contrapositive/S +contraption/MS +contrariety +contrary/P +contrast/DGRSZ +contrasting/Y +contravariant +contravene +contravention +contretemps +contribute/DGNSX +contributor/MS +contributorily +contributory +contrite/N +contrivance/MS +contrive/DGRS +control/MS +controllability +controllable +controllably +controlled +controller/MS +controlling +controversial +controversy/MS +controvertible +contumacy +contusion +conundrum/MS +convalesce +convalescent +convect +convene/DGS +convenience/MS +convenient/Y +convent/MS +convention/MS +conventional/Y +converge/DGS +convergence +convergent +conversant/Y +conversation/MS +conversational/Y +converse/DGNSXY +convert/DGRSZ +convertibility +convertible +convex +convey/DGRSZ +conveyance/MS +conveyor +convict/DGS +conviction/MS +convince/DGRSZ +convincing/Y +convivial +convocate +convoke +convolute/DN +convolve +convoy/DGS +convulse/NVX +convulsion/M +cony +coo/G +cook/DGS +cookbook +cookery +cookie/MS +cooky/S +cool/DGPRSTYZ +coolant +cooler/M +coolheaded +coolie/MS +coon/MS +coop/DRSZ +cooperate/DGNSVX +cooperative/SY +cooperator/MS +coordinate/DGNSX +coordinator/MS +coot +cop/DGJMS +cope/DGJS +copious/PY +coplanar +copolymer +copper/MS +copperas +copperhead +coppery +copra +coprinus +copse +copter +copy/DGRSZ +copybook +copyright/MS +copywriter +coquette +coquina +coral +coralberry +coralline +corbel +cord/DRS +cordage +cordial/Y +cordite +cordon +corduroy +core/DGRSZ +coriander +cork/DGRSZ +corkscrew +cormorant +corn/GRSZ +cornbread +cornea +corner/D +cornerstone/MS +cornet +cornfield/MS +cornflower +cornish +cornmeal +cornstarch +cornucopia +corny +corollary/MS +corona +coronary/S +coronate/N +coroner +coronet/MS +coroutine/MS +corpora +corporacy/S +corporal/MS +corporate/NXY +corporation/M +corporeal +corps/S +corpse/MS +corpsman +corpsmen +corpulent +corpus +corpuscular +corral +corralled +correct/DGPSVY +correctable +correction/S +corrective/SY +corrector +correlate/DGNSVX +correspond/DGS +correspondence/MS +correspondent/MS +corresponding/Y +corridor/MS +corrigenda +corrigendum +corrigible +corroborate/DGNSVX +corroboree +corrode +corrodible +corrosion +corrosive +corrugate +corrupt/DGRS +corruptible +corruption +corsage +corset +cortege +cortex +cortical +corundum +coruscate +corvette +cos +cosec +coset +cosh +cosine/S +cosmetic/S +cosmic +cosmology +cosmopolitan +cosmos +cosponsor +cost/DGSY +costume/DGRS +cosy +cot/MS +cotangent +cotillion +cotman +cotoneaster +cotta +cottage/RS +cotton/S +cottonmouth +cottonseed +cottonwood +cottony +cotty +cotyledon/MS +couch/DGS +cougar +cough/DG +coughs +could +couldn't +coulomb +council/MS +councillor/MS +councilman +councilmen +councilwoman +councilwomen +counsel/DGS +counselled +counselling +counsellor/MS +counselor/MS +count/DGRSZ +countable +countably +countenance +counter/DG +counteract/DGV +counterargument +counterattack +counterbalance +counterclockwise +counterexample/S +counterfeit/DGR +counterflow +counterintuitive +counterman +countermeasure/MS +countermen +counterpart/MS +counterpoint/G +counterpoise +counterproductive +counterproposal +counterrevolution +countersink +countersunk +countervail +countess +countless +countrify +country/MS +countryman +countrymen +countryside +countrywide +county/MS +countywide +coup +coupe +couple/DGJRSZ +coupon/MS +courage +courageous/Y +courier/MS +course/DGRS +court/DGRSYZ +courteous/Y +courtesan +courtesy/MS +courthouse/MS +courtier/MS +courtroom/MS +courtship +courtyard/MS +cousin/MS +couturier +covalent +covariant +covariate +covary +cove/RSZ +coven +covenant/MS +cover/DGJ +coverable +coverage +coverall +coverlet/MS +covert/Y +covet/DGS +covetous/P +cow/DGRSZ +coward/Y +cowardice +cowbell +cowbird +cowboy/MS +cower/DGRZ +cowering/Y +cowhand +cowherd +cowhide +cowl/GS +cowlick +cowman +cowmen +coworker +cowpea +cowpoke +cowpony +cowpox +cowpunch +cowry +cowslip/MS +cox +coxcomb +coy +coyote/MS +coypu +cozen +cozy/PR +crab/MS +crabapple +crabbing +crack/DGRSZ +crackle/DGS +crackpot +cradle/DS +craft/DGRS +craftsman +craftsmen +craftspeople +craftsperson +crafty/P +crag/MS +craggy +cram/S +cramming +cramp/MS +cranberry/MS +crane/MS +cranelike +crania +cranium +crank/DGS +crankcase +crankily +crankshaft +cranky/RT +cranny +crap +crappie +crash/DGRSZ +crass +crate/RSZ +cravat/MS +crave/DGS +craven +craw +crawl/DGRSZ +crawlspace +crayfish +crayon +craze/DGS +crazily +crazy/PRT +creak/DGS +creaky +cream/DGRSZ +creamery +creamy +crease/DGS +create/DGNSVX +creative/PY +creativity +creator/MS +creature/MS +creche +credence +credent +credential +credenza +credibility +credible +credibly +credit/DGS +creditable +creditably +creditor/MS +credo +credulity +credulous/P +creed/MS +creedal +creek/MS +creekside +creep/GRSZ +creepy +cremate/DGNSX +crematory +creosote +crepe +crept +crescendo +crescent/MS +cress +crest/DS +crestfallen +cretin +cretinous +crevice/MS +crew/DGS +crewcut +crewel +crewman +crewmen +crib/MS +cribbing +cricket/MS +crime/MS +criminal/SY +crimp +crimson/G +cringe/DGS +crinkle +cripple/DGS +crises +crisis +crisp/PY +criss +crisscross +criteria +criterion +critic/MS +critical/Y +criticise/D +criticism/MS +criticize/DGS +critique/GS +critter +croak/DGS +crochet/S +crock +crockery +crocodile +crocodilian +crocus +croft +crone +crony +crook/DS +croon +crop/MS +cropped +cropper/MS +cropping +cross/DGJRSYZ +crossable +crossarm +crossbar/MS +crossbill +crosscut +crosshatch +crossover/MS +crosspoint +crossroad +crosstalk +crosswalk +crossway +crosswise +crossword/MS +crosswort +crotch +crotchety +crouch/DG +croupier +crow/DGS +crowbait +crowberry +crowd/DGRS +crowfoot +crown/DGS +croydon +crucial/Y +crucible +crucifix +crucifixion +crucify/DGS +crud/T +cruddy +crude/PTY +cruel/RTY +cruelty +cruise/GRSZ +crumb/SY +crumble/DGS +crummy +crump +crumple/DGS +crunch/DGS +crunchy/RT +crupper +crusade/GRSZ +crush/DGRSZ +crushable +crushing/Y +crust/MS +crustacean/MS +crutch/MS +crux/MS +cry/DGRSZ +cryogenic +cryostat +crypt +cryptanalysis +cryptanalyst +cryptanalytic +cryptic +cryptogram +cryptographer +cryptographic +cryptography +cryptology +crystal/MS +crystalline +crystallite +crystallize/DGS +crystallographer +crystallography +cub/DMS +cubbyhole +cube/DS +cubic +cuckoo/MS +cucumber/MS +cud +cuddle/D +cuddly +cudgel/MS +cue/DS +cuff/MS +cufflink +cuisine +culinary +cull/DGRS +culminate/DGNS +culpa +culpable +culprit/MS +cult/MS +cultivable +cultivate/DGNSX +cultivator/MS +cultural/Y +culture/DGS +culvert +cumbersome +cumin +cumulate/V +cumulative/Y +cumulus +cunning/Y +cup/MS +cupboard/MS +cupful +cupidity +cupped +cupping +cupric +cuprous +cur/DGSY +curable +curably +curate +curb/GS +curbside +curd +curdle +cure/DGS +curfew/MS +curia +curie +curio +curiosity/MS +curious/RTY +curium +curl/DGRSZ +curlew +curlicue +currant/MS +currency/MS +current/PSY +curricula +curricular +curriculum/MS +curry/DGS +curs/DGSV +curse/DGSV +cursor/MS +cursorily +cursory +curt/PY +curtail/DS +curtain/DS +curtate +curtsey +curtsy/MS +curvaceous +curvature +curve/DGS +curvilinear +cushion/DGS +cusp/MS +custard +custodial +custodian/MS +custody +custom/RSZ +customarily +customary +customhouse +customizable +customization/MS +customize/DGRSZ +cut/MST +cutaneous +cutback +cute/T +cutlass +cutler +cutlet +cutoff +cutout +cutover +cutter/MS +cutthroat +cutting/SY +cuttlebone +cuttlefish +cutworm +cyanate +cyanic +cyanide +cybernetic/S +cycad +cycle/DGS +cyclic +cyclically +cyclist +cycloid/MS +cycloidal +cyclone/MS +cyclopean +cyclorama +cyclotron +cylinder/MS +cylindric +cymbal/MS +cynic +cynical/Y +cypress +cyst/S +cytochemistry +cytology +cytolysis +cytoplasm +czar +czarina +d'art +d'etat +d'oeuvre +d's +d/VX +dab +dabbing +dabble/DGRS +dachshund +dactyl +dactylic +dad/MS +daddy +daemon/MS +daffodil/MS +daffy +dagger +dahlia +daily/S +daintily +dainty/P +dairy +dairyman +dairymen +dais +daisy/MS +dale/MS +dally +dam/MS +damage/DGRSZ +damask +dame +damming +damn/DGS +damnation +damp/GNPRX +damsel/MS +dance/DGRSZ +dandelion/MS +dandy +dang/RZ +danger/M +dangerous/Y +dangle/DGS +dank +dapper +dapple +dare/DGRSZ +daresay +daring/Y +dark/NPRTY +darkle +darling/MS +darn/DGRS +dart/DGRS +dash/DGRSZ +dashboard +dashing/Y +dastard +data +database/MS +date/DGRSV +dateline +datum +daub +daughter/SY +daunt/D +dauntless +dauphin +dauphine +davenport +davit +dawn/DGS +day/MS +daybed +daybreak +daydream/GS +daylight/MS +daytime +daze/D +dazzle/DGRS +dazzling/Y +deacon/MS +deaconess +deactivate +dead/NPY +deadhead +deadline/MS +deadlock/DGS +deadwood +deaf/NPRT +deal/GJRSZ +deallocate/DN +dealt +dean/MS +dear/HPRTY +dearie +dearths +death/Y +deathbed +deathrate/MS +deaths +deathward +debacle +debar +debarring +debase +debatable +debate/DGRSZ +debauch +debauchery +debenture +debilitate/DGS +debility +debit +debonair +debrief +debris +debt/MS +debtor +debug/S +debugged +debugger/MS +debugging +debunk +debut +debutante +decade/MS +decadence +decadent/Y +decal +decant +decathlon +decay/DGS +decease/DGS +decedent +deceit +deceitful/PY +deceive/DGRSZ +decelerate/DGNS +december +decency/MS +decennial +decent/Y +decentralization +decentralized +deception/MS +deceptive/Y +decertify +decibel +decidability +decidable +decide/DGS +decided/Y +deciduous +decile +decimal/S +decimate/DGNS +decipher/DGRS +decision/MS +decisional +decisive/PY +deck/DGJS +declaim +declamation +declamatory +declaration/MS +declarative/SY +declarator +declaratory +declare/DGRSZ +declassify +declination/MS +decline/DGRSZ +declivity +decode/DGJRSZ +decolletage +decollimate +decompile +decomposability +decomposable +decompose/DGS +decomposition/MS +decompress +decompression +decontrol +decontrolled +decontrolling +deconvolution +deconvolve +decor +decorate/DGNSVX +decorous +decorticate +decorum +decouple/DGS +decoy/MS +decrease/DGS +decreasing/Y +decree/DS +decreeing +decrement/DGS +decry +decrypt +decryption +dedicate/DGNS +deduce/DGRS +deducible +deduct/DGV +deductible +deduction/MS +deed/DGS +deem/DGS +deemphasize/DGS +deep/NRSTXY +deepen/DG +deer +deerskin +deerstalker +deface +default/DGRS +defeat/DGS +defecate +defect/DGSV +defection/MS +defend/DGRSZ +defendant/MS +defenestrate/DGNS +defense/SV +defenseless +defensible +defer/S +deference +deferent +deferment/MS +deferrable +deferred +deferrer/MS +deferring +defiance +defiant/Y +deficiency/S +deficient +deficit/MS +defile/G +definable +define/DGRS +definite/NPVXY +definition/M +definitional +deflate/R +deflect +deflector +defocus +deforest +deforestation +deform/D +deformation/MS +deformity/MS +defraud +defray +defrost +deft/Y +defunct +defy/DGS +degas +degassing +degeneracy +degenerate/DGNSV +degradable +degradation/MS +degrade/DGS +degrease +degree/MS +degum +degumming +dehumidify +dehydrate +deify +deign/DGS +deity/MS +deja +deject/D +dejected/Y +delaware +delay/DGS +delectable +delectate +delegable +delegate/DGNSX +delete/DGNRSX +deleterious +deliberate/DGNPSVXY +deliberator/MS +delicacy/MS +delicate/Y +delicatessen +delicious/Y +delicti +delight/DGS +delighted/Y +delightful/Y +delimit/DGRSZ +delimitation +delineament +delineate/DGNS +delinquent +deliquesce +deliquescent +delirious/Y +delirium +deliver/DGRSZ +deliverable/S +deliverance +delivery/MS +dell/MS +delouse +delphine +delphinium +delta/MS +deltoid +delude/DGS +deluge/DS +delusion/MS +delusive +deluxe +delve/GS +demagnify +demagogue +demand/DGRS +demanding/Y +demarcate +demark +demean +demeanor +demented +demerit +demigod +demijohn +demiscible +demise +demit +demitted +demitting +democracy/MS +democrat/MS +democratic +democratically +demodulate +demographic +demography +demolish/DS +demolition +demon/MS +demoniac +demonic +demonstrable +demonstrate/DGNSVX +demonstrative/Y +demonstrator/MS +demoralize/DGS +demote +demountable +demultiplex +demur +demure +demurred +demurrer +demurring +demythologize +den/MS +denature +deniable +denial/MS +denigrate/DGS +denizen +denmark +denominate/NX +denomination/M +denominator/MS +denotable +denotation/MS +denotational/Y +denotative +denote/DGS +denouement +denounce/DGS +dens/RT +dense/PRTY +densitometer +densitometric +densitometry +density/MS +dent/DGS +dental/Y +dentist/MS +dentistry +denture +denudation +denude +denumerable +denunciate +deny/DGRS +deodorant +deoxyribonucleic +depart/DGS +department/MS +departmental +departure/MS +depend/DGS +dependability +dependable +dependably +dependence +dependency/S +dependent/SY +depict/DGS +deplete/DGNSX +deplorable +deplore/DS +deploy/DGS +deployment/MS +deport +deportation +deportee +deportment +depose/DS +deposit/DGS +depositary +deposition/MS +depositor/MS +depository +depot/MS +deprave/D +deprecate +deprecatory +depreciable +depreciate/NS +depress/DGSV +depressant +depressible +depression/MS +depressor +deprivation/MS +deprive/DGS +depth +depths +deputation +depute +deputy/MS +dequeue/DGS +derail/DGS +derange +derate +derby +dereference +deregulate +derelict +deride +derision +derisive +derivable +derivate/NVX +derivation/M +derivative/MS +derive/DGS +derogate +derogatory +derrick +derriere +dervish +descant +descend/DGRSZ +descendant/MS +descendent +descent/MS +describable +describe/DGRS +description/MS +descriptive/SY +descriptor/MS +descry +desecrate/R +desegregate +desert/DGRSZ +desertion/S +deserve/DGJS +deserving/Y +desiderata +desideratum +design/DGRSZ +designate/DGNSX +designator/MS +designer/M +desirability +desirable +desirably +desire/DGS +desirous +desist +desk/MS +desolate/NRXY +desorption +despair/DGS +despairing/Y +despatch/D +desperado +desperate/NY +despicable +despise/DGS +despite +despoil +despond +despondent +despot/MS +despotic +dessert/MS +dessicate +destabilize +destinate/NX +destination/M +destine/D +destiny/MS +destitute/N +destroy/DGRSZ +destroyer/M +destruct/V +destruction/MS +destructive/PY +destructor +desuetude +desultory +desynchronize +detach/DGRS +detachment/MS +detail/DGS +detain/DGS +detect/DGSV +detectable +detectably +detection/MS +detective/S +detector/MS +detent +detente/N +deter +detergent +deteriorate/DGNS +determinable +determinacy +determinant/MS +determinate/NVXY +determine/DGRSZ +determinism +deterministic +deterministically +deterred +deterrent +deterring +detest/D +detestable +detestation +detonable +detonate +detour +detract/S +detractor/MS +detriment +deuce +deus +deuterate +deuterium +devastate/DGNS +develop/DGRSZ +development/MS +developmental +deviant/MS +deviate/DGNSX +device/MS +devil/MS +devilish/Y +devious +devise/DGJS +devisee +devoid +devolve +devote/DGNSX +devoted/Y +devotee/MS +devour/DRS +devout/PY +dew +dewar +dewdrop/MS +dewy +dexter +dexterity +dextrous +dey +dharma +diabase +diabetes +diabetic +diabolic +diachronic +diacritical +diadem +diagnosable +diagnose/DGS +diagnosis +diagnostic/MS +diagnostician +diagonal/SY +diagram/MS +diagrammable +diagrammatic +diagrammatically +diagrammed +diagrammer/MS +diagramming +dial/DGS +dialect/MS +dialectic +dialog/MS +dialogue/MS +dialysis +diamagnetic +diamagnetism +diameter/MS +diametric +diametrically +diamond/MS +diaper/MS +diaphanous +diaphragm/MS +diary/MS +diathermy +diathesis +diatom +diatomaceous +diatomic +diatonic +diatribe/MS +dibble +dice +dichloride +dichondra +dichotomize +dichotomy +dick/X +dickcissel +dickey +dicky +dicotyledon +dicta +dictate/DGNSX +dictator/MS +dictatorial +dictatorship +diction +dictionary/MS +dictum/MS +did +didactic +diddle +didn't +die/DS +diehard +dieldrin +dielectric/MS +diem +diesel +diet/RSZ +dietary +dietetic +diethylstilbestrol +dietician +dietitian/MS +diety +differ/DGNRSZ +difference/MS +different/Y +differentiable +differential/MS +differentiate/DGNSX +differentiators +difficult/Y +difficulty/MS +diffident +diffract +diffractometer +diffuse/DGNRSVXYZ +diffusible +difluoride +dig/ST +digest/DGSV +digestible +digestion +digger/MS +digging/S +digit/MS +digital/Y +digitalis +dignify/D +dignitary +dignity/S +digram +digress/DGSV +digression/MS +dihedral +dike/MS +dilapidate +dilatation +dilate/DGNS +dilatory +dilemma/MS +dilettante +diligence +diligent/Y +dill +dilogarithm +diluent +dilute/DGNS +dim/PSY +dime/MS +dimension/DGS +dimensional/Y +dimensionality +dimethyl +diminish/DGS +diminution +diminutive +dimmed +dimmer/MS +dimmest +dimming +dimple +din/DGRZ +dine/DGRSZ +ding +dinghy +dingo +dingy/P +dinner/MS +dinnertime +dinnerware +dinosaur +dint +diocesan +diocese +diode/MS +diophantine +diopter +diorama +diorite +dioxide +dip/S +diphtheria +diphthong +diploma/MS +diplomacy +diplomat/MS +diplomatic +dipole +dipped +dipper/MS +dipping/S +dire +direct/DGPSVY +direction/MS +directional/Y +directionality +directive/MS +director/MS +directorate +directory/MS +directrices +directrix +dirge/MS +dirt/S +dirtily +dirty/PRT +disability/MS +disable/DGRSZ +disadvantage/MS +disagree/DGS +disagreeable +disagreeing +disagreement/MS +disallow/DGS +disambiguate/DGNSX +disappear/DGS +disappearance/MS +disappoint/DG +disappointment/MS +disapproval +disapprove/DS +disarm/DGS +disarmament +disassemble/DGS +disaster/MS +disastrous/Y +disband/DGS +disburse/DGS +disbursement/MS +disc/MS +discard/DGS +discern/DGS +discernibility +discernible +discernibly +discerning/Y +discernment +discharge/DGS +disciple/MS +disciplinary +discipline/DGS +disclaim/DRS +disclose/DGS +disclosure/MS +discoid +discomfit +discomfort +disconcert/G +disconcerting/Y +disconnect/DGS +disconnection +discontent/D +discontinuance +discontinue/DS +discontinuity/MS +discontinuous +discord +discordant +discount/DGS +discourage/DGS +discouragement +discourse/MS +discover/DGRSZ +discovery/MS +discredit/D +discreet/Y +discrepancy/MS +discrepant +discrete/NPY +discretionary +discriminable +discriminant +discriminate/DGNS +discriminatory +discrot +discus +discuss/DGS +discussant +discussion/MS +disdain/GS +disdainful +disease/DS +disembowel +disengage/DGS +disfigure/DGS +disgorge +disgrace/DS +disgraceful/Y +disgruntle/D +disguise/DS +disgust/DGS +disgusted/Y +disgustful +disgusting/Y +dish/DGS +dishearten/G +dishevel +dishonest/Y +dishonor/DGS +dishwasher/S +dishwashing +dishwater +disillusion/DG +disillusionment/MS +disinterested/P +disjoint/DP +disjunct/SV +disjunction/S +disjunctive/Y +disk/MS +dislike/DGS +dislocate/DGNSX +dislodge/D +dismal/Y +dismay/DG +dismiss/DGRSZ +dismissal/MS +dismount/DGS +disobedience +disobey/DGS +disorder/DSY +disorganized +disown/DGS +disparage +disparate +disparity/MS +dispatch/DGRSZ +dispel/S +dispell/DGS +dispensary +dispensate/N +dispense/DGRSZ +dispersal +disperse/DGNSVX +dispersible +displace/DGS +displacement/MS +display/DGS +displease/DGS +displeasure +disposable +disposal/MS +dispose/DGRS +disposition/MS +disprove/DGS +disputant +dispute/DGRSZ +disqualify/DGNS +disquiet/G +disquietude +disquisition +disregard/DGS +disrupt/DGSV +disruption/MS +dissatisfaction/MS +dissatisfied +dissemble +disseminate/DGNS +dissension/MS +dissent/DGRSZ +dissertation/MS +disservice +dissident/MS +dissimilar +dissimilarity/MS +dissipate/DGNS +dissociable +dissociate/DGNS +dissolution/MS +dissolve/DGS +dissonant +dissuade +distaff +distal/Y +distance/S +distant/Y +distaste/S +distasteful/Y +distemper +distill/DGRSZ +distillate/N +distillery +distinct/PVY +distinction/MS +distinctive/PY +distinguish/DGS +distinguishable +distort/DGS +distortion/MS +distract/DGS +distraction/MS +distraught +distress/DGS +distribute/DGNSVX +distribution/M +distributional +distributivity +distributor/MS +district/MS +distritbute/DGS +distrust/D +disturb/DGRS +disturbance/MS +disturbing/Y +disulfide +disyllable +ditch/MS +dither +ditto +ditty +diurnal +diva +divalent +divan/MS +dive/DGRSTZ +diverge/DGS +divergence/MS +divergent +diverse/NXY +diversify/DGNS +diversionary +diversity/S +divert/DGS +divest/DGS +divestiture +divide/DGRSZ +dividend/MS +divination +divine/GRY +divinity/MS +divisible +division/MS +divisional +divisive +divisor/MS +divorce/D +divorcee +divulge/DGS +dixieland +dizzy/P +do/GJRZ +doberman +dobson +docile +dock/DS +docket +dockside +dockyard +doctor/DS +doctoral +doctorate/MS +doctrinaire +doctrinal +doctrine/MS +document/DGRSZ +documentary/MS +documentation/MS +dodecahedra +dodecahedral +dodecahedron +dodge/DGRZ +doe/GJRSZ +doesn't +doff +dog/MS +dogbane +dogberry +dogfish +dogged/PY +dogging +doggone +doghouse +dogleg +dogma/MS +dogmatic +dogmatism +dogtooth +dogtrot +dogwood +dolce +doldrum +dole/DS +doleful/Y +doll/MS +dollar/S +dolly/MS +dolomite +dolomitic +dolphin/MS +dolt +doltish +domain/MS +dome/DS +domestic +domestically +domesticate/DGNS +domicile +dominance +dominant/Y +dominate/DGNS +domineer +dominion +domino +don't +don/S +donate/DGS +done +donkey/MS +donning +donnybrook +donor +doodle +doom/DGS +doomsday +door/MS +doorbell +doorkeep/R +doorknob +doorman +doormen +doorstep/MS +doorway/MS +dopant +dope/DGRSZ +dormant +dormitory/MS +dosage +dose/DS +dosimeter +dossier +dot/DGMS +dote/DGS +doting/Y +dotted +dotting +double/DGRSZ +doubleheader +doublet/MS +doubleton +doubloon +doubly +doubt/DGRSZ +doubtable +doubtful/Y +doubtless/Y +douce +dough +doughnut/MS +dour +douse +dove/RS +dovekie +dovetail +dowager +dowel +dowitcher +down/DGSZ +downbeat +downcast +downdraft +downfall/N +downgrade +downhill +downplay/DGS +downpour +downright +downside +downslope +downspout +downstairs +downstream +downtown/S +downtrend +downtrodden +downturn +downward/S +downwind +downy +dowry +doze/DGS +dozen/HS +drab +draft/DGRSZ +draftee +draftsman +draftsmen +draftsperson +drafty +drag/S +dragged +dragging +dragnet +dragon/MS +dragonfly +dragonhead +dragoon/DS +drain/DGRS +drainage +drake +dram +drama/MS +dramatic/S +dramatically +dramatist/MS +dramaturgy +drank +drape/DRSZ +drapery/MS +drastic +drastically +draught/MS +draw/GJRSZ +drawback/MS +drawbridge/MS +drawl/DGS +drawn/PY +dread/DGS +dreadful/Y +dreadnought +dream/DGRSZ +dreamboat +dreamily +dreamlike +dreamt +dreamy +dreary/P +dredge +dreg/S +drench/DGS +dress/DGJRSZ +dressmake/RZ +dressmaker/M +dressy +drew +drib +dribble +drier/M +drift/DGRSZ +drill/DGRS +drily +drink/GRSZ +drinkable +drip/MS +dripping +drippy +drive/GRSZ +driven +driveway/MS +drizzle +drizzly +droll +dromedary +drone/MS +drool +droop/DGS +droopy +drop/MS +drophead +droplet +dropout +dropped +dropper/MS +dropping/MS +drosophila +dross +drought/MS +drove/RSZ +drown/DGJS +drowse +drowsy/P +drub +drubbing +drudge +drudgery +drug/MS +drugging +druggist/MS +drugstore +druid +drum/MS +drumhead +drumlin +drummed +drummer/MS +drumming +drunk/NRSY +drunkard/MS +drunken/P +dry/DGRSTYZ +dryad +du/Y +dual +dualism +duality/MS +dub/S +dubious/PY +dubitable +ducat +duchess/MS +duchy +duck/DGS +duckling +duct +ductile +ductwork +dud +due/S +duel/GS +duet +duff +duffel +dug +dugout +duke/MS +dulcet +dull/DGPRST +dully +dulse +dumb/PRTY +dumbbell/MS +dummy/MS +dump/DGRS +dumpy +dun +dunce/MS +dune/MS +dung +dungeon/MS +dunk +duopolist +duopoly +dupe +duplex +duplicable +duplicate/DGNSX +duplicator/MS +duplicity +durability/S +durable +durably +duration/MS +duress +during +dusk +dusky/P +dust/DGRSZ +dustbin +dusty/RT +dutchess +dutiable +dutiful/PY +duty/MS +dwarf/DS +dwarves +dwell/DGJRSZ +dwelt +dwindle/DG +dyad +dyadic +dye/DGRSZ +dyeing +dynamic/S +dynamically +dynamism +dynamite/DGS +dynamo +dynast +dynastic +dynasty/MS +dyne +dysentery +dyspeptic +dysplasia +dysprosium +dystrophy +e'er +e's +e/X +each +eager/PY +eagle/MS +ear/DHSY +eardrum +earl/MS +early/PRT +earmark/DGJS +earn/DGJRSTZ +earner/M +earnest/PY +earphone +earring/MS +earsplitting +earth/NY +earthenware +earthly/P +earthmen +earthmover +earthmoving +earthquake/MS +earths +earthworm/MS +earthy +earwig +ease/DGS +easel +easement/MS +easily +east/R +eastbound +eastern/RZ +easternmost +eastward/S +easy/PRT +easygoing +eat/GJNRSZ +eave/S +eavesdrop/S +eavesdropped +eavesdropper/MS +eavesdropping +ebb/GS +ebony +ebullient +eccentric/MS +eccentricity/S +ecclesiastic +ecclesiastical +echelon +echinoderm +echo/DG +echoes +eclat +eclectic +eclipse/DGS +ecliptic +eclogue +ecology +econometric +economic/S +economical/Y +economist/MS +economize/DGRSZ +economy/MS +ecosystem +ecstasy +ecstatic +ecumenic +ecumenist +eddy/MS +edelweiss +edematous +edge/DGS +edgewise +edgy +edible +edict/MS +edifice/MS +edify +edit/DGS +edition/MS +editor/MS +editorial/SY +educable +educate/DGNSX +educational/Y +educator/MS +eel/MS +eelgrass +eerie +eerily +efface +effaceable +effect/DGSV +effective/PY +effector/MS +effectual/Y +effectuate +effeminate +efferent +effete +efficacious +efficacy +efficiency/S +efficient/Y +effigy +effloresce +efflorescent +effluent +effluvia +effluvium +effort/MS +effortless/PY +effusive +eft +egalitarian +egg/DGS +egghead +eggplant +eggshell +ego/S +egocentric +egotism +egotist +egregious +egress +egret +eh +eider +eidetic +eigenfunction +eigenstate +eigenvalue/MS +eigenvector +eight/S +eighteen/HS +eightfold +eighth/MS +eightieth +eighty/S +einsteinium +either +ejaculate/DGNSX +eject/DGS +ejector +eke/DS +el +elaborate/DGNPSXY +elaborators +elan +elapse/DGS +elastic +elastically +elasticity +elastomer +elate +elbow/GS +elder/SY +eldest +elect/DGSV +election/MS +elective/S +elector/MS +electoral +electorate +electress +electret +electric +electrical/PY +electrician +electricity +electrify/GN +electro +electrocardiogram +electrocardiograph +electrocute/DGNSX +electrode/MS +electroencephalogram +electroencephalograph +electroencephalography +electrolysis +electrolyte/MS +electrolytic +electron/MS +electronic/S +electronically +electrophoresis +electrophorus +elegance +elegant/Y +elegiac +elegy +element/MS +elemental/S +elementary +elephant/MS +elephantine +elevate/DNS +elevator/MS +eleven/HS +elf +elfin +elicit/DGS +elide +eligibility +eligible +eliminate/DGNSX +eliminator/S +elision +elite +elk/MS +ell +ellipse/MS +ellipsis +ellipsoid/MS +ellipsoidal +ellipsometer +ellipsometry +elliptic +elliptical/Y +elm/RS +elongate +elope +eloquence +eloquent/Y +else +elsewhere +eluate +elucidate/DGNS +elude/DGS +elusive/PY +elute/N +elves +elysian +em +emaciate/D +emanate/G +emancipate/N +emasculate +embalm +embank +embarcadero +embargo +embargoes +embark/DS +embarrass/DGS +embarrassment +embassy/MS +embattle +embed/S +embedded +embedder +embedding +embellish/DGS +embellishment/MS +ember +embezzle +emblem +emblematic +embodiment/MS +embody/DGS +embolden +emboss +embouchure +embower +embrace/DGS +embraceable +embrittle +embroider/DS +embroidery/S +embroil +embryo/MS +embryology +embryonic +emcee +emendable +emerald/MS +emerge/DGS +emergence +emergency/MS +emergent +emeritus +emery +emigrant/MS +emigrate/DGNS +eminence +eminent/Y +emirate +emissary +emission +emissivity +emit/S +emittance +emitted +emitter +emitting +emma +emolument +emotion/MS +emotional/Y +empathy +emperor/MS +emphases +emphasis +emphasize/DGS +emphatic +emphatically +emphysema +emphysematous +empire/MS +empiric +empirical/Y +empiricist/MS +emplace +employ/DGRSZ +employable +employee/MS +employer/M +employment/MS +emporium +empower/DGS +empress +emptily +empty/DGPRST +emulate/DNSX +emulator/MS +emulsify +emulsion +en +enable/DGRSZ +enact/DGS +enactment +enamel/DGS +encamp/DGS +encapsulate/DGNS +encase +encephalitis +enchain +enchant/DGRS +enchantment +enchantress +encipher/DGS +encircle/DS +enclave +enclose/DGS +enclosure/MS +encode/DGJRS +encomia +encomium +encompass/DGS +encore +encounter/DGS +encourage/DGS +encouragement/S +encouraging/Y +encroach +encrust +encrypt/DGS +encryption +encumber/DGS +encumbrance +encyclical +encyclopedia/MS +encyclopedic +end/DGJRSZ +endanger/DGS +endear/DGS +endeavor/DGS +endgame +endless/PY +endogamous +endogamy +endogenous +endorse/DGS +endorsement +endosperm +endothelial +endothermic +endow/DGS +endowment/MS +endpoint +endurable +endurably +endurance +endure/DGS +enduring/Y +enema/MS +enemy/MS +energetic +energy/S +enervate +enfant +enfeeble +enforce/DGRSZ +enforceable +enforcement +enforcible +enfranchise +engage/DGS +engagement/MS +engaging/Y +engender/DGS +engine/MS +engineer/DGMS +england/RZ +english +engrave/DGJRS +engross/DG +engulf +enhance/DGS +enhancement/MS +enigma +enigmatic +enjoin/DGS +enjoinder +enjoy/DGS +enjoyable +enjoyably +enjoyment +enlarge/DGRSZ +enlargeable +enlargement/MS +enlighten/DG +enlightenment +enlist/DS +enlistment +enliven/DGS +enmesh +enmity/S +ennoble/DGS +ennui +enormity/S +enormous/Y +enough +enqueue/DS +enquire/DRS +enquiry +enrage/DGS +enrapture +enrich/DGS +enroll/DGS +enrollee +enrollment/MS +ensconce +ensemble/MS +enshroud +ensign/MS +enslave/DGS +ensnare/DGS +enstatite +ensue/DGS +ensure/DGRSZ +entail/DGS +entangle +entendre +enter/DGS +enterprise/GS +entertain/DGRSZ +entertaining/Y +entertainment/MS +enthalpy +enthrall +enthrone +enthusiasm/S +enthusiast/MS +enthusiastic +enthusiastically +entice/DGRSZ +entire/Y +entirety/S +entitle/DGS +entity/MS +entomb +entomology +entourage +entrain +entrance/DS +entranceway +entrant +entrap +entrapping +entreat/D +entreaty +entree +entrench/DGS +entrepreneur/MS +entrepreneurial +entropy +entrust/DGS +entry/MS +entwine +enumerable +enumerate/DGNSV +enumerator/S +enunciable +enunciate/N +envelop/DGRS +envelope/DGRS +envenom +enviable +envious/PY +environ/GS +environment/MS +environmental +envisage/DS +envision/DGS +envoy/MS +envy/DS +enzymatic +enzyme +enzymology +eohippus +eosine +epaulet/MS +ephemeral +ephemerides +ephemeris +epic/MS +epicure +epicycle +epicyclic +epidemic/MS +epidemiology +epidermic +epidermis +epigenetic +epigram +epigrammatic +epigraph +epileptic +epilogue +epiphyseal +epiphysis +episcopal +episcopate +episode/MS +epistemological +epistemology +epistle/MS +epistolatory +epitaph +epitaphs +epitaxial/Y +epitaxy +epithelial +epithelium +epithet/MS +epitome +epitomize/DGS +epoch +epochs +epoxy +epsilon +equable +equal/DGSY +equality/MS +equalize/DGRSZ +equanimity +equate/DGNSX +equator/MS +equatorial +equestrian +equidistant +equilateral +equilibrate +equilibria +equilibrium/S +equine +equinoctial +equinox +equip/S +equipment +equipoise +equipotent +equipped +equipping +equitable +equitably +equitation +equity +equivalence/S +equivalent/SY +equivocal +era/M +eradicable +eradicate/DGNS +eras/DGRSZ +erasable +erase/DGRSZ +erasure +erbium +ere +erect/DGS +erection/MS +erector/MS +erg +ergo +ergodic +ermine/MS +erode +erodible +erosible +erosion +erosive +erotic +erotica +err/DGS +errancy +errand +errant +errantry +errata +erratic +erratum +erring/Y +erroneous/PY +error/MS +ersatz +erudite/N +erupt +eruption +escadrille +escalate/DGNS +escapable +escapade/MS +escape/DGS +escapee/MS +escheat +eschew/DGS +escort/DGS +escritoire +escrow +escutcheon +esophagi +esoteric +especial/Y +espionage +esplanade +espousal +espouse/DGS +esprit +espy +esquire/S +essay/DS +essence/MS +essential/SY +establish/DGS +establishment/MS +estate/MS +esteem/DGS +ester +estimable +estimate/DGNSX +estop +estoppal +estrange +estuarine +estuary +et +eta +etc +etch +eternal/Y +eternity/S +ethane +ethanol +ether/MS +ethereal/Y +ethic/S +ethical/Y +ethnic +ethnography +ethnology +ethology +ethos +ethyl +ethylene +etiology +etiquette +etude +etymology +eucalyptus +eucre +eugenic +eulogy +eunuch +eunuchs +euphemism/MS +euphemist +euphorbia +euphoria +euphoric +eureka +europe +european/S +europium +eutectic +euthanasia +evacuate/DN +evade/DGS +evaluable +evaluate/DGNSVX +evaluator/MS +evanescent +evangel +evangelic +evaporate/DGNV +evasion +evasive +eve/R +even/DGJPSY +evenhanded/PY +evening/M +evensong +event/MS +eventful/Y +eventide +eventual/Y +eventuality/S +eventuate +evergreen +everlasting/Y +evermore +every +everybody +everyday +everyman +everyone/M +everything +everywhere +evict/DGS +eviction/MS +evidence/DGS +evident/Y +evidential +evil/SY +evildoer +eviller +evince/DS +evocable +evocate +evoke/DGS +evolute/MNSX +evolution/M +evolutionary +evolve/DGS +evzone +ewe/MS +exacerbate/DGNSX +exact/DGPSY +exacting/Y +exaction/MS +exactitude +exaggerate/DGNSX +exalt/DGS +exaltation +exam/MS +examination/MS +examine/DGRSZ +example/MS +exasperate/DGNRS +excavate/DGNSX +exceed/DGS +exceeding/Y +excel/S +excelled +excellence/S +excellency +excellent/Y +excelling +excelsior +except/DGS +exception/MS +exceptional/Y +excerpt/DS +excess/SV +excessive/Y +exchange/DGS +exchangeable +exchequer/MS +excisable +excise/DGNS +excitable +excitation/MS +excitatory +excite/DGS +excited/Y +excitement +exciting/Y +exciton +exclaim/DGRSZ +exclamation/MS +exclamatory +exclude/DGS +exclusion/S +exclusionary +exclusive/PY +exclusivity +excommunicate/DGNS +excoriate +excrescent +excresence +excrete/DGNSX +excretory +excruciate +exculpatory +excursion/MS +excursus +excusable +excusably +excuse/DGS +execrable +execrate +executable +execute/DGNSVX +executional +executive/MS +executor/MS +executrix +exegesis +exegete +exemplar +exemplary +exemplify/DGNRSZ +exempt/DGS +exemption +exercisable +exercise/DGRSZ +exert/DGS +exertion/MS +exhale/DGS +exhaust/DGSV +exhaustable +exhausted/Y +exhaustible +exhaustion +exhaustive/Y +exhibit/DGS +exhibition/MS +exhibitor/MS +exhilarate +exhort +exhortation/MS +exhumation +exhume +exigent +exile/DGS +exist/DGS +existence +existent +existential/Y +existentialism +existentialist/MS +exit/DGS +exodus +exogamous +exogamy +exogenous +exonerate +exorbitant/Y +exorcise +exorcism +exorcist +exoskeleton +exothermic +exotic +exotica +expand/DGRSZ +expandable +expander/M +expanse/NSVX +expansible +expansionism +expatiate +expect/DGS +expectancy +expectant/Y +expectation/MS +expected/Y +expecting/Y +expectorant +expectorate +expedient/Y +expedite/DGNSX +expedition/M +expeditious/Y +expel/S +expellable +expelled +expelling +expend/DGS +expendable +expenditure/MS +expense/SV +expensive/Y +experience/DGS +experiential +experiment/DGRSZ +experimental/Y +experimentation/MS +expert/PSY +expertise +expiable +expiate +expiration/MS +expire/DS +explain/DGRSZ +explainable +explanation/MS +explanatory +expletive +explicable +explicate +explicit/PY +explode/DGS +exploit/DGRSZ +exploitable +exploitation/MS +exploration/MS +exploratory +explore/DGRSZ +explosion/MS +explosive/SY +exponent/MS +exponential/SY +exponentiate/DGNSX +exponentiation/M +export/DGRSZ +exportation +expose/DGRSZ +exposit +exposition/MS +expositor +expository +exposure/MS +expound/DGRS +express/DGSVY +expressibility +expressible +expressibly +expression/MS +expressive/PY +expressway +expropriate +expulsion +expunge/DGS +expurgate +exquisite/PY +extant +extemporaneous +extempore +extend/DGS +extendable +extendible +extensibility +extensible +extension/MS +extensive/Y +extensor +extent/MS +extenuate/DGN +exterior/MS +exterminate/DGNS +external/Y +extinct +extinction +extinguish/DGRS +extirpate +extol +extolled +extoller +extolling +extort +extra/S +extracellular +extract/DGS +extraction/MS +extractor/MS +extracurricular +extraditable +extralegal +extramarital +extraneous/PY +extraordinarily +extraordinary/P +extrapolate/DGNSX +extraterrestrial +extravagance +extravagant/Y +extravaganza +extrema +extremal +extreme/SY +extremist/MS +extremity/MS +extremum +extricable +extricate +extrinsic +extroversion +extrovert +extrude +extrusion +extrusive +exuberance +exuberant +exudation +exude +exult +exultant +exultation +eye/DGRSZ +eyeball +eyebright +eyebrow/MS +eyeful +eyeglass/S +eyeing +eyelash +eyelet +eyelid/MS +eyepiece/MS +eyesight +eyewitness/MS +f's +f/VX +fable/DS +fabric/MS +fabricate/DGNS +fabulous/Y +facade/DS +face/DGJS +faceplate +facet/DS +facetious +facial +facile/Y +facilitate/DGS +facility/MS +facsimile/MS +fact/MS +faction/MS +factious +facto +factor/DGS +factorial +factorization/MS +factory/MS +factual/Y +faculty/MS +fad/DGRZ +fade/DGRSZ +fadeout +faery +fag/S +fail/DGJS +failsoft +failure/MS +fain +faint/DGPRSTY +fair/GPRSTY +fairgoer +fairway +fairy/MS +fairyland +faith +faithful/PY +faithless/PY +faiths +fake/DGRS +falcon/RS +falconry +fall/GNS +fallacious +fallacy/MS +fallibility +fallible +falloff +fallout +fallow +false/PY +falsehood/MS +falsify/DGNS +falsity +falter/DS +fame/DS +familial +familiar/PY +familiarity/S +familiarization +familiarize/DGS +familism +family/MS +famine/MS +famish +famous/Y +fan/MS +fanatic/MS +fancier/M +fanciful/Y +fancily +fancy/DGPRSTZ +fanfare +fanfold +fang/MS +fangled +fanned +fanning +fanout +fantasia +fantasist +fantastic +fantasy/MS +fantod +far/DGH +farad +faraway +farce/MS +farcical +fare/DGS +farewell/S +farfetched +farina +farm/DGRSZ +farmhouse/MS +farmland +farmyard/MS +faro +farsighted +farth/GRT +fascicle +fasciculate +fascinate/DGNS +fascism +fascist +fashion/DGS +fashionable +fashionably +fast/DGNPRSTX +fasten/DGJRZ +fastidious +fat/DPS +fatal/SY +fatality/MS +fate/DS +fateful +father/DMSY +fatherland +fathom/DGS +fatigue/DGS +fatten/DGRSZ +fatter +fattest +fatty +fatuous +faucet +fault/DGS +faultless/Y +faulty +faun +fauna +favor/DGRS +favorable +favorably +favorite/S +fawn/DGS +fay +faze +fealty +fear/DGS +fearful/Y +fearless/PY +fearsome +feasibility +feasible +feast/DGS +feat/MS +feather/DGRSZ +featherbed +featherbedding +featherbrain +feathertop +featherweight +feathery +feature/DGS +febrile +february/MS +fecund +fed +federal/SY +federate/N +fee/DS +feeble/PRT +feebly +feed/DGJRSZ +feedback +feel/GJRSZ +feeling/Y +feet +feign/DG +feint +feldspar +felicitous +felicity/S +feline +fell/DG +fellow/MS +fellowship/MS +felon +felonious +felony +felsite +felt/S +female/MS +feminine +femininity +feminism +feminist +femur/MS +fen/S +fence/DGRSZ +fencepost +fend +fennel +fenugreek +ferment/DGS +fermentation/MS +fermion +fermium +fern/MS +fernery +ferocious/PY +ferocity +ferret +ferric +ferris +ferrite +ferroelectric +ferromagnet +ferromagnetic +ferromagnetism +ferrous +ferruginous +ferrule +ferry/DS +fertile/Y +fertility +fertilization +fertilize/DGRSZ +fervent/Y +fervor/MS +fescue +fest/V +festival/MS +festive/Y +festivity/S +fetal +fetch/DGS +fetching/Y +fete +fetid +fetish +fetter/DS +fettle +fetus +feud/MS +feudal +feudalism +feudatory +fever/DS +feverish/Y +few/PRT +fiance +fiancee +fiasco +fiat +fib/RZ +fibbing +fiber/M +fiberboard +fibrin +fibrosis +fibrosity/S +fibrous/Y +fiche +fickle/P +fiction/MS +fictional/Y +fictitious/Y +fictive +fiddle/GRS +fiddlestick +fide +fidelity +fidget +fiducial +fief +fiefdom +field/DGRSZ +fieldstone +fieldwork +fiend +fiendish +fierce/PRTY +fiery +fiesta +fife +fifo +fifteen/HS +fifth +fiftieth +fifty/S +fig/MS +figaro +fight/GRSZ +figural +figurate/V +figurative/Y +figure/DGJS +figurine +filament/MS +filamentary +filbert +filch +file/DGJMRS +filename/MS +filet +filial +filibuster +filigree +fill/DGJRSZ +fillable +fillet +fillip +filly +film/DGS +filmdom +filmmake +filmstrip +filmy +filter/DGMS +filth +filthy/PRT +filtrate +fin/DGMRST +final/SY +finale +finality +finalization +finalize/DGS +finance/DGS +financial/Y +financier/MS +finch +find/GJRSZ +fine/DGPRSTY +finesse/DG +finger/DGJS +fingernail +fingerprint +fingertip +finial +finicky +finish/DGRSZ +finite/PY +fink +finny +fir/DGJRZ +fire/DGJRSZ +firearm/MS +fireboat +firebreak +firebug +firecracker +firefly/MS +firehouse +firelight +fireman +firemen +fireplace/MS +firepower +fireproof +fireside +firewall +firewood +firework/S +firm/DGPRSTY +firmament +firmware +first/SY +firsthand +fiscal/Y +fish/DGRSZ +fisherman +fishermen +fishery +fishmonger +fishpond +fishy +fissile +fission +fissure/D +fist/DS +fisticuff +fit/PSY +fitful/Y +fitted +fitter/MS +fitting/SY +five/S +fivefold +fix/DGJRSZ +fixate/DGNSX +fixed/PY +fixture/MS +fizzle +fjord +flabbergast +flack +flag/MS +flagellate +flageolet +flagged +flagging +flagpole +flagrant/Y +flagstone +flail +flair +flak/DG +flake/DGS +flaky +flam/DGRZ +flamboyant +flame/DGRSZ +flamingo +flammable +flange +flank/DGRS +flannel/MS +flap/MS +flapping +flare/DGS +flash/DGRSZ +flashback +flashlight/MS +flashy +flask +flat/PSY +flatbed +flathead +flatiron +flatland +flatten/DG +flatter/DGR +flattery +flattest +flatulent +flatus +flatworm +flaunt/DGS +flautist +flavor/DGJS +flaw/DS +flawless/Y +flax/N +flaxseed +flea/MS +fleabane +fleawort +fleck +fled +fledge/D +fledgling/MS +flee/S +fleece/MS +fleecy +fleeing +fleet/GPSTY +flemish +flesh/DGSY +fleshy +fletch +flew +flex +flexibility/S +flexible +flexibly +flexural +flexure +flick/DGRS +flicker/G +flight/MS +flimsy +flinch/DGS +fling/MS +flint +flintlock +flinty +flip/S +flipflop +flippant +flipping +flirt/DGS +flirtation +flirtatious +flit +flitting +float/DGRS +floc +flocculate +flock/DGS +floe +flog +flogging +flood/DGS +floodgate +floodlight +floodlit +floor/DGJS +floorboard +flop/MS +floppily +flopping +floppy +flora +floral +florican +florid +florida +florin +florist +floss/DGS +flotation +flotilla +flounce +flounder/DGS +flour/D +flourish/DGS +floury +flout +flow/DGRSZ +flowchart/GS +flower/DG +flowerpot +flowery/P +flown +flu +flub +flubbing +fluctuate/GNSX +flue +fluency +fluent/Y +fluff +fluffy/RT +fluid/SY +fluidity +fluke +flung +fluoresce +fluorescein +fluorescent +fluoridate +fluoride +fluorine +fluorite +fluorocarbon +fluorspar +flurry/D +flush/DGS +fluster +flute/DG +flutter/DGS +flux +fly/GRSZ +flyable +flycatcher +flyer/MS +flyway +foal +foam/DGS +foamflower +foamy +fob +fobbing +focal/Y +foci +focus/DGS +focussed +fodder +foe/MS +fog/MS +fogged +foggily +fogging +foggy/RT +fogy +foible +foil/DGS +foist +fold/DGRSZ +foldout +foliage +foliate +folio +folk/MS +folklore +folksong +folksy +follicle +follicular +follow/DGJRSZ +followeth +folly/S +fond/PRY +fondle/DGS +font/MS +food/MS +foodstuff/MS +fool/DGS +foolhardy +foolish/PY +foolproof +foot/DGRZ +footage +football/MS +footbridge +footfall +foothill +foothold +footman +footmen +footnote/MS +footpad +footpath +footprint/MS +footstep/S +footstool +footwear +footwork +fop +foppish +for/HT +forage/DGS +foray/MS +forbade +forbear/MS +forbearance +forbid/S +forbidden +forbidding +forbore +forborne +force/DGMRS +forceful/PY +forcible +forcibly +ford/S +fore/T +forearm/MS +foreboding +forecast/DGRSZ +forecastle +forefather/MS +forefinger/MS +forego/G +foregoes +foregone +foreground +forehead/MS +foreign/RSZ +foreman +foremost +forenoon +forensic +foresee/S +foreseeable +foreseen +foresight/D +forest/DRSZ +forestall/DGS +forestallment +forestry +foretell/GS +foretold +forever +forewarn/DGJS +forfeit/D +forfeiture +forfend +forgave +forge/DGRSV +forgery/MS +forget/S +forgetful/P +forgettable +forgettably +forgetting +forgivable +forgivably +forgive/GPS +forgiven +forgiving/Y +forgot +forgotten +fork/DGS +forklift +forlorn/Y +form/DGRS +formal/Y +formaldehyde +formalism/MS +formality/S +formalization/MS +formalize/DGS +formant/S +format/SV +formate/NVX +formation/M +formative/Y +formatted +formatter/MS +formatting +former/Y +formic +formidable +formula/MS +formulae +formulaic +formulate/DGNSX +formulator/MS +fornication +forsake/GS +forsaken +forsook +forswear +fort/MS +forte +forthcome/G +forthright +forthwith +fortieth +fortify/DGNSX +fortin +fortiori +fortitude +fortnight/Y +fortran +fortress/MS +fortuitous/Y +fortunate/Y +fortune/MS +forty/RS +forum/MS +forward/DGPRS +fossil +fossiliferous +foster/DGS +fosterite +fought +foul/DGPSTY +foulmouth +found/DGRSZ +foundation/MS +founder/D +foundling +foundry/MS +fount/MS +fountain/MS +fountainhead +four/HS +fourfold +fourier +fourscore +foursome +foursquare +fourteen/HS +fovea +fowl/RS +fox/MS +foxglove +foxhole +foxhound +foxtail +foxy +foyer +fraction/MS +fractional/Y +fractionate +fractious +fracture/DGS +fragile +fragment/DGS +fragmentary +fragmentation +fragrance/MS +fragrant/Y +frail/T +frailty +frambesia +frame/DGRS +framework/MS +franc/S +franca +france/MS +franchise/MS +francium +franco +frangipani +frank/DGPRSTY +frankfurter +franklin +frantic +frantically +fraternal/Y +fraternity/MS +fraud/MS +fraudulent +fraught +fray/DGS +frazzle +freak/MS +freakish +freckle/DS +free/DPRSTY +freeboot +freedmen +freedom/MS +freehand +freehold +freeing/S +freeman +freemen +freestone +freethink +freeway +freewheel +freeze/GRSZ +freight/DGRSZ +french +frenetic +frenzy/D +freon +frequency/S +frequent/DGRSYZ +fresco +frescoes +fresh/NPRTXY +freshen/DGRZ +freshman +freshmen +freshwater +fret +fretful/PY +fretting +friable +friar/MS +fricative/S +friction/MS +frictional +frictionless +friday/MS +friend/MSY +friendless +friendly/PRT +friendship/MS +frieze/MS +frigate/MS +fright/NX +frighten/DG +frightening/Y +frightful/PY +frigid +frill/MS +frilly +fringe/D +frisk/DGS +frisky +fritillary +fritter +frivolity +frivolous/Y +frizzle +fro/H +frock/MS +frog/MS +frolic/S +from +front/DGS +frontage +frontal +frontier/MS +frontiersman +frontiersmen +frost/DGS +frostbite +frostbitten +frosty +froth/G +frothy +frown/DGS +frowzy +froze +frozen/Y +frugal/Y +fruit/MS +fruitful/PY +fruition +fruitless/Y +frustrate/DGNRSX +frustum +fry/DNS +fudge +fuel/DGS +fugal +fugitive/MS +fugue +fulcrum +fulfill/DGS +fulfillment/S +full/PRT +fullback +fully +fulminate +fulsome +fum/DG +fumble/DG +fume/DGS +fumigant +fumigate +fun +function/DGMS +functional/SY +functionality/S +functionary +functor/MS +fund/DGRSZ +fundamental/SY +funeral/MS +funereal +fungal +fungi +fungible +fungicide +fungoid +fungus +funk +funnel/DGS +funnily +funny/PRT +fur/MS +furbish +furious/RY +furl +furlong +furlough +furnace/MS +furnish/DGJS +furniture +furring +furrow/DS +furry/R +further/DGS +furthermore +furthermost +furthest +furtive/PY +fury/MS +furze +fuse/DGNS +fuselage +fusible +fusiform +fusillade +fuss/G +fussy +fusty +futile +futility +future/MS +fuzz +fuzzy/PR +g's +g/V +gab +gabardine +gabbing +gabble +gabbro +gable/DRS +gad +gadding +gadfly +gadget/MS +gadgetry +gadolinium +gadwall +gaff +gaffe +gag/GS +gage/G +gagged +gagging +gaggle +gagwriter +gaiety/S +gaillardia +gaily +gain/DGRSZ +gainful +gait/DRZ +gal +gala +galactic +galaxy/MS +gale +galena +galenite +gall/DGS +gallant/SY +gallantry +gallberry +gallery/DS +galley/MS +gallinule +gallium +gallivant +gallon/MS +gallonage +gallop/DGRS +gallows +gallstone +gallus +galvanic +galvanism +galvanometer +gam/DG +gambit +gamble/DGRSZ +gambol +game/DGPSY +gamecock +gamin +gamma +gamut +gander +gang/MS +gangland +gangling +ganglion +gangplank +gangrene +gangster/MS +gangway +gannet +gantlet +gantry +gap/DGMS +gape/DGS +gar +garage/DS +garb/D +garbage/MS +garble/DGS +garden/DGRSZ +gardenia +gargantuan +gargle/DGS +garish +garland/D +garlic +garment/MS +garner/D +garnet +garnish +garrison/D +garrulous +garter/MS +gas/MS +gaseous/Y +gash/MS +gasify +gasket +gaslight +gasoline +gasp/DGS +gassed +gasser +gassing/S +gassy +gastric +gastrointestinal +gastronome +gastronomy +gate/DGS +gateway/MS +gather/DGJRSZ +gator +gauche +gaucherie +gaudy/P +gauge/DS +gaugeable +gauleiter +gaunt/P +gauntlet +gaur +gauss +gauze +gave +gavel +gavotte +gawk +gawky +gay/PRTY +gayety +gaze/DGRSZ +gazelle +gazette +gcd +gear/DGS +gecko +gee +geese +geisha +gel/MS +gelable +gelatin +gelatine +gelatinous +geld +gelled +gelling +gem/MS +gemlike +gender/MS +gene/MS +genealogy +genera +general/SY +generalist/MS +generality/S +generalization/MS +generalize/DGRSZ +generate/DGNRSVX +generator/MS +generic +generically +generosity/MS +generous/PY +genesis +genetic +genetically +genial/Y +genie +genii +genius/MS +genotype +genre/MS +gent/Y +genteel +gentian +gentile +gentility +gentle/PRT +gentleman/Y +gentlemen +gentlewoman +gentry +genuine/PY +genus +geocentric +geochemical +geochemistry +geochronology +geodesic +geodesy +geodetic +geoduck +geographer +geographic +geographical/Y +geography +geological +geologist/MS +geology +geometer +geometric +geometrician +geometry/S +geophysical +geophysics +geopolitic +geranium +gerbil +geriatric +germ/MS +german/MS +germane +germanium +germany +germicidal +germicide +germinal +germinate/DGNS +gerund/V +gerundial +gestalt +gesticulate +gesture/DGS +get/S +getaway +getter/MS +getting +geyser +ghastly +gherkin +ghetto +ghost/DSY +ghostlike +ghoul +ghoulish +giant/MS +giantess +gibberish +gibbet +gibbon +gibbous +gibby +gibe +giblet +giddap +giddy/P +gift/DS +gig +gigacycle +gigahertz +gigantic +gigavolt +gigawatt +gigging +giggle/DGS +gila +gilbert +gild/DGS +gill/MS +gilt +gimbal +gimmick/MS +gimpy +gin/MS +ginger/Y +gingerbread +gingham/S +gingko +ginkgo +ginmill +ginning +ginseng +gipsy/MS +giraffe/MS +gird/RZ +girder/M +girdle +girl/MS +girlie +girlish +girt +girth +gist +give/GHRSZ +giveaway +given +glacial +glaciate +glacier/MS +glacis +glad/PY +gladden +gladder +gladdest +gladdy +glade +gladiator +gladiolus +glamor +glamorous +glamour +glance/DGS +gland/MS +glandular +glare/DGS +glaring/Y +glass/DS +glassine +glassware +glasswort +glassy +glaucoma +glaucous +glaze/DGRS +gleam/DGS +glean/DGJRS +glee/S +gleeful/Y +glen/MS +glib +glide/DRSZ +glimmer/DGS +glimpse/DS +glint/DGS +glissade +glisten/DGS +glitch +glitter/DGS +gloat +glob +global/Y +globe/MS +globular +globularity +globule +globulin +glom +glomerular +gloom +gloomily +gloomy +glorify/DNS +glorious/Y +glory/GS +gloss/DGS +glossary/MS +glossolalia +glossy +glottal +glottis +glove/DGRSZ +glow/DGRSZ +glowing/Y +glue/DGS +gluey +glum +glut +glutamic +glutinous +glutting +glutton +glyceride +glycerin +glycerinate +glycerine +glycerol +glycol +glyph +gnarl +gnash +gnat/MS +gnaw/DGS +gneiss +gnome +gnomon +gnomonic +gnostic +gnu +go/GJR +goad/D +goal/MS +goat/MS +goatee/MS +gob +gobble/DRSZ +gobbledygook +goblet/MS +goblin/MS +god/MSY +goddess/MS +godfather +godhead +godkin +godlike +godmother/MS +godparent +godsend +godson +godwit +goes +gog +goggle +gogo +gold/GNS +golden/PY +goldeneye +goldenrod +goldenseal +goldfinch +goldfish +goldsmith +golf/GRZ +golly +gondola +gone/R +gong/MS +goober +good/PSY +goodwill +goody/MS +goof +goofy +goose +gooseberry +gopher +gore +gorge/GS +gorgeous/Y +gorgon +gorilla/MS +gorse +gory +gosh +goshawk +gosling +gospel/SZ +gossamer +gossip/DGS +got +gothic +goto +gotten +gouge/DGS +gourd +gourmet +gout +govern/DGS +governance +governess +government/MS +governmental/Y +governor/MS +gown/DS +grab/S +grabbed +grabber/MS +grabbing/S +grace/DGS +graceful/PY +gracious/PY +grackle +grad/DGJRZ +gradate/NX +gradation/M +grade/DGJRSZ +gradient/MS +gradual/Y +graduate/DGNSX +graft/DGRS +graham/MS +grail +grain/DGS +gram/S +grammar/MS +grammarian +grammatic +grammatical/Y +granary/MS +grand/PRSTY +grandchild +grandchildren +granddaughter +grandeur +grandfather/MS +grandiloquent +grandiose +grandma +grandmother/MS +grandnephew +grandniece +grandpa +grandparent +grandson/MS +grandstand +grange +granite +granitic +granny +granola +grant/DGRS +grantee +grantor +granular +granularity +granulate/DGS +granule +grape/MS +grapefruit +grapevine +graph/DGM +grapheme +graphic/S +graphical/Y +graphite +graphs +grapple/DG +grasp/DGS +graspable +grasping/Y +grass/DSZ +grassland +grassy/RT +grata +grate/DGJRS +grateful/PY +gratify/DGN +gratis +gratitude +gratuitous/PY +gratuity/MS +grave/PRSTY +gravel/Y +graven +gravestone +graveyard +gravid +gravitate/N +gravitational +gravity +gravy +gray/DGPRT +graybeard +graywacke +graze/DGR +grease/DS +greasy +great/PRTY +greatcoat +grebe +greed +greedily +greedy/P +greek/MS +green/GPRSTY +greenery +greengrocer +greenhouse/MS +greenish +greensward +greenware +greenwood +greet/DGJRS +gregarious +grenade/MS +grew +grey/GT +greyhound +greylag +grid/MS +griddle +gridiron +grief/MS +grievance/MS +grieve/DGRSZ +grieving/Y +grievous/Y +griffin +grill/DGS +grille/DG +grillwork +grim/DPY +grimace +grime/D +grin/S +grind/GJRSZ +grindstone/MS +grinning +grip/DGS +gripe/DGS +grippe/DG +gripping/Y +grisly +grist +gristmill +grit/MS +gritty +grizzle +grizzly +groan/DGRSZ +groat +grocer/MS +grocery/S +groggy +groin +grommet +groom/DGS +groove/DS +grope/DGS +grosbeak +gross/DGPRSTY +grotesque/SY +grotto/MS +ground/DGRSZ +groundsel +groundskeep +groundwork +group/DGJS +grouse +grout +grove/RSZ +grovel/DGS +grow/GHRSZ +growl/DGS +grown +grownup/MS +growths +grub/MS +grubbing +grubby +grudge/MS +gruesome +gruff/Y +grumble/DGS +grunt/DGS +gryphon +guanidine +guano +guarantee/DRSZ +guaranteeing +guaranty +guard/DGS +guarded/Y +guardhouse +guardian/MS +guardianship +gubernatorial +guerdon +guernsey +guerrilla/MS +guess/DGS +guesswork +guest/MS +guffaw +guidance +guide/DGS +guidebook/MS +guideline/MS +guidepost +guignol +guild/R +guildhall +guile +guillemot +guilt +guiltily +guiltless/Y +guilty/PRT +guinea +guise/MS +guitar/MS +gulch/MS +gules +gulf/MS +gull/DGS +gullet +gullible +gully/MS +gulp/DS +gum/MS +gumbo +gumming +gummy +gumption +gumshoe +gun/MS +gunfight +gunfire +gunflint +gunk +gunky +gunman +gunmen +gunned +gunner/MS +gunnery +gunning +gunny +gunplay +gunpowder +gunshot +gunsling +gurgle +guru +gush/DGRS +gusset +gust/MS +gusto +gusty +gut/S +gutsy +gutter/DS +gutting +guttural +guy/DGRSZ +guzzle +gym +gymnasium/MS +gymnast/MS +gymnastic/S +gymnosperm +gyp +gypping +gypsite +gypsum +gypsy/MS +gyrate +gyrfalcon +gyro +gyrocompass +gyroscope/MS +h's +h/VXZ +ha/H +habeas +haberdashery +habit/MS +habitant +habitat/MS +habitation/MS +habitual/PY +habituate +hacienda +hack/DGRSZ +hackberry +hackle +hackmatack +hackney/D +hacksaw +had +haddock +hadn't +hadron +hafnium +hag +haggard/Y +haggle +haiku +hail/DGS +hailstone +hailstorm +hair/MS +haircut/MS +hairdo +hairdryer/MS +hairless +hairpin +hairy/PR +halcyon +hale/R +half +halfback +halfhearted +halfway +halibut +halide +halite +hall/MS +hallelujah +hallmark/MS +hallow/D +hallucinate +hallway/MS +halma +halo +halocarbon +halogen +halt/DGRSZ +halting/Y +halvah +halve/DGSZ +ham/MS +hamburger/MS +hamlet/MS +hammer/DGS +hammerhead +hamming +hammock/MS +hamper/DS +hamster +hand/DGS +handbag/MS +handbook/MS +handclasp +handcuff/DGS +handful/S +handgun +handhold +handicap/MS +handicapped +handicapper +handicapping +handicraft +handicraftsman +handicraftsmen +handily +handiwork +handkerchief/MS +handle/DGRSZ +handleable +handlebar +handline +handmade +handmaiden +handout +handset +handshake +handsome/PRTY +handspike +handstand +handwrite/G +handwritten +handy/PRT +handyman +handymen +hang/DGRSZ +hangable +hangar/MS +hangman +hangmen +hangout +hangover/MS +hank +hansom +hap/Y +haphazard/PY +hapless/PY +happen/DGJS +happenstance +happily +happy/PRT +harangue +harass/DGS +harassment +harbinger +harbor/DGS +hard/NPRTY +hardbake +hardboard +hardboiled +hardhat +hardscrabble +hardship/MS +hardtack +hardtop +hardware +hardwired +hardwood +hardworking +hardy/P +hare/MS +harelip +harem +hark/N +harlot/MS +harm/DGS +harmful/PY +harmless/PY +harmonic +harmonious/PY +harmonize +harmony/S +harness/DG +harp/GRZ +harpsichord +harrow/DGS +harry/DR +harsh/NPRY +hart +harvest/DGRS +harvestman +has +hash/DGRS +hashish +hasn't +hasp +hassle +hast/JNX +haste/J +hasten/DG +hastily +hasty/P +hat/DGMRS +hatch/DG +hatchet/MS +hatchway +hate/DGRS +hateful/PY +hatred +haughtily +haughty/P +haul/DGRS +haulage +haunch/MS +haunt/DGRS +have/GS +haven't +haven/MS +havoc +haw +hawk/DRSZ +hawthorn +hay/GS +hayfield +haystack +hayward +hazard/MS +hazardous +haze/MS +hazel +hazelnut +hazy/P +he'd +he'll +he/MVZ +head/DGJRSZ +headache/MS +headboard +headdress +headgear +heading/M +headland/MS +headlight +headline/DGS +headlong +headmaster +headphone +headquarter/S +headroom +headset +headsman +headsmen +headstand +headstone +headwall +headwater +headway +heady +heal/DGHRSZ +healthful/PY +healthily +healthy/PRT +heap/DGS +hear/GHJRSZ +heard +hearken +hearsay +hearse +heart/NS +heartbeat +heartbreak +heartfelt +heartily +heartless +hearty/PT +heat/DGRSZ +heatable +heated/Y +heath/NR +heathenish +heave/DGRSZ +heaven/SY +heavenward +heavily +heavy/PRT +heavyweight +hebephrenic +hecatomb +heck +heckle +hectic +hector +hedge/DS +hedgehog/MS +hedonism +hedonist +heed/DS +heedless/PY +heel/DGSZ +heft +hefty +hegemony +heifer +heigh +height/NSX +heighten/DG +heinous/Y +heir/MS +heiress/MS +held +helical +helicopter +heliocentric +heliotrope +helium +helix +hell/MS +hellbender +hellebore +hellfire +hellgrammite +hellish +hello +helm +helmet/MS +helmsman +helmsmen +help/DGRSZ +helpful/PY +helpless/PY +helpmate +hem/MS +hematite +hemisphere/MS +hemispheric +hemlock/MS +hemming +hemoglobin +hemolytic +hemorrhage +hemorrhoid +hemosiderin +hemostat/S +hemp/N +hen/MS +henbane +hence +henceforth +henchman +henchmen +henequen +henpeck +henry +hepatica +hepatitis +heptane +her/S +herald/DGS +herb/MS +herbivore +herbivorous +herd/DGRS +herdsman +here/MS +hereabout/S +hereafter +hereby +hereditary +heredity +herein +hereinabove +hereinafter +hereinbelow +hereof +heresy +heretic/MS +hereto +heretofore +hereunder +hereunto +herewith +heritable +heritage/S +hermeneutic +hermetic +hermit/MS +hermitian +hero +heroes +heroic/S +heroically +heroin +heroine/MS +heroism +heron/MS +herpes +herpetology +herring/MS +herringbone +herself +hertz +hesitant/Y +hesitate/DGNRSX +hesitating/Y +heterodyne +heterogamous +heterogeneity +heterogeneous/PY +heterosexual +heterostructure +heterozygous +heuristic/MS +heuristically +hew/DRS +hewn +hex +hexachloride +hexadecimal +hexafluoride +hexagon +hexagonal/Y +hexameter +hexane +hey +heyday +hi +hibachi +hibernate +hick +hickory +hid/G +hidalgo +hidden +hide/GS +hideaway +hideous/PY +hideout/MS +hierarchal +hierarchic +hierarchical/Y +hierarchy/MS +hieratic +hieroglyphic +hifalutin +high/PRTY +highball +highboy +highfalutin +highhanded +highland/RS +highlight/DGS +highness/MS +highroad +hightail +highway/MS +highwayman +highwaymen +hijack +hike/DGRS +hilarious/Y +hilarity +hill/MS +hillbilly +hillman +hillmen +hillock +hillside +hilltop/MS +hilly +hilt/MS +hilum +him +himself +hind/RZ +hinder/DG +hindmost +hindrance/S +hindsight +hinge/DS +hint/DGS +hinterland +hip/MS +hipping +hippo +hippodrome +hippopotamus +hippy +hipster +hire/DGJRSZ +hireling +his +hiss/DGS +histochemic +histochemistry +histogram/MS +histology +historian/MS +historic +historical/Y +historiography +history/MS +histrionic +hit/MS +hitch/DG +hitchhike/DGRSZ +hither +hitherto +hitter/MS +hitting +ho/Y +hoagie +hoagy +hoar +hoard/GR +hoarfrost +hoarse/PY +hoary/P +hob +hobble/DGS +hobby/MS +hobbyhorse +hobbyist/MS +hobo +hoc +hock +hockey +hodge +hodgepodge +hoe/MS +hog/MS +hogan +hogging +hoi +hoist/DGS +hold/GJNRSZ +holdover +holdup +hole/DS +holeable +holiday/MS +holistic +holland +holler +hollow/DGPSY +hollowware +holly +hollyhock +holmium +holocaust +hologram/MS +holography +holster +holt +holy/PS +holystone +homage +home/DGRSYZ +homebound +homebuilder +homebuilding +homecoming +homeland +homeless +homemade +homemake/RZ +homemaker/M +homeomorph +homeomorphic +homeomorphism/MS +homeopath +homeowner +homesick/P +homespun +homestead/RSZ +homeward/S +homework +homicidal +homicide +homily +homo +homogenate +homogeneity/MS +homogeneous/PY +homologous +homologue +homology +homomorphic +homomorphism/MS +homonym +homosexual +homotopy +homozygous +hondo +hone/DGRST +honest/Y +honesty +honey +honeybee +honeycomb/D +honeydew +honeymoon/DGRSZ +honeysuckle +hong +honk +honor/DGRS +honorable/P +honorably +honorarium +honorary/S +honoree +hooch +hood/DS +hoodlum +hoodwink/DGS +hoof/MS +hoofmark +hook/DGRSZ +hookup +hookworm +hooligan +hoop/RS +hoopla +hoosegow +hoot/DGRS +hooves +hop/DGS +hope/DGS +hopeful/PSY +hopeless/PY +hopper/MS +hopping +hopple +hopscotch +horde/MS +horehound +horizon/MS +horizontal/Y +hormone/MS +horn/DS +hornbeam +hornblende +hornet/MS +hornmouth +horntail +hornwort +horny +horology +horoscope +horrendous/Y +horrible/P +horribly +horrid/Y +horrify/DGS +horror/MS +horse/SY +horseback +horsedom +horseflesh +horsefly +horsehair +horselike +horseman +horsemen +horseplay +horsepower +horseshoe/R +horsetail +horsewoman +horsewomen +horticulture +hose/MS +hosiery +hospice +hospitable +hospitably +hospital/MS +hospitality +hospitalize/DGS +host/DGS +hostage/MS +hostelry +hostess/MS +hostile/Y +hostility/S +hostler +hot/PY +hotbed +hotbox +hotel/MS +hotelman +hothead +hothouse +hotrod +hotter +hottest +hough +hound/DGS +hour/SY +hourglass +house/DGS +houseboat +housebreak +housebroken +housefly/MS +household/RSZ +housekeep/GRZ +housekeeper/M +housetop/MS +housewife/Y +housewives +housework +hove/RZ +hovel/MS +hover/DG +how +howdy +however +howl/DGRS +howsoever +howsomever +hoy +hoyden +hoydenish +hub/MS +hubbub +hubby +hubris +huck +huckleberry +huckster +huddle/DG +hue/DMS +huff +hug +huge/PY +hugging +huh +hulk +hull/MS +hum/S +human/PSY +humane/PY +humanitarian +humanity/MS +humble/DGPRT +humbly +humerus +humid/Y +humidify/DGNRSZ +humidistat +humidity +humiliate/DGNSX +humility +hummed +humming +hummingbird +hummock +humor/DGRSZ +humorous/PY +hump/D +humpback +humpty +humus +hunch/DS +hundred/HS +hundredfold +hung/RZ +hunger/DG +hungrily +hungry/RT +hunk/MS +hunt/DGRSZ +huntsman +hurdle +hurl/DGRZ +hurley +hurrah +hurray +hurricane/MS +hurried/Y +hurry/DGS +hurt/GS +hurtle +hurty +husband/MS +husbandman +husbandmen +husbandry +hush/DGS +husk/DGRS +husky/P +hustle/DGRS +hut/MS +hutch +huzzah +hyacinth +hyaline +hybrid +hydra +hydrangea +hydrant +hydrate +hydraulic +hydride +hydro +hydrocarbon +hydrochemistry +hydrochloric +hydrochloride +hydrodynamic/S +hydroelectric +hydrofluoric +hydrogen/MS +hydrogenate +hydrology +hydrolysis +hydrometer +hydrophilic +hydrophobia +hydrophobic +hydrosphere +hydrostatic +hydrothermal +hydrous +hydroxide +hydroxy +hydroxyl +hydroxylate +hyena +hygiene +hygrometer +hygroscopic +hying +hymen +hymn/MS +hymnal +hyperbola +hyperbolic +hyperboloid +hyperboloidal +hypertensive +hyphen/MS +hyphenate +hypnosis +hypnotic +hypoactive +hypocrisy/S +hypocrite/MS +hypocritic +hypocycloid +hypodermic/S +hypophyseal +hypotenuse +hypothalamic +hypothalamus +hypotheses +hypothesis +hypothesize/DGRS +hypothetic +hypothetical/Y +hypothyroid +hysterectomy +hysteresis +hysteria +hysteric +hysterical/Y +hysteron +i'd +i'll +i'm +i's +i've +iambic +ibex +ibid +ibis +ice/DGJS +iceberg/MS +icebox +iceland +ichneumon +icicle +icon +iconoclasm +iconoclast +icosahedra +icosahedral +icosahedron +icy/P +idea/MS +ideal/SY +idealism +idealistic +idealization/MS +idealize/DGS +ideate +idempotent +identical/Y +identifiable +identifiably +identify/DGNRSXZ +identity/MS +ideological/Y +ideology +idiocy +idiom +idiomatic +idiosyncrasy/MS +idiosyncratic +idiot/MS +idiotic +idle/DGPRSTZ +idly +idol/MS +idolatry +idyll +idyllic +if +iffy +igloo +igneous +ignite/N +ignoble +ignominious +ignoramus +ignorance +ignorant/Y +ignore/DGS +ii +iii +ileum +iliac +ill/PS +illegal/Y +illegality/S +illegible +illegitimacy +illegitimate +illicit/Y +illimitable +illinois +illiteracy +illiterate +illness/MS +illogic +illogical/Y +illume +illuminate/DGNSX +illumine +illusion/MS +illusionary +illusive/Y +illusory +illustrate/DGNSVX +illustrative/Y +illustrator/MS +illustrious/P +illy +image/GS +imagery +imaginable +imaginably +imaginary +imaginate/NVX +imagination/M +imaginative/Y +imagine/DGJS +imbalance/S +imbecile +imbibe +imbroglio +imbrue +imbue +imitable +imitate/DGNSVX +immaculate/Y +immanent +immaterial/Y +immature +immaturity +immeasurable +immediacy/S +immediate/Y +immemorial +immense/Y +immerse/DNS +immigrant/MS +immigrate/DGNS +imminent/Y +immobile +immobility +immoderate +immodest +immodesty +immoral +immortal/Y +immortality +immovability +immovable +immovably +immune +immunity/MS +immunization +immunoelectrophoresis +immutable +imp/Y +impact/DGS +impaction +impactor/MS +impair/DGS +impale +impalpable +impart/DS +impartation +impartial/Y +impassable +impasse/NV +impatience +impatient/Y +impeach +impeccable +impedance/MS +impede/DGS +impediment/MS +impel +impelled +impeller +impelling +impend/G +impenetrability +impenetrable +impenetrably +imperate/V +imperative/SY +imperceivable +imperceptible +imperfect/Y +imperfection/MS +imperial +imperialism +imperialist/MS +imperil/D +imperious/Y +imperishable +impermanence +impermanent +impermeable +impermissible +impersonal/Y +impersonate/DGNSX +impertinent/Y +imperturbable +impervious/Y +impetuous/Y +impetus +impiety +impinge/DGS +impious +impish +implacable +implant/DGS +implantation +implausible +implement/DGRS +implementable +implementation/MS +implementor/MS +implicant/MS +implicate/DGNSX +implicit/PY +implore/DG +imply/DGNSX +impolite +impolitic +imponderable +import/DGRSZ +importance +important/Y +importation +importunate +importune +impose/DGS +imposition/MS +impossibility/S +impossible +impossibly +impost +impostor/MS +imposture +impotence +impotent +impound +impoverish/D +impoverishment +impracticable +impractical/Y +impracticality +imprecate +imprecise/NY +impregnable +impregnate +impresario +impress/DGRSV +impressible +impression/MS +impressionable +impressionist +impressionistic +impressive/PY +impressment +imprimatur +imprint/DGS +imprison/DGS +imprisonment/MS +improbable +impromptu +improper/Y +impropriety +improve/DGS +improvement/S +improvident +improvisate/NX +improvisation/M +improvisational +improvise/DGRSZ +imprudent +impudent/Y +impugn +impulse/NSV +impunity +impure +impurity/MS +imputation +impute/D +in +inability +inaccessible +inaccuracy/S +inaccurate +inaction +inactivate +inactive +inactivity +inadequacy/S +inadequate/PY +inadmissibility +inadmissible +inadvertent/Y +inadvisable +inalienable +inalterable +inane +inanimate/Y +inappeasable +inapplicable +inappreciable +inapproachable +inappropriate/P +inapt +inaptitude +inarticulate +inasmuch +inattention +inattentive +inaudible +inaugural +inaugurate/DGN +inauspicious +inboard +inborn +inbred +inbreed +incalculable +incandescent +incant +incantation +incapable +incapacitate/G +incapacity +incarcerate +incarnate/NX +incarnation/M +incautious +incendiary/S +incense/DS +incentive/MS +inception +inceptor +incessant/Y +incest +incestuous +inch/DGS +incidence +incident/MS +incidental/SY +incinerate +incipient +incise/V +incite/DGS +inclement +inclination/MS +incline/DGS +inclose/DGS +include/DGS +inclusion/MS +inclusive/PY +incoherent/Y +incombustible +income/GS +incommensurable +incommensurate +incommunicable +incommutable +incomparable +incomparably +incompatibility/MS +incompatible +incompatibly +incompetence +incompetent/MS +incomplete/NPY +incomprehensibility +incomprehensible +incomprehensibly +incomprehension +incompressible +incomputable +inconceivable +inconclusive +incondensable +incongruity +incongruous +inconsequential/Y +inconsiderable +inconsiderate/PY +inconsistency/MS +inconsistent/Y +inconsolable +inconspicuous +inconstant +incontestable +incontrollable +incontrovertible +inconvenience/DGS +inconvenient/Y +inconvertible +incorporable +incorporate/DGNS +incorrect/PY +incorrigible +incorruptible +increasable +increase/DGS +increasing/Y +incredible +incredibly +incredulity +incredulous/Y +increment/DGS +incremental/Y +incriminate +incubate/DGNS +incubator/MS +incubi +incubus +inculcate +inculpable +incumbent +incur/S +incurable +incurred +incurrer +incurring +incursion +indebted/P +indecent +indecipherable +indecision +indecisive +indecomposable +indeed +indefatigable +indefensible +indefinable +indefinite/PY +indelible +indelicate +indemnity +indent/DGS +indentation/MS +indenture +independence +independent/SY +indescribable +indestructible +indeterminable +indeterminacy/MS +indeterminate/Y +index/DGS +indexable +india +indian/MS +indiana +indicant +indicate/DGNSVX +indicator/MS +indices +indict +indictment/MS +indifference +indifferent/Y +indigene +indigenous/PY +indigent +indigestible +indigestion +indignant/Y +indignation +indignity/S +indigo +indirect/DGSY +indirection/S +indiscernible +indiscoverable +indiscreet +indiscretion +indiscriminate/Y +indispensability +indispensable +indispensably +indispose +indisposition +indisputable +indissoluble +indistinct +indistinguishable +indium +individual/MSY +individualism +individualistic +individuality +individualize/DGS +individuate +indivisibility +indivisible +indoctrinate/DGNS +indolent/Y +indomitable +indoor/S +indorse +indubitable +induce/DGRS +inducement/MS +inducible +induct/DGSV +inductance/S +inductee +induction/MS +inductive/Y +inductor/MS +indulge/DG +indulgence/MS +indulgent +industrial/SY +industrialism +industrialist/MS +industrialization +industrious/PY +industry/MS +indwell +indy/NX +ineducable +ineffable +ineffective/PY +ineffectual +inefficacy +inefficiency/S +inefficient/Y +inelastic +inelegant +ineligible +ineluctable +inept +inequality/S +inequitable +inequity +inequivalent +ineradicable +inert/PY +inertance +inertia +inertial +inescapable +inescapably +inessential +inestimable +inevitability/S +inevitable +inevitably +inexact +inexcusable +inexcusably +inexhaustible +inexorable +inexorably +inexpedient +inexpensive/Y +inexperience/D +inexpert +inexpiable +inexplainable +inexplicable +inexplicit +inexpressible +inextinguishable +inextricable +infallibility +infallible +infallibly +infamous/Y +infamy +infancy +infant/MS +infantile +infantry +infantryman +infantrymen +infarct +infatuate +infeasible +infect/DGSV +infection/MS +infectious/Y +infelicitous +infelicity +infer/S +inference/MS +inferential +inferior/MS +inferiority +infernal/Y +inferno/MS +inferred +inferring +infertile +infest/DGS +infestation +infidel/MS +infield +infighting +infiltrate +infima +infimum +infinite/PVY +infinitesimal +infinitive/MS +infinitude +infinitum +infinity +infirm +infirmary +infirmity +infix +inflame/D +inflammable +inflammation +inflammatory +inflatable +inflate/DGNRS +inflationary +inflect +inflexibility +inflexible +inflict/DGS +inflow +influence/DGS +influent +influential/Y +influenza +influx +inform/DGRSZ +informal/Y +informality +informant/MS +information +informational +informative/Y +infra +infract +infrared +infrastructure +infrequent/Y +infringe/DGS +infringement/MS +infuriate/DGNS +infuse/DGNSX +infusible +ingather +ingenious/PY +ingenuity +ingenuous +ingest +ingestible +ingestion +inglorious +ingot +ingrate +ingratiate +ingratitude +ingredient/MS +ingrown +inhabit/DGS +inhabitable +inhabitance +inhabitant/MS +inhabitation +inhalation +inhale/DGRS +inharmonious +inhere/S +inherent/Y +inherit/DGS +inheritable +inheritance/MS +inheritor/MS +inheritress/MS +inheritrices +inheritrix +inhibit/DGS +inhibition/MS +inhibitor/S +inhibitory +inholding +inhomogeneity/S +inhomogeneous +inhospitable +inhuman +inhumane +inimical +inimitable +iniquitous +iniquity/MS +initial/DGSY +initialization/MS +initialize/DGRSZ +initiate/DGNSVX +initiative/MS +initiator/MS +inject/DGSV +injection/MS +injudicious +injunct +injunction/MS +injure/DGS +injurious +injury/MS +injustice/MS +ink/DGJRSZ +inkling/MS +inlaid +inland +inlay +inlet/MS +inline +inmate/MS +inn/GJRS +innards +innate/Y +innermost +innocence +innocent/SY +innocuous/PY +innovate/NVX +innovation/M +innuendo +innumerability +innumerable +innumerably +inoculate +inoperable +inoperative +inopportune +inordinate/Y +inorganic +input/MS +inquest +inquire/DGRSZ +inquiry/MS +inquisition/MS +inquisitive/PY +inquisitor +inroad/S +insane/Y +insanity +insatiable +inscribe/DGS +inscription/MS +inscrutable +insect/MS +insecticide +insecure/Y +inseminate +insensible +insensitive/Y +insensitivity +inseparable +insert/DGS +insertion/MS +inset +inshore +inside/RSZ +insidious/PY +insight/MS +insightful +insignia +insignificance +insignificant +insincere +insinuate/DGNSX +insipid +insist/DGS +insistence +insistent/Y +insofar +insolence +insolent/Y +insoluble +insolvable +insolvent +insomnia +insomniac +insouciant +inspect/DGS +inspection/MS +inspector/MS +inspiration/MS +inspire/DGRS +instability/S +instable +install/DGRSZ +installation/MS +installment/MS +instalment +instance/S +instant/RSY +instantaneous/Y +instantiate/DGNSX +instantiation/M +instead +instep +instigate/DGS +instigator/MS +instill +instillation +instinct/MSV +instinctive/Y +instinctual +institute/DGNRSXZ +institutional/Y +institutionalize/DGS +instruct/DGSV +instruction/MS +instructional +instructive/Y +instructor/MS +instrument/DGS +instrumental/SY +instrumentalist/MS +instrumentation +insubordinate +insubstantial +insufferable +insufficient/Y +insular +insulate/DGNS +insulator/MS +insulin +insult/DGS +insuperable +insupportable +insuppressible +insurance +insure/DGRSZ +insurgent/MS +insurmountable +insurrect +insurrection/MS +intact +intake +intangible/MS +integer/MS +integrable +integral/MS +integrand +integrate/DGNSVX +integrity +integument +intellect/MS +intellectual/SY +intelligence +intelligent/Y +intelligentsia +intelligibility +intelligible +intelligibly +intemperance +intemperate +intend/DGS +intendant +intense/VY +intensify/DGNRSZ +intensity/S +intensive/Y +intent/PSY +intention/DS +intentional/Y +inter/T +interact/DGSV +interaction/MS +interactive/Y +interactivity +intercalate +intercept/DGS +interception +interceptor +interchange/DGJS +interchangeability +interchangeable +interchangeably +intercity +intercom +intercommunicate/DGNS +interconnect/DGS +interconnection/MS +intercourse +interdependence +interdependency/S +interdependent +interdict +interdisciplinary +interest/DGS +interesting/Y +interface/DGRS +interfere/DGS +interference/S +interfering/Y +interferometer +interferometric +interferometry +interim +interior/MS +interject +interlace/DGS +interleave/DGS +interlink/DS +interlude +intermediary +intermediate/MS +interminable +intermingle/DGS +intermit +intermittent/Y +intermodule +intern/DS +internal/SY +internalize/DGS +international/Y +internationality +internescine +interpersonal +interplay +interpolate/DGNSX +interpolatory +interpose/DGS +interpret/DGRSVZ +interpretable +interpretation/MS +interpretive/Y +interprocess +interregnum +interrelate/DGNSX +interrelationship/MS +interrogate/DGNSVX +interrogatory +interrupt/DGSV +interruptible +interruption/MS +intersect/DGS +intersection/MS +intersperse/DGNS +interstage +interstate +interstice +interstitial +intertwine/DGS +interval/MS +intervene/DGS +intervenor +intervention/MS +interview/DGRSZ +interviewee +interwoven +intestate +intestinal +intestine/MS +intimacy +intimal +intimate/DGNRXY +intimidate/DGNS +into +intolerable +intolerably +intolerance +intolerant +intonate/NX +intonation/M +intone +intoxicant +intoxicate/DGN +intracity +intractability +intractable +intractably +intradepartment +intramural +intramuscular +intranasal +intransigent +intransitive/Y +intraoffice +intraprocess +intrastate +intravenous +intrepid +intricacy/S +intricate/Y +intrigue/DGS +intrinsic +intrinsically +introduce/DGS +introduction/MS +introductory +introit +introject +introspect/V +introspection/S +introversion +introvert/D +intrude/DGRSZ +intruder/M +intrusion/MS +intrusive +intrust +intubate/DNS +intuitable +intuition/MS +intuitionist +intuitive/Y +inundate +inure +invade/DGRSZ +invalid/SY +invalidate/DGNSX +invalidity/S +invaluable +invariable +invariably +invariance +invariant/SY +invasion/MS +invasive +invective +inveigh +inveigle +invent/DGSV +invention/MS +inventive/PY +inventor/MS +inventory/MS +inverse/NSXY +invert/DGRSZ +invertebrate/MS +invertible +invest/DGS +investigate/DGNSVX +investigator/MS +investigatory +investment/MS +investor/MS +inveterate +inviable +invidious +invigorate +invincible +inviolable +inviolate +invisibility +invisible +invisibly +invitation/MS +invite/DGS +invitee +invocable +invocate/NX +invocation/M +invoice/DGS +invoke/DGRS +involuntarily +involuntary +involute/N +involutorial +involve/DGS +involvement/MS +invulnerable +inward/PSY +iodate +iodide +iodinate +iodine +ion/S +ionic +ionosphere +ionospheric +iota +ipecac +ipsilateral +ipso +irate/PY +ire/MS +ireland/M +iridium +iris +irk/DGS +irksome +iron/DGJS +ironic +ironical/Y +ironside +ironstone +ironwood +irony/S +irradiate +irrational/SY +irreclaimable +irreconcilable +irrecoverable +irredeemable +irredentism +irredentist +irreducible +irreducibly +irreflexive +irrefutable +irregular/SY +irregularity/S +irrelevance/S +irrelevancy +irrelevant/Y +irremediable +irremovable +irreparable +irreplaceable +irrepressible +irreproachable +irreproducible +irresistible +irresolute/N +irresolvable +irrespective/Y +irresponsible +irresponsibly +irretrievable +irreverent +irreversible +irrevocable +irrigate/DGNS +irritable +irritant +irritate/DGNSX +irruption +is +isentropic +isinglass +island/RSZ +isle/MS +islet/MS +isn't +isochronal +isochronous +isocline +isolate/DGNSX +isomer +isometric +isomorph +isomorphic +isomorphically +isomorphism/MS +isopleth +isotherm +isothermal +isotope/MS +isotopic +isotropic +isotropy +israel +issuance +issuant +issue/DGRSZ +isthmus +it'd +it'll +it/M +italian/MS +italic/S +italicize/D +itch/GS +item/MS +itemization/MS +itemize/DGS +iterate/DGNSVX +iterative/Y +iterator/MS +itinerant +itinerary +its +itself +iv +ivory +ivy/MS +ix +j's +j/V +jab/MS +jabbed +jabbing +jack +jackanapes +jackass +jackboot +jackdaw +jacket/DS +jackknife +jackpot +jade/D +jag +jagging +jaguar +jail/DGRSZ +jake +jalopy +jam/S +jamboree +jammed +jamming +jangle +janissary +janitor/MS +janitorial +january/MS +japan +japanese +jar/MS +jargon +jarred +jarring/Y +jasper +jaundice +jaunt/MS +jaunty/P +javelin/MS +jaw/MS +jawbone +jay +jazz +jazzy +jealous/Y +jealousy/S +jean/MS +jeep/MS +jeer/MS +jejune +jejunum +jelly/MS +jellyfish +jenny +jeopard +jeopardize/DGS +jeopardy +jerk/DGJS +jerky/P +jerry +jersey/MS +jess +jest/DGRS +jet/MS +jetliner +jetted +jetting +jettison +jewel/DRS +jewelry/S +jibe +jiffy +jig/MS +jigging +jiggle +jigsaw +jill +jilt +jimmy +jingle/DG +jinx +jitter +jitterbug +jittery +job/MS +jobbing +jobholder +jock +jockey +jockstrap +jocose +jocular +jocund +joey +jog/S +jogging +joggle +join/DGRSZ +joint/MSY +joke/DGRSZ +jolly +jolt/DGS +jonquil +joss +jostle/DGS +jot/S +jotted +jotting +joule +jounce +journal/MS +journalese +journalism +journalist/MS +journalize/DGS +journey/DGJS +journeyman +journeymen +joust/DGS +jovial +jowl +jowly +joy/MS +joyful/Y +joyous/PY +joyride +joystick +jubilant +jubilate +jubilee +judge/DGS +judgment/MS +judicable +judical +judicatory +judicature +judicial +judiciary +judicious/Y +judo +jug/MS +jugate +jugging +juggle/GRSZ +juice/MS +juicy/T +juju +jujube +juke +julep +july/MS +jumble/DS +jumbo +jump/DGRSZ +jumpy +junco +junction/MS +junctor +juncture/MS +june +jungle/MS +junior/MS +juniper +junk/RSZ +junkerdom +junketeer +junky +junta +jure +juridic +jurisdiction/MS +jurisprudent +jurisprudential +juror/MS +jury/MS +just/PY +justice/MS +justiciable +justifiable +justifiably +justifier/M +justify/DGNRSXZ +jut +jute +jutting +juvenile/MS +juxtapose/DGS +juxtaposition +k's +k/J +kaiser +kale +kaleidescope +kaleidoscope +kalmia +kamikaze +kangaroo +kaolin +kaolinite +kapok +kappa +karate +karyatid +kava +kayo +kazoo +keddah +keel/DGS +keelson +keen/PRTY +keep/GRSZ +keeshond +keg +kelly +kelp +ken +kennel/MS +kenning +keno +kept +kerchief/MS +kern +kernel/MS +kerosene +kerry +kerygma +kestrel +ketch +ketchup +ketone +ketosis +kettle/MS +key/DGS +keyboard/MS +keyhole +keynote +keypad/MS +keypunch +keystone +keystroke/MS +keyword/MS +khaki +khan +kibbutzim +kibitz +kick/DGRSZ +kickback +kickoff +kid/MS +kidded +kiddie +kidding +kidnap/S +kidnapper/MS +kidnapping/MS +kidney/MS +kill/DGJRSZ +killdeer +killing/Y +killjoy +kilobit +kilobuck +kilobyte +kilogauss +kilogram/S +kilohertz +kilohm +kilojoule +kilometer/S +kiloton +kilovolt +kilowatt +kiloword +kimono +kin +kind/PRSTY +kindergarten +kindhearted +kindle/DGS +kindred +kinematic +kinesic +kinesthesis +kinetic +king/SY +kingbird +kingdom/MS +kingfisher +kinglet +kingpin +kink +kinky +kinship +kinsman +kiosk +kirk +kiss/DGRSZ +kit/DGMS +kitchen/MS +kitchenette +kite/DGS +kitten/MS +kittenish +kittle +kitty +kiva +kivu +klaxon +kleenex +kludges +klystron +knack +knapsack/MS +knave/MS +knead/S +knee/DS +kneecap +kneeing +kneel/DGS +knell/MS +knelt +knew +knick +knickerbocker/MS +knife/DGS +knifelike +knight/DGSY +knighthood +knit/S +knitting +knives +knob/MS +knobby +knock/DGRSZ +knockdown +knockout +knoll/MS +knot/MS +knotted +knotting +knotty +know/GRS +knowable +knoweth +knowhow +knowing/Y +knowledgable +knowledge +knowledgeable +known +knuckle/DS +knuckleball +knurl +koala +kodak +kohlrabi +koinonia +kola +kolkhoz +kombu +kosher +kraft +kraut +krypton +kudo +kudzu +kulak +kumquat +kwashiorkor +l'oeil +l's +l/VX +la/H +lab/MS +label/DGRS +labile +labor/DGJRSZ +laboratory/MS +laborious/Y +labour +labradorite +labyrinth +labyrinths +lac/DG +lace/DGS +lacerate/DGNSX +lacewing +lack/DGS +lackadaisic +lackey +lacquer/DS +lacrosse +lactate +lacuna +lacunae +lacustrine +lacy +lad/GNS +ladder +ladle +lady/MS +ladyfern +ladylike +lag/RSZ +lagging +lagoon/MS +lagrangian +laid +lain +lair/MS +laissez +laity +lake/MS +lakeside +lam/DG +lamb/MS +lambda +lambert +lame/DGPSY +lamellar +lament/DGS +lamentable +lamentation/MS +laminar +laminate +lamming +lamp/MS +lampblack +lamplight +lampoon +lamprey +lance/DRS +land/DGJRSZ +landau +landfill +landhold +landlady/MS +landlord/MS +landmark/MS +landowner/MS +landscape/DGS +landslide +lane/MS +language/MS +languid/PY +languish/DGS +lanky +lantern/MS +lanthanide +lanthanum +lap/MS +lapel/MS +lapelled +lapidary +lappet +lapping +laps/DGS +lapse/DGS +larceny +larch +lard/R +large/PRTY +largemouth +largesse +lariat +lark/MS +larkspur +larva +larvae +larval +laryngeal +larynges +larynx +lascar +lascivious +lase/RZ +laser/M +lash/DGJS +lass/MS +lasso +last/DGSY +latch/DGS +late/PRTY +latency +latent +latera +lateral/Y +laterite +latex +lathe +latitude/MS +latitudinal +latitudinary +latrine/MS +latter/Y +lattice/MS +latus +laud +laudanum +laudatory +laugh/DG +laughable +laughably +laughing/Y +laughingstock +laughs +laughter +launch/DGJRS +launder/DGJRS +laundry +laura +laureate +laurel/MS +lava +lavabo +lavatory/MS +lavender +lavish/DGY +law/MS +lawbreaker +lawbreaking +lawful/Y +lawgiver +lawgiving +lawless/P +lawmake +lawman +lawmen +lawn/MS +lawrencium +lawsuit/MS +lawyer/MS +lax +laxative +lay/GRSZ +layer/DG +layette +layman +laymen +layoff/S +layout/MS +layup +laze/DG +lazily +lazy/PRT +lazybones +lea +leach +leachate +lead/DGJNRSZ +leadership/MS +leadeth +leadsman +leadsmen +leaf/DG +leafless +leaflet/MS +leafy/T +league/DRSZ +leak/DGS +leakage/MS +leaky +lean/DGPRST +leap/DGS +leapfrog +leapt +learn/DGRSZ +lease/DGS +leasehold +leash/MS +least +leather/DS +leatherback +leathern +leatherneck +leatherwork +leathery +leave/DGJS +leaven/DG +lebensraum +lecher +lechery +lectionary +lecture/DGRSZ +led +ledge/RSZ +lee/RS +leech/MS +leek +leery +leeward +leeway +left +leftist/MS +leftmost +leftover/MS +leftward +lefty +leg/RSZ +legacy/MS +legal/Y +legality +legalization +legalize/DGS +legate +legatee +legato +legend/MS +legendary +legerdemain +legged +legging/S +leggy +leghorn +legibility +legible +legibly +legion/MS +legislate/DGNSV +legislator/MS +legislature/MS +legitimacy +legitimate/Y +legume +leguminous +leisure/Y +leitmotif +leitmotiv +lemma/MS +lemming +lemon/MS +lemonade +lend/GRSZ +length/NXY +lengthen/DG +lengths +lengthwise +lengthy +leniency +lenient/Y +lens/MS +lent/N +lenticular +lentil/MS +leonine +leopard/MS +leper +lepidolite +leprosy +lesion +less/NRX +lessee +lessen/DG +lesson/MS +lessor +lest/R +let/MS +lethal +lethargy +letter/DGRS +letterhead +letterman +lettermen +letting +lettuce +leukemia +levee/MS +level/DGPRSY +levelled +leveller +levellest +levelling +lever/MS +leverage +levitate +levity +levy/DGS +lew +lewd/PY +lewis +lexical/Y +lexicographic +lexicographical/Y +lexicography +lexicon/MS +liability/MS +liable +liaison/MS +liar/MS +libation +libel +libelous +liberal/SY +liberalize/DGS +liberate/DGNS +liberator/MS +libertarian +libertine +liberty/MS +libidinous +libido +librarian/MS +library/MS +librate +librettist +libretto +lice +licensable +license/DGS +licensee +licensor +licentious +lichen/MS +lick/DGS +licorice +lid/MS +lidding +lie/DS +liege +lien/MS +lieu +lieutenant/MS +life/R +lifeblood +lifeboat +lifeguard +lifeless/P +lifelike +lifelong +lifespan +lifestyle/S +lifetime/MS +lift/DGRSZ +ligament +ligand +ligature +light/DGNPRSTXYZ +lighter/M +lightface +lighthearted +lighthouse/MS +lightning/MS +lightproof +lightweight +lignite +lignum +like/DGPSY +likelihood/S +likely/PRT +liken/DGS +likeness/MS +likewise +lilac/MS +lilt +lily/MS +limb/RS +limbic +limbo +lime/MS +limelight +limestone +limit/DGRSZ +limitability +limitably +limitate/NX +limitation/M +limousine +limp/DGPSY +limpet +limpid +limpkin +linden +line/DGJMRSZ +lineage +lineal +linear/Y +linearity/S +linearizable +linearize/DGS +linebacker +lineman +linemen +linen/MS +lineup +linger/DGS +lingerie +lingo +lingua +lingual +linguist/MS +linguistic/S +linguistically +liniment +link/DGRS +linkage/MS +linoleum +linseed +lint +lion/MS +lioness/MS +lip/MS +lipid +lipstick +liquefaction +liquefy +liqueur +liquid/MS +liquidate/NX +liquidation/M +liquidity +liquify/DGRSZ +liquor/MS +lisle +lisp/DGMS +list/DGJNRSXZ +listen/DGRZ +listing/M +lit/RZ +litany +literacy +literal/PSY +literary +literate +literature/MS +lithe +lithic +lithium +lithograph +lithography +lithology +lithosphere +lithospheric +litigant +litigate +litigious +litmus +litter/DGS +litterbug +little/PRT +littleneck +littoral +liturgic +liturgy +livable +livably +live/DGHPRSYZ +livelihood +liverwort +livery/D +livestock +livid +livre +lizard/MS +lo +load/DGJRSZ +loaf/DR +loam +loamy +loan/DGS +loath/DGY +loathe/DG +loathsome +loaves +lob +lobar +lobby/DS +lobe/MS +loblolly +lobo +lobscouse +lobster/MS +lobular +lobule +local/SY +locale +locality/MS +localization +localize/DGS +locate/DGNSVX +locative/S +locator/MS +loci +lock/DGJRSZ +locknut +lockout/MS +locksmith +lockup/MS +locomote/NV +locomotive/MS +locomotor +locomotory +locoweed +locus +locust/MS +locutor +lodestone +lodge/DGJRS +lodgepole +loess +loft/MS +lofty/P +log/MS +logarithm/MS +logarithmic +loge +logged +logger/MS +loggerhead +logging +logic/MS +logical/Y +logician/MS +logistic/S +logjam +loin/MS +loincloth +loiter/DGRS +loll +lollipop +lolly +lone/RYZ +lonely/PRT +lonesome +long/DGJRST +longevity +longhand +longhorn +longish +longitude/MS +longitudinal +longleg +longstanding +longtime +longue +look/DGRSZ +lookahead +lookout +lookup/MS +loom/DGS +loon +loop/DGS +loophole/MS +loose/DGPRSTY +looseleaf +loosen/DGS +loosestrife +loot/DGRS +lop +lope +lopping +lopseed +lopsided +loquacious +loquacity +lord/SY +lordship +lore +lorry +losable +lose/GRSZ +loss/MS +lossy/RT +lost +lot/MS +lotion +lottery +lotus +loud/PRTY +loudspeaker/MS +loudspeaking +lounge/DGS +louse +lousewort +lousy +louver +lovable +lovably +love/DGRSYZ +lovebird +lovelorn +lovely/PRST +loving/Y +low/PRSTYZ +lowboy +lowdown +lower/DG +lowland/S +lowly/T +loy +loyal/Y +loyalty/MS +lozenge +lubricant/M +lubricate/N +lubricious +lubricity +lucid +luck/DS +luckily +luckless +lucky/RT +lucrative +lucre +lucy +ludicrous/PY +lug/R +luge/R +luggage +lugging +luke +lukemia +lukewarm +lull/DS +lullaby +lulu +lumbar +lumber/DG +lumberman +lumbermen +lumen +luminance +luminary +luminescent +luminosity +luminous/Y +lummox +lump/DGS +lumpish +lumpy +lunacy +lunar +lunary +lunate +lunatic +lunch/DGS +luncheon/MS +lunchroom +lunchtime +lung/DS +lunge/D +lupine +lurch/DGS +lure/DGS +lurid +lurk/DGS +luscious/PY +lush +lust/RS +lustful +lustily +lustrous +lusty/P +lutanist +lute/MS +lutetium +lux +luxe +luxuriant/Y +luxuriate +luxurious/Y +luxury/MS +lycopodium +lye/G +lymph +lymphocyte +lymphoma +lynch/DRS +lynx/MS +lyre +lyric/S +lyricism +lysergic +m's +m/X +ma'am +ma/H +macabre +macaque +mace/DS +machination +machine/DGMS +machinelike +machinery +machismo +macho +macintosh +mack +mackerel +mackintosh +macro/MS +macroeconomics +macromolecule/MS +macrophage +macroscopic +mad/PY +madam +madcap +madden/G +madder +maddest +madding +made +mademoiselle +madhouse +madman +madmen +madras +madrigal +madstone +maestro +magazine/MS +magenta +maggot/MS +maggoty +magi +magic +magical/Y +magician/MS +magisterial +magistrate/MS +magna +magnanimity +magnanimous +magnate +magnesia +magnesite +magnesium +magnet +magnetic +magnetism/MS +magnetite +magneto +magnetron +magnificence +magnificent/Y +magnify/DGNRS +magnitude/MS +magnolia +magnum +magpie +mahogany +maid/NSX +maidenhair +maidservant +mail/DGJRS +mailable +mailbox/MS +mailman +mailmen +maim/DGS +main/SY +mainframe/MS +mainland +mainline +mainstay +mainstream +maintain/DGRSZ +maintainability +maintainable +maintenance/MS +maitre +maize +majestic +majesty/MS +major/DS +majority/MS +makable +make/GJRSZ +makeshift +makeup/S +maladapt/V +maladjust +maladroit +malady/MS +malaise +malaprop +malaria +malarial +malconduct +malcontent +maldistribute +male/MPS +maledict +malefactor/MS +malevolent +malfeasant +malformation +malformed +malfunction/DGS +malice +malicious/PY +malign +malignant/Y +mall +mallard +malleable +mallet/MS +mallow +malnourished +malnutrition +malocclusion +malposed +malpractice +malt/DS +maltreat +mama +mambo +mamma/MS +mammal/MS +mammalian +mammoth +man/MSY +mana +manage/DGRSZ +manageable/P +management/MS +manager/M +managerial +manatee +mandamus +mandarin +mandate/DGS +mandatory +mandible +mandrake +mandrel +mandrill +mane/MS +maneuver/DGS +manganese +mange/RZ +mangel +manger/M +mangle/DGRS +manhole +manhood +mania +maniac/MS +maniacal +manic +manicure/DGS +manifest/DGSY +manifestation/MS +manifold/MS +manikin +manila +manipulability +manipulable +manipulatable +manipulate/DGNSVX +manipulator/MS +manipulatory +mankind +manna +manned +mannequin +manner/DSY +mannerism +manning +manometer/MS +manometric +manor/MS +manpower +manse/NX +manservant +mansion/M +manslaughter +mantel/MS +mantic +mantis +mantissa/MS +mantle/MS +mantlepiece +mantrap +manual/MSY +manufacture/DGRSZ +manufacturer/M +manumission +manumit +manumitted +manure +manuscript/MS +many +manzanita +map/MS +maple/MS +mappable +mapped +mapping/MS +mar/S +marathon +maraud +marble/GS +march/DGRS +mare/MS +margarine +margin/MS +marginal/Y +marginalia +maria +marigold +marijuana +marimba +marina +marinade +marinate +marine/RS +marionette +marital +maritime +mark/DGJRSZ +markable +marked/Y +market/DGJS +marketability +marketable +marketeer +marketplace/MS +marketwise +marksman +marksmen +marlin +marmalade +marmot +maroon +marque +marquee +marquess +marquis +marriage/MS +marriageable +marring +marrow +marrowbone +marry/DGS +marsh/MS +marshal/DGS +marshland +marshmallow +mart/NS +martial +martin +martingale +martini +martyr/MS +martyrdom +marvel/DS +marvelled +marvelling +marvelous/PY +maryland +mascara +masculine/Y +masculinity +maser +mash/DGS +mask/DGJRS +masochist/MS +mason/MS +masonry +masque +masquerade/GRS +mass/DGSV +massachusetts +massacre/DS +massage/GS +masseur +massif +mast/DRSZ +master/DGJMY +masterful/Y +mastermind +masterpiece/MS +mastery +mastic +mastiff +mastodon +masturbate/DGNS +mat/DGJMRS +match/DGJRSZ +matchable +matchbook +matchless +matchmake +mate/DGJMRS +material/SY +materialize/DGS +materiel +maternal/Y +maternity +mathematic/S +mathematical/Y +mathematician/MS +matinal +matinee +matins +matriarch +matriarchal +matrices +matriculate/N +matrimonial +matrimony +matrix +matroid +matron/Y +matte/DGRZ +matter/D +mattock +mattress/MS +maturate/N +mature/DGSY +maturity/S +maudlin +maul +mausoleum +mauve +maverick +maw +mawkish +max +maxim/MS +maxima +maximal/Y +maximize/DGRSZ +maximum/S +maxwell +may +mayapple +maybe +mayhap +mayhem +mayonnaise +mayor/MS +mayoral +mayst +maze/MS +mazurka +me +mead +meadow/MS +meadowland +meadowsweet +meager/PY +meal/MS +mealtime +mealy +mean/GJPRSTY +meander/DGS +meaning/M +meaningful/PY +meaningless/PY +meant +meantime +meanwhile +measle/S +measurable +measurably +measure/DGRS +measurement/MS +meat/MS +meaty +mechanic/MS +mechanical/Y +mechanism/MS +mechanist +mechanization/MS +mechanize/DGS +mecum +medal/MS +medallion/MS +meddle/DGRS +media +medial +median/MS +mediate/DGNSX +medic/MS +medical/Y +medicate +medicinal/Y +medicine/MS +medico +medieval +mediocre +mediocrity +meditate/DGNSVX +medium/MS +medley +medusa +meek/PRTY +meet/GJS +meetinghouse +megabit +megabyte +megahertz +megalomania +megalomaniac +megaton +megavolt +megawatt +megaword +megohm +melamine +melancholy +melange +melanin +melanoma +meld +melee +meliorate +mellow/DGPS +melodic +melodious/PY +melodrama/MS +melodramatic +melody/MS +melon/MS +melt/DGS +melting/Y +member/MS +membership/MS +membrane +memento +memo/MS +memoir/S +memorabilia +memorable/P +memoranda +memorandum +memorial/SY +memorization +memorize/DGRS +memory/MS +memoryless +men/MS +menace/DG +menagerie +menarche +mend/DGRS +mendacious +mendacity +mendelevium +menfolk +menhaden +menial/S +menstruate +mensurable +mensuration +mental/Y +mentality/S +mention/DGRSZ +mentionable +mentor/MS +menu/MS +mercantile +mercenary/MPS +mercer +merchandise/GR +merchant/MS +merciful/Y +merciless/Y +mercurial +mercuric +mercury +mercy +mere/TY +meretricious +merganser +merge/DGRSZ +meridian +meridional +meringue +merit/DGS +meritorious/PY +merlin +mermaid +merrily +merriment +merry/T +merrymake +mesa +mescal +mescaline +mesenteric +mesh +mesmeric +meson +mesquite +mess/DGS +message/MS +messenger/MS +messiah +messiahs +messieurs +messily +messy/PRT +met/DGRSZ +metabole +metabolic +metabolism +metabolite +metacircular +metacircularity +metal/MS +metalanguage +metallic +metalliferous +metallization/S +metallography +metalloid +metallurgic +metallurgy +metalwork +metamathematical +metamorphic +metamorphism +metamorphose +metamorphosis +metaphor/MS +metaphoric +metaphorical/Y +metaphysical/Y +metaphysics +metavariable +mete/DGRSZ +meteor/MS +meteoric +meteorite +meteoritic +meteorology +meter/G +methacrylate +methane +method/MS +methodic +methodical/PY +methodist/MS +methodological/Y +methodologists +methodology/MS +methyl +methylene +meticulous +metier +metric/MS +metrical +metro +metronome +metropolis +metropolitan +mettle +mettlesome +mew/DS +mezzo +mi/N +miasma +miasmal +mica +mice +michigan +micro +microbial +microbicidal +microbicide +microcode/DGS +microcomputer/MS +microcosm +microeconomics +microfilm/MS +micrography +microinstruction/MS +microjoule +micron +microphone/GS +microprocessing +microprocessor/MS +microprogram/MS +microprogrammed +microprogramming +microscope/MS +microscopic +microscopy +microsecond/MS +microstore +microword/S +mid +midband +midday +middle/GS +middleman +middlemen +middleweight +midge +midget +midland +midmorn +midnight/S +midpoint/MS +midrange +midscale +midsection +midshipman +midshipmen +midspan +midst/S +midstream +midsummer +midway +midweek +midwest +midwife +midwinter +midwives +miff +mig +might +mightily +mightn't +mighty/PRT +mignon +migrant +migrate/DGNSX +migratory +mike +mila +milch +mild/PRTY +mildew +mile/MS +mileage +milestone/MS +milieu +militant/Y +militarily +militarism +militarist +military +militate +militia +militiamen +milk/DGRSZ +milkmaid/MS +milkweed +milky/P +mill/DGRS +millenarian +millenia +millennia +millennium +millet +milliammeter +milliampere/S +millihenry +millijoule +millimeter/S +millinery +million/HS +millionaire/MS +millipede/MS +millisecond/S +millivolt +millivoltmeter +milliwatt +millstone/MS +milord +milt +mimeograph +mimesis +mimetic +mimic/S +mimicked +mimicking +minaret +mince/DGS +mincemeat +mind/DGS +mindful/PY +mindless/Y +mine/DGNRSZ +minefield +mineral/MS +mineralogy +minesweeper +mingle/DGS +mini +miniature/MS +miniaturization +miniaturize/DGS +minicomputer/MS +minim +minima +minimal/Y +minimax +minimization/MS +minimize/DGRSZ +minimum +minister/DGMS +ministerial +ministry/MS +mink/MS +minnesota/M +minnow/MS +minor/MS +minority/MS +minot +minstrel/MS +minstrelsy +mint/DGRS +minuend +minuet +minus +minuscule +minute/PRSY +minuteman +minutemen +minutiae +miracle/MS +miraculous/Y +mirage +mire/DS +mirror/DGS +mirth +misanthrope +misanthropic +misbehaving +miscalculation/MS +miscegenation +miscellaneous/PY +miscellany +mischief +mischievous/PY +misconception/MS +misconstrue/DS +miscreant +miser/SY +miserable/P +miserably +misery/MS +misfit/MS +misfortune/MS +misgiving/S +mishap/MS +misjudgment +mislead/GS +misled +mismatch/DGS +misnomer +misogynist +misogyny +misplace/DGS +mispronunciation +misrepresentation/MS +miss/DGSV +misshapen +missile/MS +mission/RS +missionary/MS +misspell/DGJS +mist/DGRSZ +mistakable +mistake/GNS +mistaken/Y +mistletoe +mistress +mistrust/D +misty/P +mistype/DGS +misunderstand/GJRZ +misunderstanding/M +misunderstood +misuse/DGS +mit/MR +mite/R +miterwort +mitigate/DGNSV +mitral +mitre +mitt/NX +mitten/M +mix/DGRSZ +mixture/MS +mixup +mnemonic/MS +mnemonically +moan/DS +moat/MS +mob/MS +mobbing +mobcap +mobile +mobility +mobster +moccasin/MS +mock/DGRS +mockernut +mockery +mockingbird +mockup +modal/Y +modality/MS +mode/ST +model/DGJS +modem/S +moderate/DGNPSY +modern/PSY +modernity +modernize/DGR +modest/Y +modesty +modicum +modifiability +modifiable +modify/DGNRSXZ +modish +modular/Y +modularity +modularization +modularize/DGS +modulate/DGNSX +modulator/MS +module/MS +moduli +modulo +modulus +modus +moiety +moire +moist/NPY +moisture +molal +molar +molasses +mold/DGRS +moldboard +mole/ST +molecular +molecule/MS +molehill +molest/DGS +mollify +mollusk +mollycoddle +molt/N +molybdate +molybdenite +molybdenum +moment/MS +momenta +momentarily +momentary/P +momentous/PY +momentum +mommy +monad +monadic +monarch +monarchic +monarchs +monarchy/MS +monastery/MS +monastic +monaural +monday/MS +monel +monetarism +monetary +money/DS +moneymake +moneywort +mongoose +monies +monitor/DGS +monitory +monk/MS +monkey/DGS +monkeyflower +monkish +monochromatic +monochromator +monocotyledon +monocular +monogamous +monogamy +monogram/MS +monograph/MS +monographs +monolith +monolithic +monologist +monologue +monomer +monomeric +monomial +monopoly/MS +monotheism +monotone +monotonic +monotonically +monotonicity +monotonous/PY +monotony +monoxide +monsieur +monsoon +monster/MS +monstrosity +monstrous/Y +montage +montana/M +monte +month/Y +months +monument/MS +monumental/Y +moo +mood/MS +moody/P +moon/DGS +moonlight/GR +moonlike +moonlit +moonshine +moor/DGJS +moose +moot +mop/DS +mopping +moraine +moral/SY +morale +morality/S +morass +moratorium +morbid/PY +more/S +morel +moreover +morgen +morgue +moribund +morn/GJ +moron +morose +morpheme +morphemic +morphine +morphism/S +morphological +morphology +morphophonemic +morris +morrow +morsel/MS +mort +mortal/SY +mortality +mortar/DGS +mortem +mortgage/MS +mortgagee +mortgagor +mortician +mortify/DGNS +mortise +mosaic/MS +mosque +mosquito +mosquitoes +moss/MS +mossy +most/Y +mot/V +motel/MS +motet +moth/RZ +mother/DGMRYZ +motherhood +motherland +motif/MS +motion/DGS +motionless/PY +motivate/DGNSX +motive/S +motley +motor/GS +motorcar/MS +motorcycle/MS +motorist/MS +motorize/DGS +mottle +motto +mottoes +mould/G +mound/DS +mount/DGJRS +mountain/MS +mountaineer/GS +mountainous/Y +mountainside +mourn/DGRSZ +mournful/PY +mouse/RS +moustache +mousy +mouth/DGS +mouthe/DGS +mouthful +mouthpiece +mouths +movable +move/DGJRSZ +movement/MS +movie/MS +mow/DRS +mr +ms +mu +much +mucilage +muck/GR +mucosa +mucus +mud +mudding +muddle/DGRSZ +muddlehead +muddy/DP +mudguard +mudsling +muezzin +muff/MS +muffin/MS +muffle/DGRS +mug/MS +mugging +muggy +mugho +mulatto +mulberry/MS +mulch +mulct +mule/MS +mulish +mull +mullah +mullein +mulligan +mulligatawny +mullion +multi +multicellular +multidimensional +multilevel +multinational +multinomial +multiple/MS +multiplex/DGS +multiplexor/MS +multipliable +multiplicand/MS +multiplicative/S +multiplicity +multiply/DGNRSXZ +multiprocess/G +multiprocessor/MS +multiprogram +multiprogrammed +multiprogramming +multistage +multitude/MS +multitudinous +multivariate +mum +mumble/DGJRSZ +mummy/MS +munch/DG +mundane/Y +mung +municipal/Y +municipality/MS +munificent +munition/S +muon +mural +murder/DGRSZ +murderous/Y +muriatic +murk +murky +murmur/DGRS +murre +muscle/DGS +muscular +musculature +muse/DGJS +museum/MS +mush +mushroom/DGS +mushy +music +musical/SY +musicale +musician/SY +musicology +musk/S +muskellunge +musket/MS +muskmelon +muskox/N +muskrat/MS +muslim +muslin +mussel/MS +must/RS +mustache/DS +mustachio +mustang +mustard +mustn't +musty/P +mutability +mutable/P +mutandis +mutant +mutate/DGNSVX +mutatis +mute/DPY +mutilate/DGNS +mutineer +mutiny/MS +mutt/RZ +mutter/DGRZ +mutton +mutual/Y +mutuel +muzzle/MS +my +mycobacteria +mycology +myel +myeline +myeloid +mylar +mynah +myocardial +myocardium +myofibril +myopia +myopic +myosin +myriad +myrrh +myrtle +myself +mysterious/PY +mystery/MS +mystic/MS +mystical +mystify +mystique +myth +mythic +mythical +mythology/MS +n's +nab +nabbing +nadir +nag/MS +nagging +naiad +nail/DGS +naive/PY +naivete +naked/PY +name/DGRSYZ +nameable +nameless/Y +nameplate +namesake/MS +nanosecond/S +nap/MS +napkin/MS +napping +narcissist +narcissus +narcosis +narcotic/S +narrate/V +narrative/MS +narrow/DGPRSTY +nary +nasal/Y +nascent +nastily +nasturtium +nasty/PRT +natal +nation/MS +national/SY +nationalist/MS +nationality/MS +nationalization +nationalize/DGS +nationhood +nationwide +native/SY +nativity +natty +natural/PSY +naturalism +naturalist +naturalization +nature/DMS +naturopath +naught +naughty/PR +nausea +nauseate +nauseum +nautical +nautilus +naval/Y +nave +navel +navigable +navigate/DGNS +navigator/MS +navy/MS +nay +nazi/MS +nd +ne/T +near/DGPRSTY +nearby +nearsighted +neat/PRTY +neath +nebula +nebulae +nebular +nebulous +necessarily +necessary/S +necessitate/DGNS +necessity/S +neck/GS +necklace/MS +neckline +necktie/MS +necromancer +necromancy +necromantic +necropsy +necrosis +necrotic +nectar +nectareous +nectary +nee/D +need/DGS +needful +needham +needle/DGRSZ +needlepoint +needless/PY +needlework +needn't +needy +negate/DGNSVX +negative/SY +negator/S +neglect/DGS +negligee +negligence +negligent +negligible +negotiable +negotiate/DGNSX +negro +negroes +neigh +neighbor/GSY +neighborhood/MS +neither +nemesis +neoclassic +neodymium +neolithic +neologism +neon +neonatal +neonate +neophyte/S +neoprene +nepal +nepenthe +nephew/MS +neptunium +nereid +nerve/MS +nervous/PY +nest/DGRS +nestle/DGS +net/MS +nether +netherlands +netherworld +netted +netting +nettle/D +nettlesome +network/DGMS +neural +neuralgia +neurasthenic +neuritis +neuroanatomic +neuroanatomy +neuroanotomy +neurological +neurologists +neurology +neuromuscular +neuron/MS +neuronal +neuropathology +neurophysiology +neuropsychiatric +neuroses +neurosis +neurotic +neuter +neutral/Y +neutrality/S +neutralize/DG +neutrino/MS +neutron +neve/R +nevertheless +new/PRSTY +newborn +newcomer/MS +newel +newfound +newlywed +newsboy +newscast +newsletter +newsman +newsmen +newspaper/MS +newspaperman +newspapermen +newsreel +newsstand +newt +newton +newtonian +next +nib/S +nibble/DGRSZ +nice/PRTY +nicety +niche +nichrome +nick/DGRS +nickel/MS +nickname/DS +nicotine +niece/MS +nifty +niggardly +nigger +niggle +nigh +night/SY +nightcap +nightclub +nightdress +nightfall +nightgown +nighthawk +nightingale/MS +nightmare/MS +nightmarish +nightshirt +nighttime +nihilism +nihilist +nil +nilpotent +nimble/PR +nimbly +nimbus +nine/S +ninebark +ninefold +nineteen/HS +ninetieth +ninety/S +ninth +niobium +nip/S +nipping +nipple +nirvana +nit +nitpick +nitrate +nitric +nitride +nitrite +nitrogen +nitrogenous +nitroglycerine +nitrous +nitty +no +nob/Y +nobelium +nobility +noble/PRST +nobleman +noblemen +noblesse +nobody +nobody'd +nocturnal/Y +nocturne +nod/MS +nodal +nodded +nodding +node/MS +nodular +nodule +noise/S +noiseless/Y +noisemake +noisily +noisy/PR +nolo +nomenclature +nominal/Y +nominate/DGNV +nominee +non +nonblocking +nonce +nonchalant +nonconservative +noncyclic +nondecreasing +nondescript/Y +nondestructively +nondeterminacy +nondeterminate/Y +nondeterminism +nondeterministic +nondeterministically +none +nonempty +nonetheless +nonexistence +nonexistent +nonextensible +nonfunctional +noninteracting +noninterference +nonintuitive +nonlinear/Y +nonlinearity/MS +nonlocal +nonnegative +nonogenarian +nonorthogonal +nonorthogonality +nonperishable +nonprocedural/Y +nonprogrammable +nonprogrammer +nonsense +nonsensic +nonsensical +nonspecialist/MS +nontechnical +nonterminal/MS +nonterminating +nontermination +nontrivial +nonuniform +nonzero +noodle +nook/MS +noon/S +noonday +noontide +noontime +noose +nor/H +norm/MS +normal/SY +normalcy +normality +normalization +normalize/DGS +normative +northbound +northeast/R +northeastern +northerly +northern/RYZ +northernmost +northland +northward/S +northwest +northwestern +nose/DGS +nosebag +nosebleed +nostalgia +nostalgic +nostril/MS +not/DG +notable/S +notably +notarize/DGS +notary +notate/NX +notation/M +notational +notch/DGS +note/DGNSX +notebook/MS +noteworthy +nothing/PS +notice/DGS +noticeable +noticeably +notify/DGNRSXZ +notoriety +notorious/Y +notwithstanding +noun/MS +nourish/DGS +nourishment +nouveau +nova +novel/MS +novelist/MS +novelty/MS +november +novice/MS +novitiate +novo +now +nowaday/S +nowhere +nowise +noxious +nozzle +nu +nuance/S +nubile +nucleant +nuclear +nucleate +nuclei +nucleic +nucleoli +nucleolus +nucleotide/MS +nucleus +nuclide +nude +nudge +nugatory +nugget +nuisance/MS +null/DS +nullary +nullify/DGSZ +numb/DGPRSYZ +number/DGR +numberless +numerable +numeral/MS +numerate +numerator/MS +numeric/S +numerical/Y +numerology +numerous +numinous +numismatic +numismatist +nun/MS +nuptial +nurse/DGS +nursery/MS +nurture/DGS +nut/MS +nutate +nutcrack +nuthatch +nutmeg +nutria +nutrient +nutrition +nutritious +nutritive +nutshell +nutting +nuzzle +nylon +nymph +nymphomania +nymphomaniac +nymphs +o'clock +o'er +o's +oaf +oak/NS +oakwood +oar/MS +oases +oasis +oat/NS +oath +oaths +oatmeal +obduracy +obdurate +obedience/S +obedient/Y +obeisant +obelisk +obese +obey/DGS +obfuscate +obfuscatory +obituary +object/DGMRSV +objectify +objection/MS +objectionable +objective/SY +objectivity +objector/MS +objet +obligate/NX +obligation/M +obligatory +oblige/DGS +obliging/Y +oblique/PY +obliterate/DGNS +oblivion +oblivious/PY +oblong +obnoxious +oboe +oboist +obscene +obscure/DGRSY +obscurity/S +obsequious +obsequy +observable +observance/MS +observant +observation/MS +observatory +observe/DGRSZ +obsess/V +obsession/MS +obsidian +obsolescence +obsolescent +obsolete/DGS +obstacle/MS +obstinacy +obstinate/Y +obstruct/DGV +obstruction/MS +obtain/DGS +obtainable +obtainably +obtrude +obtrusive +obverse +obviate/DGNSX +obvious/PY +ocarina +occasion/DGJS +occasional/Y +occident +occidental +occipital +occlude/DS +occlusion/MS +occlusive +occult +occultate +occupancy/S +occupant/MS +occupation/MS +occupational/Y +occupy/DGRS +occur/S +occurred +occurrence/MS +occurrent +occurring +ocean/MS +oceanic +oceanography +oceanside +ocelot +octagon +octagonal +octahedra +octahedral +octahedron +octal +octane +octave/S +octennial +octet +octile +octillion +october +octogenarian +octopus +octoroon +ocular +odd/PRSTY +oddity/MS +ode/MS +odious/PY +odium +odometer +odor/MS +odorous/PY +oedipus +oersted +of +off/GRZ +offal +offbeat +offend/DGRSZ +offense/SV +offensive/PY +offer/DGJRZ +offertory +offhand +office/RSZ +officeholder +officemate +officer/M +official/SY +officialdom +officiate +officio +officious/PY +offload +offsaddle +offset/MS +offsetting +offshoot +offshore +offspring +offstage +oft/N +oftentimes +ogle +ogre +ogress +oh +ohio/M +ohm +ohmic +ohmmeter +oil/DGRSZ +oilcloth +oilman +oilmen +oilseed +oily/RT +oint +ointment +ok +okay +old/NPRT +oldster +oldy +oleander +olefin +oleomargarine +oligarchic +oligarchy +oligoclase +oligopoly +olive/MS +olivine +omega +omelet +omen/MS +omicron +ominous/PY +omission/MS +omit/S +omitted +omitting +omnibus +omnipotent +omnipresent +omniscient/Y +omnivore +on/Y +onanism +onboard +once +oncology +oncoming +one/MNPSX +onerous +oneself +onetime +oneupmanship +ongoing +online +onlooker +onlooking +onrush/G +onset/MS +onslaught +onto +ontogeny +ontology +onus +onward/S +onyx +oodles +ooze/D +opacity +opal/MS +opalescent +opaque/PY +opcode +open/DGJPRSYZ +opening/M +opera/MS +operable +operand/MS +operandi +operant +operate/DGNSVX +operatic +operational/Y +operative/S +operator/MS +operetta +opiate +opinion/MS +opinionate +opium +opossum +opponent/MS +opportune/Y +opportunism +opportunistic +opportunity/MS +opposable +oppose/DGS +opposite/NPSY +oppress/DGSV +oppression +oppressor/MS +opprobrium +opt/DGS +opthalmic +opthalmologic +opthalmology +optic/S +optical/Y +optima +optimal/Y +optimality +optimism +optimist +optimistic +optimistically +optimization/MS +optimize/DGRSZ +optimum +option/MS +optional/Y +optoacoustic +optoisolate +optometric +optometrist +optometry +opulent +opus +or/MY +oracle/MS +oral/Y +orange/MS +orangeroot +orangutan +orate/NX +oration/M +orator/MS +oratoric +oratorio +oratory/MS +orb +orbit/DGRSZ +orbital/Y +orchard/MS +orchestra/MS +orchestral +orchestrate +orchid/MS +orchis +ordain/DGS +ordeal +order/DGJSY +orderly/S +ordinal +ordinance/MS +ordinarily +ordinary/P +ordinate/NS +ordnance +ore/MS +oregano +organ/MS +organdy +organic +organism/MS +organismic +organist/MS +organizable +organization/MS +organizational/Y +organize/DGRSZ +organometallic +orgasm +orgiastic +orgy/MS +orient/DGS +oriental +orientation/MS +orifice/MS +origin/MS +original/SY +originality +originate/DGNS +originator/MS +oriole +ornament/DGS +ornamental/Y +ornamentation +ornate/Y +ornery +orographic +orography +orphan/DS +orphanage +orthant +orthicon +orthoclase +orthodontic +orthodontist +orthodox +orthodoxy +orthogonal/Y +orthogonality +orthography +orthonormal +orthopedic +orthophosphate +orthorhombic +oscillate/DGNSX +oscillation/M +oscillator/MS +oscillatory +oscilloscope/MS +osier +osmium +osmosis +osmotic +osprey +osseous +ossify +ostensible +ostentatious +osteology +osteopath +osteopathic +osteopathy +osteoporosis +ostracism +ostracod +ostrich/MS +other/S +otherwise +otherworld/Y +otter/MS +ouch +ought +oughtn't +ounce/S +our/S +ourself +ourselves +oust +out/GRS +outbreak/MS +outburst/MS +outcast/MS +outcome/MS +outcry/S +outdoor/S +outermost +outfit/MS +outgoing +outgrew +outgrow/GHS +outgrown +outlandish +outlast/S +outlaw/DGS +outlawry +outlay/MS +outlet/MS +outline/DGS +outlive/DGS +outlook +outperform/DGS +outpost/MS +output/MS +outputting +outrage/DS +outrageous/Y +outright +outrun/S +outset +outside/RZ +outsider/M +outskirts +outstanding/Y +outstretched +outstrip/S +outstripped +outstripping +outvote/DGS +outward/Y +outweigh/DG +outweighs +outwit/S +outwitted +outwitting +ouzel +ouzo +ova +oval/MS +ovary/MS +ovate +oven/MS +ovenbird +over/Y +overall/MS +overboard +overcame +overcoat/MS +overcome/GS +overcrowd/DGS +overdone +overdraft/MS +overdue +overemphasis +overemphasized +overestimate/DGNS +overflow/DGS +overhang/GS +overhaul/G +overhead/S +overhear/GS +overheard +overjoy/D +overland +overlap/MS +overlapped +overlapping +overlay/DGS +overload/DGS +overlook/DGS +overnight/RZ +overpower/DGS +overprint/DGS +overproduction +overridden +override/GS +overrode +overrule/DS +overrun/S +overseas +oversee/RSZ +overseeing +overshadow/DGS +overshoot +overshot +oversight/MS +oversimplify/DGS +overstate/DGS +overstatement/MS +overstocks +overt/Y +overtake/GRSZ +overtaken +overthrew +overthrow +overthrown +overtime +overtone/MS +overtook +overture/MS +overturn/DGS +overuse +overview/MS +overwhelm/DGS +overwhelming/Y +overwork/DGS +overwrite/GS +overwritten +overzealous +oviform +ow/DGY +owe/DGS +owl/MS +own/DGRSZ +ownership/S +ox/N +oxalate +oxalic +oxcart +oxeye +oxidant +oxidate +oxide/MS +oxidize/D +oxygen +oxygenate +oyster/MS +ozone +p's +p/X +pa/H +pace/DGRSZ +pacemake +pacesetting +pacific +pacifism +pacifist +pacify/NRS +pack/DGRSZ +package/DGJRSZ +packet/MS +pact/MS +pad/MS +padded +padding +paddle +paddock +paddy +padlock +padre +paean +pagan/MS +page/DGMRSZ +pageant/MS +pageantry +paginate/DGNS +pagoda +paid +pail/MS +pain/DS +painful/Y +painstaking/Y +paint/DGJRSZ +paintbrush +pair/DGJS +pairwise +pajama/S +pal/DGMRST +palace/MS +palate/MS +palazzi +palazzo +pale/DGPRSTY +palette +palfrey +palindrome +palindromic +palisade +pall +palladia +palladium +pallet +palliate/V +pallid +palm/DGRS +palmate +palmetto +palpable +palsy +pampa +pamper +pamphlet/MS +pan/MS +panacea/MS +panama +pancake/MS +panda +pandemic +pandemonium +pander +pane/MS +panel/DGS +panelist/MS +pang/MS +panic/MS +panicked +panicky +panicle +panjandrum +panned +panning +panoply +panorama +panoramic +pansy/MS +pant/DGS +pantheism +pantheist +pantheon +panther/MS +pantomime +pantomimic +pantry/MS +panty/S +pap/RZ +papa +papal +papaw +paper/DGJMRZ +paperback/MS +paperweight +paperwork +papery +papillary +papoose +pappy +paprika +papyri +papyrus +par/GJ +parabola +parabolic +paraboloid +paraboloidal +parachute/MS +parade/DGS +paradigm/MS +paradigmatic +paradise +paradox/MS +paradoxic +paradoxical/Y +paraffin +paragon/MS +paragonite +paragraph/G +paragraphs +parakeet +paralinguistic +parallax +parallel/DGS +parallelepiped +parallelism +parallelize/DGS +parallelled +parallelling +parallelogram/MS +paralysis +paralyze/DGS +paramagnet +paramagnetic +parameter/MS +parameterizable +parameterization/MS +parameterize/DGS +parameterless +parametric +paramilitary +paramount +paranoia +paranoiac +paranoid +paranormal +parapet/MS +paraphernalia +paraphrase/DGS +parapsychology +parasite/MS +parasitic/S +parasol +parasympathetic +paratroop +paraxial +parboil +parcel/DGS +parch/D +parchment +pardon/DGRSZ +pardonable +pardonably +pare/GJS +paregoric +parent/MS +parentage +parental +parentheses +parenthesis +parenthesized +parenthetic +parenthetical/Y +parenthood +pariah +parimutuel +parish/MS +parishioner +parity +park/DGRSZ +parkish +parkland +parklike +parkway +parlance +parlay +parley +parliament/MS +parliamentarian +parliamentary +parlor/MS +parochial +parody +parole/DGS +parolee +parquet +parrot/GS +parrotlike +parry/D +pars/DGJRSZ +parse/DGJRSZ +parsimonious +parsimony +parsley +parsnip +parson/MS +parsonage +part/DGJRSYZ +partake/GRS +partial/Y +partiality +participant/MS +participate/DGNS +participle +particle/MS +particular/SY +particulate +partisan/MS +partition/DGS +partner/DS +partnership +partook +partridge/MS +party/MS +parvenu +pascal +paschal +pasha +pass/DGRSVZ +passage/MS +passageway +passband +passe/DGNRSVXZ +passenger/MS +passerby +passionate/Y +passivate +passive/PY +passivity +passport/MS +password/MS +past/DGMPS +paste/DGS +pasteboard +pastel +pasteup +pastiche +pastime/MS +pastor/MS +pastoral +pastry +pasture/MS +pasty +pat/NRS +patch/DGS +patchwork +patchy +pate/R +patent/DGRSYZ +patentable +patentee +paternal/Y +paternoster +pathetic +pathogen +pathogenesis +pathogenic +pathological +pathology +pathos +paths +pathway/MS +patience +patient/SY +patina +patio +patriarch +patriarchal +patriarchs +patriarchy +patrician/MS +patrimonial +patrimony +patriot/MS +patriotic +patriotism +patristic +patrol/MS +patrolled +patrolling +patrolman +patrolmen +patron/MS +patronage +patroness +patronize/DGS +patter/DGJS +pattern/DGS +patting +patty/MS +paucity +paunch +paunchy +pauper +pause/DGS +pavanne +pave/DGS +pavement/MS +pavilion/MS +paw/GS +pawn/MS +pawnshop +pax +pay/GRSZ +payable +paycheck/MS +payday +payer/M +paymaster +payment/MS +payoff/MS +payroll +pea/MS +peace +peaceable +peaceful/PY +peacemake +peacetime +peach/MS +peacock/MS +peafowl +peak/DS +peaky +peal/DGS +peanut/MS +pear/SY +pearl/MS +pearlstone +peasant/MS +peasanthood +peasantry +peat +pebble/MS +pecan +peccary +peck/DGS +pectoral +pectoralis +peculate +peculiar/Y +peculiarity/MS +pecuniary +pedagogic +pedagogical +pedagogue +pedagogy +pedal +pedant +pedantic +pedantry +peddle/RZ +peddler/M +pedestal +pedestrian/MS +pediatric/S +pediatrician +pedigree +pediment +pee/DRZ +peek/DGS +peel/DGS +peep/DGRS +peephole +peepy +peer/DG +peerless +peg/MS +pegboard +pegging +pejorative +pelican +pellagra +pellet +pelt/GS +peltry +pelvic +pelvis +pemmican +pen/S +penal +penalize/DGS +penalty/MS +penance +penates +pence +penchant +pencil/DS +pend/DGS +pendant +pendulum/MS +penetrable +penetrate/DGNSVX +penetrating/Y +penetrator/MS +penguin/MS +penicillin +peninsula/MS +penitent +penitential +penitentiary +penman +penmen +penna +pennant +penned +penniless +penning +pennsylvania +penny/MS +pennyroyal +pens/V +pension/RS +pent +pentagon/MS +pentagonal +pentane +pentecostal +penthouse +penultimate +penumbra +penurious +penury +peony +people/DMS +pep +pepper/DGS +peppergrass +peppermint +pepperoni +peppery +pepping +peppy +peptide +per +perceivable +perceivably +perceive/DGRSZ +percent/S +percentage/S +percentile/S +percept/V +perceptible +perceptibly +perception/S +perceptive/Y +perceptual/Y +perch/DGS +perchance +perchlorate +percolate +percussion +percussive +percutaneous +perdition +peremptory +perennial/Y +perfect/DGPSY +perfectible +perfection +perfectionist/MS +perfidious +perfidy +perforate +perforce +perform/DGRSZ +performance/MS +perfume/DGS +perfumery +perfunctory +perfusion +perhaps +peridotite +perihelion +peril/MS +perilous/Y +perimeter +period/MS +periodic +periodical/SY +peripatetic +peripheral/SY +periphery/MS +periphrastic +periscope +perish/DGRSZ +perishable/MS +peritectic +periwinkle +perjure +perjury +perk +perky +permalloy +permanence +permanent/Y +permeable +permeate/DGNS +permissibility +permissible +permissibly +permission/S +permissive/Y +permit/MS +permitted +permitting +permutation/MS +permute/DGS +pernicious +peroxide +perpendicular/SY +perpetrate/DGNSX +perpetrator/MS +perpetual/Y +perpetuate/DGNS +perpetuity +perplex/DG +perplexity +perquisite +persecute/DGNS +persecutor/MS +persecutory +perseverance +persevere/DGS +persiflage +persimmon +persist/DGS +persistence +persistent/Y +person/MS +persona +personage/MS +personal/Y +personality/MS +personalization +personalize/DGS +personify/DGNS +personnel +perspective/MS +perspicacious +perspicous +perspicuity +perspicuous/Y +perspiration +perspire +persuadable +persuade/DGRSZ +persuasion/MS +persuasive/PY +pert +pertain/DGS +pertinacious +pertinent +perturb/D +perturbate/NX +perturbation/M +perusal +peruse/DGRSZ +pervade/DGS +pervasion +pervasive/Y +perverse/N +pervert/DS +pessimal +pessimism +pessimist +pessimistic +pessimum +pest/RS +peste/R +pesticide +pestilence +pestilent +pestilential +pestle +pet/RSZ +petal/MS +peter +peters/N +petit +petite/NX +petition/DGR +petrel +petri +petrify +petrochemical +petroglyph +petrol +petroleum +petrology +petted +petter/MS +petticoat/MS +petting +petty/P +petulant +petunia +pew/MS +pewee +pewter +pfennig +phagocyte +phalanger +phalanx +phalarope +phantasy +phantom/MS +pharmaceutic +pharmacist +pharmacology +pharmacopoeia +pharmacy +phase/DGRSZ +pheasant/MS +phenol +phenolic +phenomena +phenomenal/Y +phenomenological/Y +phenomenology/S +phenomenon +phenotype +phenyl +phi +philanthrope +philanthropic +philanthropy +philharmonic +philodendron +philology +philosoph/RZ +philosopher/M +philosophic +philosophical/Y +philosophize/DGRSZ +philosophy/MS +phloem +phlox +phobic +phoebe +phoenix +phon/DG +phone/DGS +phoneme/MS +phonemic +phonetic/S +phonic +phonograph +phonographs +phonology +phonon +phony +phosgene +phosphate/MS +phosphide +phosphine +phosphor +phosphoresce +phosphorescent +phosphoric +phosphorus +photo/MS +photocopy/DGS +photogenic +photograph/DGRZ +photographic +photographs +photography +photolysis +photolytic +photometric +photometry +photon +phrase/DGJS +phrasemake +phraseology +phthalate +phycomycetes +phyla +phylogeny +phylum +physic/S +physical/PSY +physician/MS +physicist/MS +physiochemical +physiognomy +physiological/Y +physiology +physiotherapist +physiotherapy +physique +phytoplankton +pi/HRZ +pianissimo +pianist +piano/MS +piazza/MS +pica +picayune +piccolo +pick/DGJRSZ +pickaxe +pickerel +picket/DGRSZ +pickle/DGS +pickoff +pickup/MS +picky +picnic/MS +picnicked +picnicker +picnicking +picofarad +picojoule +picosecond +pictorial/Y +picture/DGS +picturesque/P +piddle +pidgin +pie/RSZ +piece/DGS +piecemeal +piecewise +pierce/DGS +pietism +piety +piezoelectric +pig/MS +pigeon/MS +pigeonberry +pigeonfoot +pigeonhole +pigging +piggish +piggy +pigment/DS +pigmentation +pigpen +pigroot +pigskin +pigtail +pike/RS +pile/DGJSZ +pilewort +pilfer +pilferage +pilgrim/MS +pilgrimage/MS +pill/MS +pillage/D +pillar/DS +pillory +pillow/MS +pilot/GS +pimp +pimple +pin/DGMS +pinafore +pinball +pinch/DGS +pincushion +pine/DGNS +pineapple/MS +ping +pinhead +pinhole +pink/PRSTY +pinkie +pinkish +pinnacle/MS +pinnate +pinned +pinning/S +pinochle +pinpoint/GS +pinscher +pint/MS +pintail +pinto +pinwheel +pinxter +pion +pioneer/DGS +pious/Y +pip/DGRZ +pipe/DGRSZ +pipeline/DGS +pipette +pipsissewa +piquant +pique +piracy +pirate/MS +pirogue +pirouette +piss +pistachio +pistil/MS +pistol/MS +pistole +piston/MS +pit/MS +pitch/DGRSZ +pitchblende +pitchfork +pitchstone +piteous/Y +pitfall/MS +pith/DGS +pithy/PRT +pitiable +pitiful/Y +pitiless/Y +pitman +pitted +pitting +pituitary +pity/DGRSZ +pitying/Y +pivot/GS +pivotal +pixel +pixy +pizza +pizzicato +placard/MS +placate/R +place/DGRS +placeable +placebo +placeholder +placement/MS +placenta +placental +placid/Y +plagiarism +plagiarist +plagioclase +plague/DGS +plaguey +plaid/MS +plain/PRSTY +plaintiff/MS +plaintive/PY +plait/MS +plan/DGMRSZ +planar +planarity +plane/DGMRSZ +planeload +planet/MS +planetaria +planetarium +planetary +planetesimal +planetoid +plank/GS +plankton +planned +planner/MS +planning +planoconcave +planoconvex +plant/DGJRSZ +plantain +plantation/MS +plaque +plasm +plasma +plasmon +plaster/DGRS +plastic/S +plasticity +plastisol +plastron +plat/DGNX +plate/DGS +plateau/MS +platelet/MS +platen/M +platform/MS +platinize +platinum +platitude +platitudinous +platonic +platoon +platter/MS +platting +plausibility +plausible +play/DGRSZ +playa +playable +playback +playboy +player/M +playful/PY +playground/MS +playhouse +playmate/MS +playoff +playroom +plaything/MS +playtime +playwright/MS +playwriting +plaza +plea/MS +plead/DGRS +pleas/DGS +pleasant/PY +please/DGS +pleasing/Y +pleasure/S +pleat +plebeian +plebian +plebiscite/MS +pledge/DS +plenary +plenipotentiary +plenitude +plenteous +plentiful/Y +plenty +plenum +plethora +pleura +pleural +pleurisy +pliable +pliancy +pliant +plight +plod +plodding +plop +plopping +plot/MS +plotted +plotter/MS +plotting +plough +ploughman +plover +plow/DGRS +plowman +plowshare +ploy/MS +pluck/DG +plucky +plug/MS +pluggable +plugged +plugging +plum/DMS +plumage +plumb/DGMS +plumbago +plumbate +plume/DS +plummet/G +plump/DP +plunder/DGRSZ +plunge/DGRSZ +plunk +plural/S +plurality +plus +plush +plushy +plutonium +ply/DSZ +plyscore +plywood +pneumatic +pneumonia +poach/RS +pocket/DGS +pocketbook/MS +pocketful +pod/MS +podge +podia +podium +poem/MS +poesy +poet/MS +poetic/S +poetical/Y +poetry/MS +pogo +pogrom +poi +poignant +poinsettia +point/DGRSZ +pointed/Y +pointless +pointy +poise/DS +poison/DGRS +poisonous/P +poke/DGRS +pokerface +pol/DG +poland +polar +polarimeter +polarimetry +polariscope +polariton +polarity/MS +polarogram +polarograph +polarography +polaron +pole/DGS +polecat +polemic/S +police/DGMS +policeman +policemen +policy/MS +polio +polis +polish/DGRSZ +polite/PRTY +politic/S +political/Y +politician/MS +politicking +politico +polity +polka +poll/DGNS +pollcadot +pollock +polloi +pollutant +pollute/DGNS +polo +polonaise +polonium +polopony +polygon +polygonal +polygynous +polyhedra +polyhedral +polyhedron +polymer/MS +polymerase +polymeric +polymorph +polymorphic +polynomial/MS +polyphony +polypropylene +polytechnic +polytope +polytypy +pomade +pomegranate +pomp +pompadour +pompano +pompey +pompon +pomposity +pompous/PY +poncho +pond/RSZ +ponder/DG +ponderous +pong +pont +pontiff +pontific +pontificate +pony/MS +pooch +poodle +pooh +pool/DGS +poop +poor/PRTY +pop/MS +pope +popish +poplar +poplin +popped +popping +poppy/MS +populace +popular/Y +popularity +popularization +popularize/DGS +populate/DGNSX +populous/P +porcelain +porch/MS +porcine +porcupine/MS +pore/DGS +pork/R +pornographer +pornographic +pornography +porosity +porous +porphyry +porpoise +porridge +port/RSYZ +portability +portable +portage +portal/MS +portend/DGS +portent +portentous +porterhouse +portfolio +portico +portion/MS +portland +portmanteau +portrait/MS +portraiture +portray/DGS +portrayal +portulaca +pose/DGRSZ +poseur +posey +posh +posit/DGSV +position/DGS +positional +positive/PSY +positron +posse +posseman +possemen +possess/DGSV +possession/MS +possessional +possessive/PY +possessor/MS +possibility/MS +possible +possibly +possum/MS +post/DGRSZ +postage +postal +postcard +postcondition +postdoctoral +posterior +posteriori +posterity +postfix +postgraduate +posthumous +postlude +postman +postmark +postmaster/MS +postmen +postmortem +postmultiply +postoffice/MS +postoperative +postorder +postpone/DG +postprocess +postprocessor +postscript/MS +postulate/DGNSX +posture/MS +postwar +posy +pot/MS +potable +potash +potassium +potato +potatoes +potbelly +potboil +potent +potentate/MS +potential/SY +potentiality/S +potentiating +potentiometer/MS +pothole +potion +potlatch +potpourri +potted +potter/MS +pottery +potting +pouch/MS +poultice +poultry +pounce/DGS +pound/DGRSZ +pour/DGRSZ +pout/DGS +poverty +pow/RZ +powder/DGS +powderpuff +powdery +power/DG +powerful/PY +powerless/PY +powerset/MS +pox +ppm +practicable +practicably +practical/Y +practicality +practice/DGS +practise +practitioner/MS +pragmatic/S +pragmatically +pragmatism +pragmatist +prairie +praise/DGRSZ +praiseworthy +praising/Y +pram +prance/DGR +prank/MS +praseodymium +prate +pray/DGRZ +prayer/M +prayerful +preach/DGRSZ +preachy +preamble +preassign/DGS +precarious/PY +precaution/MS +precautionary +precede/DGS +precedence/MS +precedent/DS +precept/MS +precess +precession +precinct/MS +precious/PY +precipice +precipitable +precipitate/DGNPSY +precipitous/Y +precise/NPXY +preclude/DGS +precocious/Y +precocity +preconceive/D +preconception/MS +precondition/DS +precursor/MS +predate/DGS +predatory +predecessor/MS +predefine/DGS +predefinition/MS +predetermine/DGS +predicament +predicate/DGNSX +predict/DGSV +predictability +predictable +predictably +prediction/MS +predictor +predilect +predispose +predisposition +predominant/Y +predominate/DGNSY +preeminent +preempt/DGSV +preemption +preemptor +preen +prefab +prefabricate +preface/DGS +prefatory +prefect +prefecture +prefer/S +preferable +preferably +preference/MS +preferential/Y +preferred +preferring +prefix/DS +pregnant +prehistoric +preinitialize/DGS +prejudge/D +prejudice/DS +prejudicial +prelate +preliminary/S +prelude/MS +premature/Y +prematurity +premeditate/D +premier/MS +premiere +premise/MS +premium/MS +premonition +premonitory +preoccupation +preoccupy/DS +prep +preparation/MS +preparative/MS +preparatory +prepare/DGS +preponderant +preponderate +preposition/MS +prepositional +preposterous/Y +prepping +preproduction +preprogrammed +prerequisite/MS +prerogative/MS +presage +prescribe/DS +prescript/V +prescription/MS +preselect/DGS +presence/MS +present/DGPRSY +presentation/MS +presentational +preservation/S +preserve/DGRSZ +preset +preside/DGS +presidency +president/MS +presidential +press/DGJRS +pressure/DGS +pressurize/D +prestidigitate +prestige +prestigious +presto +presumably +presume/DGS +presumption/MS +presumptive +presumptuous/P +presuppose/DGS +presupposition +pretend/DGRSZ +pretense/NSX +pretentious/PY +pretext/MS +prettily +pretty/PRT +prevail/DGS +prevailing/Y +prevalence +prevalent/Y +prevent/DGSV +preventable +preventably +prevention +preventive/S +preview/DGS +previous/Y +prexy +prey/DGS +price/DGRSZ +priceless +prick/DGSY +prickle +pride/DGS +prig +priggish +prim/DGRZ +prima +primacy +primal +primarily +primary/MS +primate +prime/DGPRSZ +primeval +primitive/PSY +primitivism +primp +primrose +prince/SY +princess/MS +principal/SY +principality/MS +principle/DS +print/DGRSZ +printable +printably +printmake +printout +prior +priori +priority/MS +priory +prism/MS +prismatic +prison/RSZ +prisoner/M +prissy +pristine +privacy/S +private/NSXY +privet +privilege/DS +privy/MS +prize/DGRSZ +prizewinning +pro/MS +probabilist +probabilistic +probabilistically +probability/S +probable +probably +probate/DGNSV +probe/DGJS +probity +problem/MS +problematic +problematical/Y +procaine +procedural/Y +procedure/MS +proceed/DGJS +process/DGMS +procession +processor/MS +proclaim/DGRSZ +proclamation/MS +proclivity/MS +procrastinate/DGNS +procreate +procrustean +proctor +procure/DGRSZ +procurement/MS +prod +prodding +prodigal/Y +prodigious +prodigy +produce/DGRSZ +producible +product/MSV +production/MS +productive/Y +productivity +profane/Y +profess/DGS +profession/MS +professional/SY +professionalism +professor/MS +professorial +proffer/DS +proficiency +proficient/Y +profile/DGS +profit/DGS +profitability +profitable +profitably +profiteer/MS +profitted +profitter/MS +profligate +profound/TY +profundity +profuse/N +prog +progenitor +progeny +prognosis +prognosticate +program/MS +programmability +programmable +programmed +programmer/MS +programming +progress/DGSV +progression/MS +progressive/Y +prohibit/DGSV +prohibition/MS +prohibitive/Y +prohibitory +project/DGSV +projectile +projection/MS +projective/Y +projector/MS +prolate +prolegomena +proletariat +proliferate/DGNS +prolific +prolix +prologue +prolong/DGS +prolongate +prolusion +promenade/MS +promethium +prominence +prominent/Y +promiscuous +promise/DGS +promontory +promote/DGNRSXZ +promotional +prompt/DGJPRSTY +promptitude +promulgate/DGNS +prone/P +prong/DS +pronoun/MS +pronounce/DGS +pronounceable +pronouncement/MS +pronto +pronunciation/MS +proof/MS +proofread +prop/RS +propaganda +propagandist +propagate/DGNSX +propane +propel/S +propellant +propelled +propeller/MS +propelling +propensity +proper/PY +property/DS +prophecy/MS +prophesy/DRS +prophet/MS +prophetic +propionate +propitiate +propitious +proponent/MS +proportion/DGS +proportional/Y +proportionate/Y +proportionment +propos/DGRS +proposal/MS +propose/DGRS +proposition/DGS +propositional/Y +propound/DGS +propping +proprietary +proprietor/MS +propriety +proprioception +proprioceptive +propulsion/MS +propyl +propylene +prorate +prorogue +prosaic +proscenium +proscribe +proscription +prose +prosecute/DGNSX +prosecutor +proselytize/DGS +prosodic/S +prosody +prosopopoeia +prospect/DGSV +prospection/MS +prospective/SY +prospector/MS +prospectus +prosper/DGS +prosperity +prosperous +prostate +prosthetic +prostitute/N +prostrate/N +protactinium +protagonist +protean +protease +protect/DGSV +protection/MS +protective/PY +protector/MS +protectorate +protege/MS +protein/MS +proteolysis +proteolytic +protest/DGS +protestant +protestation/S +protesting/Y +protestor/MS +prothonotary +protocol/MS +proton/MS +protoplasm +protoplasmic +prototype/DGS +prototypic +prototypical/Y +protozoan +protract +protrude/DGS +protrusion/MS +protrusive +protuberant +proud/RTY +provability +provable +provably +prove/DGRSZ +proven +provenance +proverb/MS +proverbial +provide/DGRSZ +providence +provident +providential +province/MS +provincial +provision/DGS +provisional/Y +proviso +provocateur +provocation +provocative +provoke/DS +provost +prow/MS +prowess +prowl/DGRZ +proximal +proximate +proximity +proxy +prudence +prudent/Y +prudential +prune/DGRSZ +prurient +pry/GT +psalm/MS +psalter +pseudo +psi +psych/S +psyche/MS +psychiatric +psychiatrist/MS +psychiatry +psychic +psycho +psychoacoustic +psychoanalysis +psychoanalyst +psychoanalytic +psychobiology +psychological/Y +psychologist/MS +psychology +psychometric +psychometry +psychopath +psychopathic +psychophysic/S +psychophysical +psychophysiology +psychopomp +psychoses +psychosis +psychosocial +psychosomatic +psychotherapeutic +psychotherapist +psychotherapy +psychotic +psyllium +ptarmigan +pub/MS +puberty +pubescent +public/Y +publication/MS +publicity +publicize/DGS +publish/DGRSZ +puck/RZ +pucker/DG +puckish +pudding/MS +puddingstone +puddle/GS +puddly +pueblo +puerile +puff/DGS +puffball +puffery +puffin +puffy +pug +puissant +puke +pull/DGJRS +pulley/MS +pullover +pulmonary +pulp/G +pulpit/MS +pulsar +pulsate +pulse/DGS +pulverable +puma +pumice +pummel +pump/DGS +pumpkin/MS +pumpkinseed +pun/MS +punch/DGRS +punctual/Y +punctuate/N +puncture/DGMS +pundit +punditry +pungent +punish/DGS +punishable +punishment/MS +punitive +punk +punky +punning +punster +punt/DGS +puny +pup/MS +pupa +pupal +pupate +pupil/MS +puppet/MS +puppeteer +puppy/MS +puppyish +purchasable +purchase/DGRSZ +purchaseable +pure/RTY +purgation +purgative +purgatory +purge/DGS +purify/DGNRSXZ +puritanic +purity +purl +purloin +purple/RT +purport/DGRSZ +purported/Y +purpose/DSVY +purposeful/Y +purr/DGS +purse/DRS +purslane +pursuant +pursue/DGRSZ +pursuit/MS +purvey +purveyor +purview +pus +push/DGRSZ +pushbutton +pushdown +puss +pussy +pussycat +put/S +putative +putt/GRZ +putter/G +putty +puzzle/DGJRSZ +puzzlement +pygmy/MS +pyknotic +pyracanth +pyramid/MS +pyramidal +pyre +pyridine +pyrite +pyroelectric +pyrolyse +pyrolysis +pyrometer +pyrometry +pyrophosphate +pyrotechnic +pyroxene +pyroxenite +python +q's +qua +quack/DS +quackery +quad +quadrangle +quadrangular +quadrant/MS +quadratic/S +quadratical/Y +quadrature/MS +quadrennial +quadric +quadriceps +quadrilateral +quadrille/N +quadripartite +quadrivium +quadruple/DGS +quadrupole +quaff +quagmire/MS +quahog +quail/MS +quaint/PY +quake/DGRSZ +qualify/DGNRSXZ +qualitative/Y +quality/MS +qualm +quandary/MS +quanta +quantifiable +quantify/DGNRSXZ +quantile +quantitative/Y +quantity/MS +quantization +quantize/DGS +quantum +quarantine/MS +quark +quarrel/DGS +quarrelsome +quarry/MS +quarryman +quarrymen +quart/RSZ +quarter/DGY +quarterback +quartermaster +quartet/MS +quartic +quartile +quartz +quartzite +quasar +quash/DGS +quasi +quasiparticle +quaternary +quatrain +quaver/DGS +quay +queasy +queen/MSY +queer/PRTY +quell/G +quench/DGS +querulous +query/DGS +quest/DGRSZ +question/DGJRSZ +questionable +questionably +questioning/Y +questionnaire/MS +quetzal +queue/DGRSZ +queueing +quibble +quick/NPRTXY +quicken/DG +quickie +quicklime +quicksand +quicksilver +quickstep +quiescent +quiet/DGPRSTY +quietude +quietus +quill +quillwort +quilt/DGS +quince +quinine +quint +quintet +quintic +quintillion +quintus +quip +quipping +quirk +quirky +quirt +quit/S +quite +quitter/MS +quitting +quiver/DGS +quixotic +quiz +quizzed +quizzes +quizzical +quizzing +quo/H +quod +quonset +quorum +quota/MS +quotation/MS +quote/DGS +quotient +r's +r/J +rabat +rabbet +rabbi +rabbit/MS +rabble +rabid +rabies +raccoon/MS +race/DGRSZ +racetrack +raceway +racial/Y +rack/DGS +racket/MS +racketeer/GS +rackety +racy +radar/MS +radial/Y +radian +radiance +radiant/Y +radiate/DGNSX +radiator/MS +radical/SY +radices +radii +radio/DGS +radioactive +radioastronomy +radiocarbon +radiochemical +radiochemistry +radiography +radiology +radiometer +radiometric +radiometry +radiophysics +radiosonde +radiosterilize +radiotherapy +radish/MS +radium +radius +radix +radon +raffia +raffish +raft/RSZ +rag/DGMS +rage/DGS +ragged/PY +ragging +ragout +ragweed +raid/DGRSZ +rail/DGRSZ +railbird +railhead +raillery +railroad/DGRSZ +railway/MS +raiment +rain/DGS +rainbow +raincoat/MS +raindrop/MS +rainfall +rainstorm +rainy/RT +raise/DGRSZ +raisin +raj +rajah +rake/DGS +rakish +rally/DGS +ram/MS +ramble/GJRS +ramification/M +ramify/NX +ramming +ramp/MS +rampage +rampant +rampart +ramrod +ran +ranch/DGRSZ +rancho +rancid +rancorous +random/PY +randy +rang/DGRZ +range/DGRSZ +rangeland +rangy +rank/DGJPRSTYZ +ranker/M +ranking/M +rankle +ransack/DGS +ransom/GRS +rant/DGRSZ +rap/DGMRS +rapacious +rape/DGRS +rapid/SY +rapidity +rapier +rapping +rapport +rapprochement +rapt/Y +rapture/MS +rapturous +rare/PRTY +rarefy +rarety/MS +rarity +rasa +rascal/SY +rash/PRY +rasp/DGS +raspberry +raster +rat/DGJMRSZ +rata +rate/DGJNRSXZ +rather +ratify/DGNS +ratio/MS +ratiocinate +rational/Y +rationale/MS +rationality/S +rationalize/DGS +rattail +rattle/DGRSZ +rattlesnake/MS +raucous +ravage/DGRSZ +rave/DGJS +ravel +raven/GS +ravenous/Y +ravine/MS +ravish +raw/PRTY +rawboned +rawhide +ray/MS +raze +razor/MS +razorback +rd +re/GJTY +reabbreviate/DGS +reach/DGRS +reachable +reachably +react/DGSV +reactant +reaction/MS +reactionary/MS +reactivate/DGNS +reactive/Y +reactivity +reactor/MS +read/GJRSZ +readability +readable +readily +readjusted +readout/MS +ready/DGPRST +reagent +real/PRSTY +realign/DGS +realisable +realism +realist/MS +realistic +realistically +reality/S +realizable +realizably +realization/MS +realize/DGS +realm/MS +realtor +realty +ream +reanalyze/GS +reap/DGRS +reappear/DGS +reappraisal/S +rear/DGS +rearrange/DGS +rearrangeable +rearrangement/MS +rearrest/D +reason/DGJRS +reasonable/P +reasonably +reassemble/DGS +reassessment/MS +reassign/DGS +reassignment/MS +reassure/DGS +reave +reawaken/DGS +reb +rebate/MS +rebel/MS +rebelled +rebelling +rebellion/MS +rebellious/PY +rebound/DGS +rebroadcast +rebuff/D +rebuild/GS +rebuilt +rebuke/DGS +rebut +rebuttal +rebutted +rebutting +recalcitrant +recalculate/DGNSX +recall/DGS +recant +recapitulate/DNS +recappable +recapture/DGS +recast/GS +recede/DGS +receipt/MS +receivable +receive/DGRSZ +recent/PY +receptacle/MS +reception/MS +receptive/PY +receptivity +receptor +recess/DSV +recession +recherche +recipe/MS +recipient/MS +reciprocal/Y +reciprocate/DGNS +reciprocity +recirculate/DGS +recital/MS +recitation/MS +recitative +recite/DGRS +reck +reckless/PY +reckon/DGJRS +reclaim/DGRSZ +reclaimable +reclamation/S +reclassify/DGNS +recline/G +recluse +recode/DGS +recognition/MS +recognizability +recognizable +recognizably +recognize/DGRSZ +recoil/DGS +recollect/DG +recollection/MS +recombine/DGS +recommend/DGRS +recommendation/MS +recompense +recompute/DGS +reconcile/DGRS +reconciliation +recondite +reconfigurable +reconfiguration/MS +reconfigure/DGRS +reconnaissance +reconnect/DGS +reconnection +reconsider/DGS +reconsideration +reconstruct/DGS +reconstruction +record/DGJRSZ +recount/DGS +recoup +recourse +recover/DGS +recoverable +recovery/MS +recreate/DGNSVX +recreational +recriminate +recruit/DGMRS +recta +rectangle/MS +rectangular +rectify/R +rectilinear +rectitude +rector/MS +rectory +rectum/MS +recumbent +recuperate +recur/S +recurred +recurrence/MS +recurrent/Y +recurring +recurs/DGSV +recurse/DGNSVX +recursion/M +recursive/Y +recusant +recuse +recyclable +recycle/DGS +red/PSY +redact +redactor +redbird +redbreast +redbud +redcoat +redden/D +redder +reddest +reddish/P +redeclare/DGS +redeem/DGRSZ +redefine/DGS +redefinition/MS +redemption +redemptive +redesign/DGS +redevelopment +redhead +redirecting +redirection/S +redisplay/DGS +redistribute/DGS +redneck +redone +redouble/D +redound +redpoll +redraw +redrawn +redress/DGS +redshank +redstart +redtop +reduce/DGRSZ +reducibility +reducible +reducibly +reduct +reduction/MS +redundancy/S +redundant/Y +redwood +reed/MS +reedbuck +reeducation +reedy +reef/RS +reek +reel/DGRS +reelect/DGS +reemphasize/DGS +reenforcement +reenter/DGS +reentrant +reestablish/DGS +reevaluate/DGNS +reeve +reexamine/DGS +refectory +refer/S +referee/DS +refereeing +reference/DGRS +referenda +referendum +referent/MS +referential/Y +referentiality +referral/MS +referred +referring +refill/DGS +refillable +refine/DGRS +refinement/MS +refinery +reflect/DGSV +reflectance +reflection/MS +reflective/Y +reflectivity +reflector/MS +reflex/MSV +reflexive/PY +reflexivity +reforestation +reform/DGRSZ +reformable +reformat/S +reformation +reformatory +reformatted +reformatting +reformulate/DGNS +refract +refractometer +refractory +refrain/DGS +refresh/DGRSZ +refreshing/Y +refreshment/MS +refrigerate +refrigerator/MS +refuel/DGS +refuge +refugee/MS +refusal +refuse/DGS +refutable +refutation +refute/DGRS +regain/DGS +regal/DY +regale/D +regalia +regard/DGS +regardless +regatta +regenerate/DGNSV +regent/MS +regime/MS +regimen +regiment/DS +regimentation +region/MS +regional/Y +register/DGS +registrable +registrant +registrar +registration/MS +registry +regress/DGSV +regression/MS +regret/S +regretful/Y +regrettable +regrettably +regretted +regretting +regroup/DG +regular/SY +regularity/S +regulate/DGNSVX +regulator/MS +regulatory +rehabilitate +rehearsal/MS +rehearse/DGRS +reign/DGS +reimbursable +reimburse/D +reimbursement/MS +rein/DS +reincarnate/DN +reindeer +reinforce/DGRS +reinforcement/MS +reinsert/DGS +reinstate/DGS +reinstatement +reinterpret/DGS +reintroduce/DGS +reinvent/DGS +reiterate/DGNS +reject/DGS +rejection/MS +rejector/MS +rejoice/DGRS +rejoin/DGS +rejoinder +relabel/DGS +relapse +relate/DGNRSVX +relational/Y +relationship/MS +relative/PSY +relativism +relativistic +relativistically +relativity +relax/DGRS +relaxation/MS +relay/DGS +releasable +release/DGS +relegate/DGS +relent/DGS +relentless/PY +relevance/S +relevant/Y +reliability +reliable/P +reliably +reliance +reliant +relic/MS +relict +relief +relieve/DGRSZ +religion/MS +religiosity +religious/PY +relinquish/DGS +reliquary +relish/DGS +relive/GS +reload/DGRS +relocate/DGNSX +reluctance +reluctant/Y +rely/DGS +remain/DGS +remainder/MS +reman +remand +remark/DGS +remarkable/P +remarkably +remediable +remedial +remedy/DGS +remember/DGS +remembrance/MS +remind/DGRSZ +reminisce +reminiscence/MS +reminiscent/Y +remiss +remission +remit +remittance +remitted +remitting +remnant/MS +remodel/DGS +remonstrate/DGNSV +remorse +remorseful +remote/PRTY +removable +removal/MS +remove/DGRS +remunerate +renaissance +renal +rename/DGS +rend/GRSZ +render/DGJ +rendezvous +rendition/MS +renegotiable +renew/DGRS +renewal +renounce/GS +renovate +renown/D +rent/DGS +rental/MS +renumber/GS +renunciate +reopen/DGS +reorder/DGS +reorganization/MS +reorganize/DGS +rep/Y +repaid +repair/DGRS +repairman +repairmen +reparation/MS +repartee +repast/MS +repay/DGS +repeal/DGRS +repeat/DGRSZ +repeatable +repeated/Y +repel/S +repelled +repellent +repelling +repent/DGS +repentance +repentant +repercussion/MS +repertoire +repertory +repetition/MS +repetitious +repetitive/PY +rephrase/DGS +repine +replace/DGRS +replaceable +replacement/MS +replay/DGS +replenish/DGS +replete/NP +replica +replicate/DGNSX +reply/DGNSX +report/DGRSZ +reported/Y +reportorial +repose/DGS +reposition/DGS +repository/MS +reprehensible +represent/DGS +representable +representably +representation/MS +representational/Y +representative/PSY +repress/DGSV +repression/MS +reprieve/DGS +reprimand +reprint/DGS +reprisal/MS +reprise +reproach/DGS +reproduce/DGRSZ +reproducibility/S +reproducible +reproducibly +reproduction/MS +reprogram/S +reprogrammed +reprogramming +reproof +reprove/R +reptile/MS +reptilian +republic/MS +republican/MS +repudiate/DGNSX +repugnant +repulse/DGNSVX +reputable +reputably +reputation/MS +repute/DS +reputed/Y +request/DGRSZ +require/DGS +requirement/MS +requisite/NSX +requisition/DG +requited +reread +reredos +reroute/DGS +rescind +rescue/DGRSZ +research/DGRSZ +reselect/DGS +resemblance/MS +resemblant +resemble/DGS +resent/DGS +resentful/Y +resentment +reserpine +reservation/MS +reserve/DGRS +reservoir/MS +reset/S +resetting/S +reside/DGS +residence/MS +resident/MS +residential/Y +residual +residuary +residue/MS +residuum +resign/DGS +resignation/MS +resilient +resin/MS +resinlike +resiny +resist/DGSV +resistable +resistably +resistance/S +resistant/Y +resistible +resistivity +resistor/MS +resolute/NPXY +resolvable +resolve/DGRSZ +resonance/S +resonant +resonate +resorcinol +resort/DGS +resound/GS +resource/MS +resourceful/PY +respect/DGRSV +respectability +respectable +respectably +respectful/PY +respective/Y +respiration +respirator +respiratory +respire +respite +resplendent/Y +respond/DGRS +respondent/MS +response/SV +responsibility/S +responsible/P +responsibly +responsive/PY +rest/DGSV +restart/DGS +restate/DGS +restatement +restaurant/MS +restaurateur +restful/PY +restitution +restless/PY +restoration/MS +restorative +restore/DGRSZ +restrain/DGRSZ +restraint/MS +restrict/DGSV +restriction/MS +restrictive/Y +restroom +restructure/DGS +result/DGS +resultant/SY +resumable +resume/DGS +resumption/MS +resurgent +resurrect/DGS +resurrection/MS +resurrector/S +resuscitate +ret +retail/GRZ +retain/DGRSZ +retainment +retaliate/N +retaliatory +retard/DGR +retardant +retardation +retch +retention/S +retentive/PY +reticent +reticle/MS +reticular +reticulate/DGNSY +reticulum +retina/MS +retinal +retinue +retire/DGS +retiree +retirement/MS +retort/DS +retrace/DG +retract/DGS +retraction/S +retrain/DGS +retransmission/MS +retransmit/S +retransmitted +retransmitting +retreat/DGS +retribution +retrievable +retrieval/MS +retrieve/DGRSZ +retroactive/Y +retrofit +retrofitting +retrograde +retrogress/V +retrorocket +retrospect/V +retrospection +retrovision +retry/DGRSZ +return/DGRS +returnable +retype/DGS +reunion/MS +reunite/DG +reusable +reuse/DGS +revamp/DGS +reveal/DGS +revel/DGRS +revelation/MS +revelatory +revelry +revenge/R +revenue/SZ +rever/DG +reverberate +revere/DGS +reverence +reverend/MS +reverent/Y +reverie +reverify/DGS +reversal/MS +reverse/DGNRSY +reversible +revert/DGSV +revery +revet +review/DGRSZ +revile/DGR +revisable +revisal +revise/DGNRSX +revision/M +revisionary +revisit/DGS +revival/MS +revive/DGRS +revocable +revocation +revoke/DGRS +revolt/DGRS +revolting/Y +revolution/MS +revolutionary/MS +revolutionize/DR +revolve/DGRSZ +revulsion +revved +revving +reward/DGS +rewarding/Y +rewind/GS +rework/DGS +rewound +rewrite/GS +rewritten +rhapsodic +rhapsody +rhenium +rheology +rheostat +rhesus +rhetoric +rhetorician +rheum +rheumatic +rheumatism +rhinestone +rhino +rhinoceros +rho +rhodium +rhododendron +rhodolite +rhodonite +rhombi +rhombic +rhombus +rhubarb +rhyme/DGS +rhythm/MS +rhythmic +rhythmically +rib/MS +ribald +ribbed +ribbing +ribbon/MS +riboflavin +ribonucleic +rice +rich/PRSTY +rick +rickets +rickety +rickshaw/MS +ricochet +rid/GRZ +riddance +ridden +ridding +riddle/DGS +ride/GRSZ +ridge/MS +ridgepole +ridicule/DGS +ridiculous/PY +riffle +rifle/DGRS +rifleman +riflemen +rift +rig/MS +rigging +right/DGPRSY +righteous/PY +rightful/PY +rightmost +rightward +rigid/Y +rigidity +rigor/S +rigorous/Y +rill +rilly +rim/MS +rime +rimming +rimy +rind/MS +ring/DGJRZ +ringing/Y +ringlet +ringside +rink +rinse/DGRS +riot/DGRSZ +riotous +rip/NS +riparian +ripe/PY +ripoff +ripped +ripping +ripple/DGS +rise/GJRSZ +risen +risible +risk/DGS +risky +rite/MS +ritual/SY +rival/DS +rivalled +rivalling +rivalry/MS +riven +river/MS +riverbank +riverfront +riverine +riverside +rivet/RS +rivulet/MS +roach +road/MS +roadbed +roadblock +roadhouse +roadside +roadster/MS +roadway/MS +roam/DGS +roar/DGRS +roast/DGRS +rob/DGS +robbed +robber/MS +robbery/MS +robbin +robbing +robe/DGS +robin/MS +robot/MS +robotic/S +robust/PY +rock/DGRSZ +rockabye +rockaway +rockbound +rocket/DGS +rocklike +rocky/S +rococo +rod/MS +rode +rodent +rodeo +roe +roebuck +rogue/MS +roil +roister +role/MS +roll/DGRSZ +rollback +rollick +romance/GRSZ +romantic/MS +romp/DGRS +rondo +rood +roof/DGRS +rooftop +rooftree +rook +rookie +rooky +room/DGRSZ +roomful +roommate +roomy +roost/RZ +root/DGMRS +rope/DGRSZ +rosary +rose/MS +rosebud/MS +rosebush +rosemary +rosette +roster +rostrum +rosy/P +rot/S +rotary +rotate/DGNSX +rotator +rotenone +rotogravure +rotor +rototill +rotten/P +rotting +rotund +rotunda +rouge +rough/DNPRTY +roughcast +roughish +roughneck +roughshod +roulette +round/DGPRSTY +roundabout +rounded/P +roundhead +roundhouse +roundoff +roundtable +roundup +roundworm +rouse/DGS +roustabout +rout/DGJRZ +route/DGJRSZ +routine/SY +rove/DGRS +row/DGRS +rowboat +rowdy +royal/Y +royalist/MS +royalty/MS +rub/SX +rubbed +rubber/MS +rubbery +rubbing +rubbish +rubble +rubdown +rubicund +rubidium +ruble/MS +rubout +rubric +ruby/MS +ruckus +rudder/MS +ruddy/P +rude/PY +rudiment/MS +rudimentary +rue +rueful/Y +ruffian/SY +ruffle/DS +rufous +rug/MS +rugged/PY +ruin/DGS +ruination/MS +ruinous/Y +rule/DGJRSZ +rum/N +rumble/DGRS +ruminant +rummage +rummy +rumor/DS +rump/Y +rumple/D +rumpus +run/S +runabout +runaway +rundown +rune +rung/MS +runic +runner/MS +runneth +running +runoff +runt +runtime +runty +runway +rupee +rupture/DGS +rural/Y +ruse +rush/DGRS +rusk +russet +russian/MS +russula +rust/DGS +rustic +rusticate/DGNS +rustle/DGRZ +rustproof +rusty/N +rut/MS +rutabaga +ruthenium +ruthless/PY +rutile +rutting +rutty +rye +s's +s/J +sa +sabbath +sabbatical +saber/MS +sable/MS +sabotage +sabra +sac +sachem +sack/GRS +sacral +sacrament +sacred/PY +sacrifice/DGRSZ +sacrificial/Y +sacrilege +sacrilegious +sacrosanct +sad/PY +sadden/DS +sadder +saddest +saddle/DS +saddlebag +sadism +sadist/MS +sadistic +sadistically +safari +safe/PRSTY +safeguard/DGS +safekeeping +safety/S +saffron +sag/S +saga +sagacious +sagacity +sage/SY +sagebrush +sagging +sagittal +sago +saguaro +said +sail/DGS +sailboat +sailfish +sailor/SY +saint/DSY +sainthood +sake/S +salable +salacious +salad/MS +salamander +salami +salary/DS +sale/MS +salesgirl +saleslady +salesman +salesmen +salesperson +salient +saline +saliva +salivary +salivate +sallow +sally/GS +salmon +salmonberry +salon/MS +saloon/MS +saloonkeep +salsify +salt/DGRSZ +saltbush +saltwater +salty/PRT +salubrious +salutary +salutation/MS +salute/DGS +salvage/DGRS +salvageable +salvation +salve/RS +salvo +samarium +samba +same/P +samovar +sample/DGJRSZ +sanatoria +sanatorium +sanctify/DN +sanctimonious +sanction/DGS +sanctity +sanctuary/MS +sand/DGRSZ +sandal/MS +sandalwood +sandbag +sandblast +sanderling +sandhill +sandman +sandpaper +sandpile +sandpiper +sandstone +sandwich/S +sandy +sane/RTY +sang +sangaree +sanguinary +sanguine +sanguineous +sanicle +sanitarium +sanitary +sanitate/N +sanity +sank +sans +sap/MS +sapiens +sapient +sapling/MS +saponify +sapphire +sapping +sappy +sapsucker +sarcasm/MS +sarcastic +sarcoma +sardine +sardonic +sari +sarsaparilla +sarsparilla +sash +sashay +sassafras +sat/DG +satan +satanic +satchel/MS +sate/DGS +satellite/MS +satiable +satiate +satiety +satin +satire/MS +satiric +satisfaction/MS +satisfactorily +satisfactory +satisfiability +satisfiable +satisfy/DGS +saturable +saturate/DGNRS +saturday/MS +saturnine +satyr +sauce/RSZ +saucepan/MS +saucy +sauerkraut +saunter +sausage/MS +saute +sauterne +savage/DGPRSYZ +savagery +savant +save/DGJRSZ +savior/MS +savor/DGS +savory +savoy +savvy +saw/DGS +sawbelly +sawdust +sawfish +sawfly +sawmill/MS +sawtimber +sawtooth +sawyer +sax +saxifrage +saxophone +say/GJRSZ +scab +scabbard/MS +scabious +scabrous +scaffold/GJS +scalable +scalar/MS +scald/DG +scale/DGJS +scallop/DS +scalp/MS +scaly +scamp/RZ +scamper/G +scan/S +scandal/MS +scandalous +scandium +scanned +scanner/MS +scanning +scant/Y +scantily +scanty/PRT +scapegoat +scapula +scapular +scar/DGMS +scarce/PY +scarcity +scare/DGS +scarecrow +scarf +scarface +scarify +scarlet +scarves +scary +scat +scathe +scatter/DGS +scatterbrain +scattergun +scatting +scaup +scavenge +scenario/MS +scene/MS +scenery +scenic +scent/DS +scepter/MS +sceptic +schedule/DGRSZ +schelling +schema/MS +schemata +schematic +schematically +scheme/DGMRSZ +scherzo +schism +schist +schizoid +schizomycetes +schizophrenia +schizophrenic +schlieren +schnapps +scholar/SY +scholarship/MS +scholastic/S +scholastically +school/DGRSZ +schoolbook +schoolboy/MS +schoolgirl +schoolgirlish +schoolhouse/MS +schoolmarm +schoolmaster/MS +schoolmate +schoolroom/MS +schoolteacher +schoolwork +schooner +sciatica +science/MS +scientific +scientifically +scientist/MS +scimitar +scintillate +scion +scissor/DGS +sclerosis +sclerotic +scoff/DGRS +scold/DGS +scoop/DGS +scoot +scope/DGS +scopic +scops +scorch/DGRS +score/DGJRSZ +scoreboard +scorecard +scoria +scorn/DGRS +scornful/Y +scorpion/MS +scotch +scotland +scoundrel/MS +scour/DGS +scourge +scout/DGS +scow +scowl/DGS +scrabble +scraggly +scram +scramble/DGRS +scramming +scrap/DGJMRSZ +scrapbook +scrape/DGJRSZ +scrapped +scrapping +scratch/DGRSZ +scratchpad/MS +scratchy +scrawl/DGS +scrawny +scream/DGRSZ +screech/DGS +screechy +screed +screen/DGJS +screenplay +screw/DGS +screwball +screwbean +screwdriver +screwworm +scribble/DRS +scribe/GS +scrim +scrimmage +script/MS +scription +scriptural +scripture/S +scriven +scroll/DGS +scrooge +scrounge +scrub +scrubbing +scrumptious +scruple +scrupulosity +scrupulous/Y +scrutable +scrutinize/DG +scrutiny +scuba +scud +scudding +scuff +scuffle/DGS +scull +sculpin +sculpt/DS +sculptor/MS +sculptural +sculpture/DS +scum +scurrilous +scurry/D +scurvy +scuttle/DGS +scutum +scythe/MS +sea/SY +seaboard +seacoast/MS +seafare +seafood +seagull +seahorse +seal/DGRS +sealant +sealevel +seam/DGNS +seaman +seamy +seance +seaport/MS +seaquake +sear/DGS +search/DGJRSZ +searching/Y +searchlight +searing/Y +seashore/MS +seaside +season/DGJRSZ +seasonable +seasonably +seasonal/Y +seat/DGRS +seaward +seaweed +sec +secant +secede/DGS +secession +seclude/D +seclusion +second/DGRSYZ +secondarily +secondary +secondhand +secrecy +secret/DGSVY +secretarial +secretariat +secretary/MS +secrete/DGNSVX +secretive/Y +sect/MS +sectarian +section/DGS +sectional +sector/MS +secular +secure/DGJSY +security/S +sedan +sedate +sedentary +seder +sedge +sediment/MS +sedimentary +sedimentation +sedition +seditious +seduce/DGRSZ +seduction +seductive +sedulous +see/DRSZ +seeable +seed/DGJRSZ +seedbed +seedling/MS +seedy +seeing +seek/GRSZ +seem/DGSY +seeming/Y +seen +seep/DGS +seepage +seersucker +seethe/DGS +segment/DGS +segmentation/MS +segregant +segregate/DGNS +seismic +seismograph +seismography +seismology +seize/DGS +seizure/MS +seldom +select/DGSV +selection/MS +selective/Y +selectivity +selectman +selectmen +selector/MS +selenate +selenite +selenium +self +selfish/PY +selfsame +sell/GRSZ +sellout +seltzer +selves +semantic/S +semantical/Y +semanticist/MS +semaphore/MS +semblance +semester/MS +semi +semiautomated +semicolon/MS +semiconductor/MS +seminal +seminar/MS +seminarian +seminary/MS +semipermanent/Y +semper +sen +senate/MS +senator/MS +senatorial +send/GRSZ +senile +senior/MS +seniority +senor +senorita +sensate/NX +sensation/M +sensational/Y +sense/DGS +senseless/PY +sensibility/S +sensible +sensibly +sensitive/PSY +sensitivity/S +sensor/MS +sensory +sensual +sensuous +sent +sentence/DGS +sentential +sentient +sentiment/MS +sentimental/Y +sentinel/MS +sentry/MS +sepal +separable +separate/DGNPSXY +separator/MS +sepia +sept +septa +septate +september +septennial +septic +septillion +septuagenarian +septum +sepuchral +sepulcher/MS +sepulchral +sequel/MS +sequence/DGJRSZ +sequent +sequential/Y +sequentiality +sequentialize/DGS +sequester +sequestration +sequin +sequitur +sera +seraglio +serape +seraphim +serenade +serendipitous +serendipity +serene/Y +serenity +serf/MS +serge +sergeant/MS +serial/SY +serialization/MS +serialize/DGS +seriatim +series +serif +serious/PY +sermon/MS +serology +serpent/MS +serpentine +serum/MS +servant/MS +serve/DGJRSZ +service/DGS +serviceable +serviceberry +serviceman +servicemen +serviette +servile +servitor +servitude +servo +servomechanism +sesame +session/MS +set/MS +setback +setscrew +setter/MS +setting/S +settle/DGRSZ +settlement/MS +setup/S +seven/HS +sevenfold +seventeen/HS +seventieth +seventy/S +sever/DGRST +several/Y +severalfold +severalty +severance +severe/DGRTY +severity/MS +sew/DGRSZ +sewage +sewerage +sewn +sex/DS +sextet +sextillion +sexton +sextuple +sextuplet +sexual/Y +sexuality +sexy +sforzando +shabby +shack/DS +shackle/DGS +shad/DGJ +shadbush +shade/DGJS +shadflower +shadily +shadow/DGS +shadowy +shady/PRT +shaft/MS +shag +shagbark +shagging +shaggy +shah +shakable +shakably +shake/GRSZ +shakeable +shakedown +shaken +shako +shaky/P +shale +shall +shallot +shallow/PRY +shalom +sham/DGMS +shamble/S +shame/DGS +shameface +shameful/Y +shameless/Y +shampoo +shamrock +shan't +shank +shanty/MS +shape/DGRSYZ +shapeless/PY +sharable +shard +share/DGRSZ +shareable +sharecrop +sharecropper/MS +shareholder/MS +shark/MS +sharp/NPRTXY +sharpen/DG +sharpshoot +shatter/DGS +shatterproof +shave/DGJS +shaven +shaw +shawl/MS +shay +she'd +she'll +she/DMS +sheaf +shear/DGRS +sheath/G +sheathe/G +sheaths +sheave/S +shed/S +shedding +sheen +sheep +sheepskin +sheer/D +sheet/DGS +sheik +shelf +shell/DGRS +shelter/DGS +shelve/DGS +shenanigan +shepherd/MS +sherbet +sheriff/MS +sherry +shibboleth +shield/DGS +shift/DGRSZ +shiftily +shifty/PRT +shill/GJ +shim +shimmer/G +shimming +shimmy +shin/DGRZ +shinbone +shine/DGRSZ +shingle/MS +shining/Y +shiny +ship/MS +shipboard +shipbuild/G +shiplap +shipman +shipmate +shipmen +shipment/MS +shipped +shipper/MS +shipping +shipshape +shipwreck/DS +shipyard +shire +shirk/GRS +shirt/GS +shirtmake +shish +shitepoke +shiv/RZ +shiver/DGR +shivery +shoal/MS +shock/DGRSZ +shocking/Y +shod +shoddy +shoe/DS +shoehorn +shoeing +shoelace +shoemake/R +shoestring +shoji +shone +shoo +shoofly +shook +shoot/GJRSZ +shop/MS +shopkeep/RZ +shopkeeper/M +shopped +shopper/MS +shopping +shopworn +shore/MS +shoreline +shorn +short/DGNPRSTXY +shortage/MS +shortcoming/MS +shortcut/MS +shorten/DG +shortfall +shorthand/D +shortish +shortsighted +shortstop +shot/MS +shotbush +shotgun/MS +should/RZ +shoulder/DG +shouldn't +shout/DGRSZ +shove/DGS +shovel/DS +show/DGJRSZ +showboat +showcase +showdown +shower/DG +showman +showmen +shown +showpiece +showplace +showroom +showy +shrank +shrapnel +shred/MS +shredding +shrew/MS +shrewd/PTY +shrewish +shriek/DGS +shrift +shrike +shrill/DGP +shrilly +shrimp +shrine/MS +shrink/GS +shrinkable +shrinkage +shrive +shrivel/D +shroud/D +shrove +shrub/MS +shrubbery +shrug/S +shrugging +shrunk/N +shuck +shudder/DGS +shuddery +shuffle/DGS +shuffleboard +shun/S +shunning +shunt +shut/S +shutdown/MS +shutoff +shutout +shutter/DS +shutting +shuttle/DGS +shuttlecock +shy/DSY +shyness +sial +sib +sibilant +sibling/MS +sibyl +sic +sick/NPRTY +sickish +sickle +sicklewort +sickness/MS +sickroom +side/DGJS +sidearm +sideband +sideboard/MS +sideburn/MS +sidecar +sidelight/MS +sideline +sidelong +sideman +sidemen +sidereal +siderite +sidesaddle +sideshow +sidestep +sidestepping +sidetrack +sidewalk/MS +sidewall +sideway/S +sidewinder +sidewise +sidle +siege/MS +sienna +sierra +siesta +sieve/MS +sift/DGR +sigh/DG +sighs +sight/DGJSY +sightsee/R +sightseeing +sigma +sign/DGRSZ +signal/DGSY +signalled +signalling +signature/MS +signboard +signet +significance +significant/SY +signify/DGNS +signor +signpost +sikkim +silage +silane +silence/DGRSZ +silent/Y +silhouette/DS +silica +silicate +siliceous +silicic +silicide +silicon +silicone +silk/NS +silkily +silkine +silkworm +silky/RT +sill/MS +silly/PT +silo +silt/DGS +siltation +siltstone +silty +silver/DGS +silversmith +silverware +silvery +sima +similar/Y +similarity/S +simile +similitude +simmer/DGS +simper +simple/PRT +simplectic +simpleminded +simpleton +simplex +simplicity/MS +simplify/DGNRSXZ +simplistic +simply +simulate/DGNSX +simulator/MS +simulcast +simultaneity +simultaneous/Y +sin/MS +since +sincere/TY +sincerity +sine/S +sinew/MS +sinewy +sinful/PY +sing/DGRSYZ +singable +singe/DGRZ +singing/Y +single/DGPS +singlehanded +singlet +singleton/MS +singsong +singular/Y +singularity/MS +sinh +sinister +sinistral +sink/DGRSZ +sinkhole +sinned +sinner/MS +sinning +sinter +sinuous +sinus +sinusoid/S +sinusoidal +sip/S +sipping +sir/DNSX +sire/DS +sirup +sis +sisal +siskin +sister/SY +sit/DGS +site/DGS +sitter/MS +sitting/S +situ/S +situate/DGNSX +situational/Y +siva +six/HS +sixfold +sixgun +sixpence +sixteen/HS +sixtieth +sixty/S +sizable +size/DGJS +sizzle +skat/DGRZ +skate/DGRSZ +skeet +skeletal +skeleton/MS +skeptic/MS +skeptical/Y +sketch/DGS +sketchbook +sketchily +sketchpad +sketchy +skew/DGRSZ +ski/DGS +skid +skidding +skiddy +skiff +skill/DS +skillet +skillful/PY +skim/MS +skimming +skimp/DGS +skimpy +skin/MS +skindive +skinned +skinner/MS +skinning +skinny +skip/S +skipjack +skipped +skipper/MS +skipping +skirmish/DGRSZ +skirt/DGS +skit +skittle +skulk/DGRS +skull/MS +skullcap +skullduggery +skunk/MS +sky/DMS +skyhook +skyjack +skylark/GS +skylight/MS +skyline +skyrocket +skyscrape/RZ +skyscraper/M +skyward +skywave +skyway +slab +slack/GNPRSY +sladang +slag +slain +slake +slam/S +slammed +slamming +slander/RS +slanderous +slang +slant/DGS +slap/S +slapped +slapping +slapstick +slash/DGS +slat/DMRS +slate/DRS +slatting +slaughter/DGS +slaughterhouse +slave/RS +slavery +slavish +slay/GRSZ +sled/MS +sledding +sledge/MS +sledgehammer +sleek +sleep/GRSZ +sleepily +sleepless/PY +sleepwalk +sleepy/P +sleet +sleety +sleeve/MS +sleigh +sleighs +sleight +slender/R +slept +sleuth +slew/G +slice/DGRSZ +slick/RSZ +slid/GRZ +slide/GRSZ +slight/DGPRSTY +slim/DY +slime/D +slimy +sling/GS +slingshot +slip/MS +slippage +slipped +slipper/MS +slippery/P +slipping +slit/MS +slither +slitting +sliver +slivery +slob +sloe +slog +slogan/MS +sloganeer +slogging +sloop +slop/DGRSZ +slope/DGRSZ +slopped +slopping +sloppy/P +slosh +slot/MS +sloth +slothful +sloths +slotted +slouch/DGS +slough +sloven +slow/DGPRSTY +slowdown +sludge +slug/S +slugging +sluggish/PY +sluice +slum/MS +slumber/D +slumming +slump/DS +slung +slur/MS +slurp +slurring +slurry +sly/Y +smack/DGS +small/PRT +smallish +smallpox +smalltime +smart/DPRTY +smash/DGRSZ +smashing/Y +smattering +smear/DGS +smell/DGS +smelly +smelt/RS +smile/DGS +smiling/Y +smirk +smite +smith +smithereens +smiths +smithy +smitten +smock/GS +smog +smokable +smoke/DGRSZ +smokehouse +smokescreen +smokestack +smoky/S +smolder/DGS +smooch +smooth/DGPRSTY +smoothbore +smote +smother/DGS +smudge +smudgy +smug +smuggle/DGRSZ +smut +smutty +snack +snafu +snag +snagging +snail/MS +snake/DS +snakebird +snakelike +snakeroot +snap/S +snapback +snapdragon +snapped +snapper/MS +snappily +snapping +snappish +snappy +snapshot/MS +snare/DGS +snark +snarl/DG +snatch/DGS +snazzy +sneak/DGRSZ +sneakily +sneaky/PRT +sneer/DGS +sneeze/DGS +snell +snick +sniff/DGSY +sniffle +snifter +snigger +snip +snipe +snippet +snippy +snivel +snob +snobbery +snobbish +snook +snoop/DGS +snoopy +snore/DGS +snorkel +snort/DGS +snotty +snout/MS +snow/DGS +snowball +snowfall +snowflake +snowily +snowman +snowmen +snowshoe/MS +snowstorm +snowy/RT +snub +snubbing +snuff/DGRSY +snuffle +snug/PY +snuggle/DGS +snuggly +snyaptic +so +soak/DGS +soap/DGS +soapstone +soapsud +soapy +soar/DGS +sob/RSZ +sobbing +sober/DGPY +sobriety +sobriquet +soccer +sociability +sociable +sociably +social/Y +socialism +socialist/MS +socialize/DGS +societal +society/MS +socioeconomic +sociological/Y +sociology +sociometric +sociometry +sock/DGS +socket/MS +sockeye +sod/MS +soda +sodden +sodding +sodium +sodomy +sofa/MS +soffit +soft/NPRTXY +softball +soften/DG +software/MS +softwood +soggy +soignee +soil/DGS +soiree +sojourn/RZ +solace/D +solar +sold/R +soldier/GSY +soldiery +sole/SY +solecism +solemn/PY +solemnity +solenoid +solicit/DGS +solicitation +solicitor +solicitous +solicitude +solid/PSY +solidarity +solidify/DGNS +solidity +soliloquy +solipsism +solitaire +solitary +solitude/MS +solo/MS +solstice +solubility +soluble +solute/NX +solution/M +solvable +solvate +solve/DGRSZ +solvent/MS +soma +somal +somatic +somber/Y +sombre +some +somebody +somebody'll +someday +somehow +someone'll +someone/M +someplace +somersault +something +sometime/S +somewhat +somewhere +sommelier +somnolent +son/MS +sonar +sonata +song/MS +songbag +songbook +songful +sonic +sonnet/MS +sonny +sonority +sonorous +soon/RT +soot +sooth/DGRS +soothe/DGRS +soothsay/R +sop +sophia +sophism +sophisticate/DN +sophistry +sophomore/MS +sophomoric +sopping +soprano +sora +sorb +sorcerer/MS +sorcery +sordid/PY +sore/PRSTY +sorghum +sorority +sorption +sorrel +sorrow/MS +sorrowful/Y +sorry/RT +sort/DGRSZ +sortie +sou/H +souffle +sought +soul/MS +soulful +sound/DGJPRSTY +sounding/M +soundproof +soup/MS +sour/DGPRSTY +sourberry +source/MS +sourdough +sourwood +soutane +southbound +southeast +southeastern +southern/RZ +southernmost +southland +southpaw +southward +southwest +southwestern +souvenir +sovereign/MS +sovereignty +soviet/MS +sovkhoz +sow +sowbelly +sown +soy +soya +soybean +spa +space/DGJRSZ +spacecraft +spaceship/MS +spacesuit +spacious +spade/DGS +spaghetti +spain +spalding +span/MS +spandrel +spangle +spaniel +spanish +spank/DGS +spanking/Y +spanned +spanner/MS +spanning +spar/DGRT +spare/DGPRSTY +sparge +sparing/Y +spark/DGS +sparkle +sparky +sparling +sparring +sparrow/MS +sparse/PRTY +spasm +spastic +spat +spate/MS +spatial/Y +spatlum +spatter/D +spatterdock +spatula +spavin +spawn/DGS +spay/D +speak/GRSZ +speakable +speakeasy +spear/DS +spearhead +spearmint +spec +special/SY +specialist/MS +specialization/MS +specialize/DGS +specialty/MS +specie/S +specifiable +specific/S +specifically +specificity +specify/DGNRSXZ +specimen/MS +specious +speck/MS +speckle/DS +spectacle/DS +spectacular/Y +spectator/MS +specter/MS +spectra +spectral +spectrogram/MS +spectrograph +spectrography +spectrometer +spectrometric +spectrometry +spectrophotometer +spectrophotometric +spectrophotometry +spectroscope +spectroscopic +spectroscopy +spectrum +specular +speculate/DGNSVX +speculator/MS +sped +speech/MS +speechless/P +speed/DGRSZ +speedboat +speedily +speedometer +speedup/MS +speedwell +speedy +spell/DGJRSZ +spellbound +spencer +spend/GRSZ +spent +sperm +spermatophyte +spew +sphagnum +sphalerite +sphere/MS +spheric +spherical/Y +spheroid +spheroidal +spherule +sphinx +spice/DS +spicebush +spicy/P +spider/MS +spiderwort +spidery +spigot +spike/DS +spikenard +spiky +spill/DGRS +spilt +spin/S +spinach +spinal/Y +spindle/G +spine +spinnaker +spinner/MS +spinneret +spinning +spinodal +spinoff +spinster +spiny +spiral/DGY +spire/MS +spirit/DGS +spirited/Y +spiritual/SY +spit/DGS +spite/DGS +spiteful/PY +spitfire +spitting +spittle +spitz +splash/DGS +splashy +splat +splay/D +spleen +spleenwort +splendid/Y +splendor +splenetic +splice/DGJRSZ +spline/MS +splint/RZ +splinter/D +splintery +split/MS +splitter/MS +splitting +splotch +splotchy +splurge +splutter +spoil/DGRSZ +spoilage +spoke/DS +spoken +spokesman +spokesmen +sponge/DGRSZ +spongy +sponsor/DGS +sponsorship +spontaneity +spontaneous/Y +spoof +spook +spooky +spool/DGRS +spoon/DGS +spoonful +sporadic +spore/MS +sport/DGSV +sporting/Y +sportsman +sportsmen +sportswear +sportswriter +sportswriting +sporty +spot/MS +spotless/Y +spotlight +spotted +spotter/MS +spotting +spotty +spouse/MS +spout/DGS +sprain +sprang +sprawl/DGS +spray/DGRS +spread/GJRSZ +spree/MS +sprig +sprightly +spring/GRSZ +springboard +springe/GRZ +springtail +springtime +springy/PRT +sprinkle/DGRS +sprint/DGRSZ +sprite +sprocket +sprout/DG +spruce/D +sprue +sprung +spud +spume +spumoni +spun +spunk +spur/MS +spurge +spurious +spurn/DGS +spurring +spurt/DGS +sputnik +sputter/D +spy/GS +spyglass +squabble/DGS +squad/MS +squadron/MS +squalid +squall/MS +squamous +squander +square/DGPRSTY +squash/DG +squashberry +squashy +squat/S +squatting +squaw +squawbush +squawk/DGS +squawroot +squeak/DGS +squeaky +squeal/DGS +squeamish +squeegee +squeeze/DGRS +squelch +squid +squill +squint/DG +squire/MS +squirehood +squirm/DS +squirmy +squirrel/DGS +squirt +squishy +st +stab/SY +stabbed +stabbing +stabile +stability/MS +stabilize/DGRSZ +stable/DGRS +stableman +stablemen +staccato +stack/DGMS +stadia +stadium +staff/DGRSZ +stag/DGMRSZ +stage/DGRSZ +stagecoach +stagger/DGS +stagnant +stagnate +stagy +staid +stain/DGS +stainless +stair/MS +staircase/MS +stairway/MS +stairwell +stake/DS +stalactite +stale +stalemate +stalk/DG +stall/DGJS +stallion +stalwart/Y +stamen/MS +stamina +staminate +stammer/DGRS +stamp/DGRSZ +stamped/DG +stampede/DGS +stance +stanch/T +stanchion +stand/GJS +standard/SY +standardization +standardize/DGS +standby +standeth +standoff +standpoint/MS +standstill +stank +stannic +stannous +stanza/MS +staph +staphylococcus +staple/GRS +star/DGMRS +starboard +starch/D +starchy +stardom +stare/DGRS +starfish +stargaze +stark/Y +starlet +starlight +starling +starred +starring +starry +start/DGRSZ +startle/DGS +startup/MS +starvation +starve/DGS +stash +stasis +state/DGMNRSXY +statement/MS +stateroom +statesman +statesmanlike +statesmen +statewide +static +statically +station/DGR +stationarity +stationary +stationery +stationmaster +statistic/S +statistical/Y +statistician/MS +stator +statuary +statue/MS +statuesque/PY +statuette +stature +status/S +statute/MS +statutorily +statutory/P +staunch/TY +stave/DS +stay/DGS +stead +steadfast/PY +steadily +steady/DGPRST +steak/MS +steal/GHRS +stealthily +stealthy +steam/DGRSZ +steamboat/MS +steamship/MS +steamy +steed +steel/DGSZ +steelmake +steely +steep/DGNPRSTY +steeple/MS +steeplebush +steer/DGS +steeve +stein +stella +stellar +stem/MS +stemmed +stemming +stench/MS +stencil/MS +stenographer/MS +stenography +stenotype +step/MS +stepchild +stephanotis +stepmother/MS +steppe/DG +steprelation +stepson +stepwise +steradian +stereo/MS +stereography +stereoscopy +stereotype/DS +stereotypical +sterile +sterilization/MS +sterilize/DGRS +sterling +stern/PSY +sternal +sternum +steroid +stethoscope +stevedore +stew/DS +steward/MS +stewardess +stick/GNRSZ +stickily +stickle +stickleback +stickpin +sticktight +sticky/PRT +stiff/NPRSTXY +stifle/DGS +stigma +stigmata +stile/MS +stiletto +still/DGPRST +stillbirth +stillwater +stilt +stimulant/MS +stimulate/DGNSVX +stimulatory +stimuli +stimulus +sting/GS +stingy +stink/GRSZ +stinkpot +stinky +stint +stipend/MS +stipple +stipulate/DGNSX +stir/S +stirred +stirrer/MS +stirring/SY +stirrup +stitch/DGS +stochastic +stochastically +stock/DGJRSZ +stockade/MS +stockbroker +stockholder/MS +stockpile +stockroom +stocky +stodgy +stoic +stoichiometric +stoichiometry +stoke +stole/MS +stolen +stolid +stomach/DGRS +stomp +stone/DGS +stonecrop +stonewall +stoneware +stonewort +stony +stood +stooge +stool +stoop/DGS +stop/S +stopband +stopcock/S +stopgap +stopover +stoppable +stoppage +stopped +stopper/MS +stopping +stopwatch +storage/MS +store/DGS +storehouse/MS +storekeep +storeroom +stork/MS +storm/DGS +stormbound +stormy/PRT +story/DS +storyboard +storyteller +stout/PRTY +stove/MS +stow/D +stowage +strabismic +strabismus +straddle +strafe +straggle/DGRSZ +straight/NPRTX +straightaway +straightforward/PY +straightway +strain/DGRSZ +strait/NS +strand/DGS +strange/PRTYZ +strangle/DGJRSZ +strangulate/NX +strangulation/M +strap/MS +strapping +strata +stratagem/MS +strategic +strategist +strategy/MS +stratify/DNSX +stratosphere +stratospheric +stratum +straw/MS +strawberry/MS +strawflower +stray/DS +streak/DS +stream/DGRSZ +streamline/DGRS +streamside +street/SZ +streetcar/MS +strength/NX +strengthen/DGR +strengths +strenuous/Y +streptococcus +stress/DGS +stressful +stretch/DGRSZ +strew/S +strewn +striate +stricken +strict/PRTY +stricture +stride/GRS +strife +strike/GRSZ +strikebreak +striking/Y +string/DGMRSZ +stringent/Y +stringy/PRT +strip/DMS +stripe/DS +stripped +stripper/MS +stripping +striptease +strive/GJS +striven +strobe +stroboscopic +strode +stroke/DGRSZ +stroll/DGRS +strong/RTY +stronghold +strongroom +strontium +strop +strophe +stropping +strove +struck +structural/Y +structure/DGRS +struggle/DGS +strum +strumming +strung +strut/S +strutting +strychnine +stub/MS +stubbing +stubble +stubborn/PY +stubby +stucco +stuck +stud/MS +studding +student/MS +studio/MS +studious/Y +study/DGS +stuff/DGS +stuffy/RT +stultify +stumble/DGS +stump/DGS +stumpage +stumpy +stun +stung +stunk +stunning/Y +stunt/MS +stupefy/G +stupendous/Y +stupid/TY +stupidity/S +stupor +sturdy/P +sturgeon +stutter +style/DGRSZ +styli +stylish/PY +stylistic +stylistically +stylites +stylized +stylus +stymie +styrene +suave +sub/S +subatomic +subbing +subclass/MS +subcomponent/MS +subcomputation/MS +subconscious/Y +subculture/MS +subdivide/DGS +subdivision/MS +subdue/DGS +subexpression/MS +subfield/MS +subfile/MS +subgoal/MS +subgraph +subgraphs +subgroup/MS +subinterval/MS +subject/DGSV +subjection +subjective/Y +subjectivity +sublimate/NX +sublime/D +subliminal +sublist/MS +submarine/RSZ +submerge/DGS +submersible +submission/MS +submit/S +submittal +submitted +submitting +submode/S +submodule/MS +subnetwork/MS +subordinate/DNS +subpoena +subproblem/MS +subprogram/MS +subproject +subproof/MS +subrange/MS +subrogation +subroutine/MS +subschema/MS +subscribe/DGRSZ +subscript/DGS +subscription/MS +subsection/MS +subsegment/MS +subsequence/MS +subsequent/Y +subservient +subset/MS +subside/DGS +subsidiary/MS +subsidize/DGS +subsidy/MS +subsist/DGS +subsistence +subsistent +subspace/MS +substance/MS +substantial/Y +substantiate/DGNSX +substantive/Y +substantivity +substituent +substitutability +substitutable +substitute/DGNSX +substitutionary +substrate/MS +substring/S +substructure/MS +subsume/DGS +subsystem/MS +subtask/MS +subterfuge +subterranean +subtitle/S +subtle/PRT +subtlety/S +subtly +subtract/DGS +subtraction/S +subtractor/MS +subtrahend/MS +subtree/MS +subunit/MS +suburb/MS +suburban +suburbia +subversion +subversive +subvert/DGRS +subway/MS +succeed/DGS +success/SV +successful/Y +succession/MS +successive/Y +successor/MS +succinct/PY +succor +succubus +succumb/DGS +such +suck/DGRSZ +suckle/G +suction +sud/S +sudden/PY +suds/G +sue/DGS +suey +suffer/DGJRSZ +sufferance +suffice/DGS +sufficiency +sufficient/Y +suffix/DGRS +suffocate/DGNS +suffrage +suffragette +suffuse +sugar/DGJS +suggest/DGSV +suggestible +suggestion/MS +suggestive/Y +suicidal/Y +suicide/MS +suit/DGMSZ +suitability +suitable/P +suitably +suitcase/MS +suite/DGSZ +suitor/MS +sulfa +sulfate +sulfide +sulfite +sulfonamide +sulfur +sulfuric +sulfurous +sulk/DGS +sulky/P +sullen/PY +sully +sulphate +sulphur/D +sulphuric +sultan/MS +sultry +sum/MS +sumac +summand/MS +summarily +summarization/MS +summarize/DGS +summary/MS +summate/NX +summation/M +summed +summer/MS +summertime +summing +summit +summitry +summon/DGRSZ +summons/S +sumptuous +sun/MS +sunbeam/MS +sunbonnet +sunburn +sunburnt +sunday/MS +sunder +sundew +sundial +sundown +sundry/S +sunfish +sunflower +sung +sunglass/S +sunk/N +sunlight +sunlit +sunned +sunning +sunny +sunrise +sunset +sunshade +sunshine +sunshiny +sunspot +suntan +suntanned +suntanning +sup/R +superannuate +superb/Y +supercilious +supercomputer/MS +superego/MS +superficial/Y +superfluity/MS +superfluous/Y +superhuman/Y +superimpose/DGS +superintend +superintendent/MS +superior/MS +superiority +superlative/SY +superlunary +supermarket/MS +supernatant +superposable +superpose/DGS +superscript/DGS +supersede/DGS +superset/MS +superstition/MS +superstitious +supervene +supervise/DGNS +supervisor/MS +supervisory +supine +supper/MS +supping +supplant/DGS +supple/P +supplement/DGS +supplemental +supplementary +supplicate/N +supply/DGNRSZ +support/DGRSVZ +supportable +supporting/Y +supportive/Y +supposable +suppose/DGS +supposed/Y +supposition/MS +suppress/DGNS +suppressible +suppression +suppressor +supra +supranational +supremacy +supreme/Y +supremity/S +surcease +surcharge +sure/PY +surety/S +surf +surface/DGPS +surfactant +surfeit +surge/DGS +surgeon/MS +surgery +surgical/Y +surly/P +surmise/DS +surmount/DGS +surname/MS +surpass/DGS +surplus/MS +surprise/DGS +surprising/Y +surreal +surrender/DGS +surreptitious +surrey +surrogate/MS +surround/DGJS +surtax +surtout +surveillant +survey/DGS +surveyor/MS +survival/S +survive/DGS +survivor/MS +susceptible +sushi +suspect/DGS +suspend/DGRSZ +suspender/M +suspense/NSX +suspensor +suspicion/MS +suspicious/Y +sustain/DGS +sustenance +suture/S +suzerain +suzerainty +svelte +swab +swabbing +swabby +swag +swagger/DG +swain/MS +swallow/DGS +swallowtail +swam +swami +swamp/DGS +swampy +swan/MS +swank +swanky +swanlike +swap/S +swapped +swapping +swarm/DGS +swart +swarthy +swastika +swat +swatch +swath +swathe +swatted +swatting +sway/DG +swear/GRS +sweat/DGRSZ +sweatband +sweatshirt +sweaty +sweep/GJRSZ +sweepstake +sweet/NPRSTXY +sweeten/DGJRZ +sweetheart/MS +sweetish +swell/DGJS +swelt/R +swept +swerve/DGS +swift/PRTY +swig +swigging +swim/S +swimmer/MS +swimming/Y +swimsuit +swindle +swine +swing/GRSZ +swingable +swingy +swipe +swirl/DG +swirly +swish/D +swishy +swiss +switch/DGJRSZ +switchblade +switchboard/MS +switchgear +switchman +swivel +swizzle +swollen +swoon +swoop/DGS +sword/MS +swordfish +swordplay +swordtail +swore +sworn +swum +swung +sybarite +sycamore +sycophant +sycophantic +syenite +syllabic +syllabify +syllable/MS +syllogism/MS +syllogistic +sylvan +symbiosis +symbiotic +symbol/MS +symbolic +symbolically +symbolism +symbolization +symbolize/DGS +symmetric +symmetrical/Y +symmetry/MS +sympathetic +sympathize/DGRSZ +sympathizing/Y +sympathy/MS +symphonic +symphony/MS +symposia +symposium/S +symptom/MS +symptomatic +synagogue +synapse/MS +synaptic +synchronism +synchronization +synchronize/DGRSZ +synchronous/Y +synchrony +synchrotron +syncopate +syndic +syndicate/DNS +syndrome/MS +synergism +synergistic +synergy +synod +synonym/MS +synonymous/Y +synonymy +synopses +synopsis +synoptic +syntactic +syntactical/Y +syntax +synthesis +synthesize/DGRSZ +synthetic/S +syringa +syringe/S +syrinx +syrup +syrupy +system/MS +systematic +systematically +systematize/DGS +systemic +systemization +systemwide +t's +t/X +tab/S +tabbing +tabernacle/MS +table/DGS +tableau/MS +tableaux +tablecloth +tablecloths +tableland +tablespoon/MS +tablespoonful/MS +tablet/MS +tabloid +taboo/MS +tabu +tabula +tabular +tabulate/DGNSX +tabulator/MS +tachinid +tachometer/MS +tacit/Y +tack/DG +tackle/MS +tacky +tact +tactful +tactic/S +tactile +tactual +tad +tadpole +taffeta +taffy +taft +tag/MS +tagged +tagging +tail/DGS +tailgate +tailor/DGS +taint/D +take/GJRSZ +taken +takeoff +takeover +talc +talcum +tale/MS +talent/DS +talisman +talismanic +talk/DGRSZ +talkative/PY +talkie +talky +tall/PRT +tallow +tally +tallyho +talon +talus +tam/DGR +tamale +tamarack +tamarind +tambourine +tame/DGPRSY +tamp/RZ +tamper/DG +tampon +tan +tanager +tandem +tang +tangent/MS +tangential +tangerine +tangible +tangibly +tangle/D +tango +tangy +tanh +tank/RSZ +tanner/MS +tannin +tanning +tansy +tantalizing/Y +tantalum +tantamount +tantrum/MS +tao +tap/DGJMRSZ +tapa +tape/DGJRSZ +taper/DG +tapestry/MS +tapeworm +tapir +tapis +tappa +tapped +tapper/MS +tappet +tapping +taproot/MS +tar +tara +tarantara +tarantula +tardy/P +target/DGS +tariff/MS +tarnish +tarpaper +tarpaulin +tarpon +tarring +tarry +tart/PY +tartar +task/DGS +taskmaster +tassel/MS +taste/DGRSZ +tasteful/PY +tasteless/Y +tasty +tat/R +tate/R +tatter/D +tatting +tattle/R +tattletale +tattoo/DS +tatty +tau +taught +taunt/DGRS +taut/PY +tautological/Y +tautology/MS +tavern/MS +taverna +tawdry +tawny +tax/DGS +taxable +taxation +taxi/DGS +taxicab/MS +taxonomic +taxonomically +taxonomy +taxpayer/MS +taxpaying +tea/S +teacart +teach/GJRSZ +teachable +teacher/M +teacup +teahouse +teakettle +teakwood +teal +team/DGS +teammate +teamster +teamwork +teapot +tear/DGS +teardrop +tearful/Y +teas/DGS +tease/DGS +teasel +teaspoon/MS +teaspoonful/MS +teat +tech +technetium +technic +technical/Y +technicality/MS +technician/MS +technique/MS +technological/Y +technologist/MS +technology/S +tectonic +tecum +ted +tedding +tedious/PY +tedium +tee/H +teeing +teem/DGS +teen/S +teenage/DRZ +teensy +teet +teeth/DGS +teethe/DGS +teetotal +teflon +tektite +telecommunicate/NX +teleconference +telegram/MS +telegraph/DGRZ +telegraphic +telegraphs +telegraphy +telekinesis +telemeter +telemetric +telemetry +teleological/Y +teleology +teleost +telepathic +telepathy +telephone/DGRSZ +telephonic +telephony +telephotography +teleprinter +teleprocessing +teleprompter +telescope/DGS +telescopic +teletype/MS +teletypesetting +teletypewrite +televise/DGNSX +televisor/MS +tell/GRSZ +tellurium +temerity +temper/DGS +tempera +temperament/S +temperamental +temperance +temperate/PY +temperature/MS +tempest +tempestuous/Y +template/MS +temple/MS +tempo +temporal/Y +temporarily +temporary/S +tempt/DGRSZ +temptation/MS +tempting/Y +temptress +ten/HS +tenable +tenacious/Y +tenacity +tenant/MS +tend/DGRSZ +tendency/S +tender/PY +tenderfoot +tenderloin +tendon +tenebrous +tenement/MS +tenet +tenfold +tennessee +tennis +tenon +tenor/MS +tens/DGRST +tense/DGNPRSTXY +tensile +tensional +tensor +tenspot +tent/DGS +tentacle/DS +tentative/Y +tenuous +tenure +tepee +tepid +teratogenic +teratology +terbium +tercel +term/DGS +terminable +terminal/MSY +terminate/DGNSX +terminator/MS +termini +terminology/S +terminus +termite +termwise +tern +ternary +terpsichorean +terrace/DS +terrain/MS +terramycin +terrapin +terrestrial +terrible +terribly +terrier/M +terrific +terrify/DGS +territorial +territory/MS +terror/MS +terrorism +terrorist/MS +terroristic +terrorize/DGS +terry/RZ +terse +tertiary +tessellate +test/DGJRSZ +testability +testable +testament/MS +testamentary +testate +testes +testicle/MS +testicular +testify/DGRSZ +testimonial +testimony/MS +testy +tetanus +tete +tether +tetrachloride +tetrafluouride +tetragonal +tetrahedra +tetrahedral +tetrahedron +tetravalent +texas +text/MS +textbook/MS +textile/MS +textual/Y +textural +texture/DS +th +thallium +thallophyte +than +thank/DGS +thankful/PY +thankless/PY +thanksgiving +that'd +that'll +that/MS +thatch/S +thaw/DGS +the/GJ +theater/MS +theatric +theatrical/SY +thee +theft/MS +their/S +them +thematic +theme/MS +themselves +then +thence +thenceforth +theocracy +theologian +theological +theology +theorem/MS +theoretic +theoretical/Y +theoretician/S +theorist/MS +theorization/MS +theorize/DGRSZ +theory/MS +therapeutic +therapist/MS +therapy/MS +there'd +there'll +there/M +thereabouts +thereafter +thereat +thereby +therefor +therefore +therefrom +therein +thereof +thereon +thereto +theretofore +thereunder +thereupon +therewith +thermal +thermionic +thermistor +thermo +thermocouple +thermodynamic/S +thermoelastic +thermoelectric +thermometer/MS +thermometric +thermometry +thermomigrate +thermonuclear +thermopile +thermoplastic +thermopower +thermosetting +thermostable +thermostat/MS +thermostatic +thesaurus +these/S +thesis +thespian +theta +they +they'd +they'll +they're +they've +thiamin +thick/NPRTXY +thicket/MS +thickish +thief +thieve/GS +thigh +thighs +thimble/MS +thin/PY +thine +think/GRSZ +thinkable +thinkably +thinner +thinnest +thinning +thinnish +thiocyanate +thiouracil +third/SY +thirst/DS +thirsty +thirteen/HS +thirtieth +thirty/S +this +this'll +thistle +thistledown +thither +thong +thoriate +thorium +thorn/MS +thorny +thorough/PY +thoroughbred +thoroughfare/MS +thoroughgoing +those +thou +though +thought/MS +thoughtful/PY +thoughtless/PY +thousand/HS +thrash/DGRS +thread/DGRSZ +threadbare +threat/NSX +threaten/DG +three/MS +threefold +threescore +threesome +thresh +threshold/MS +threw +thrice +thrift +thrifty +thrill/DGRSZ +thrilling/Y +thrips +thrive/DGS +throat/DS +throaty +throb/S +throbbed +throbbing +throes +thrombosis +throne/MS +throng/MS +throttle/DGS +through +throughout +throughput +throw/GRS +throwback +thrown +thrum +thrumming +thrush +thrust/DGRSZ +thud/S +thudding +thug/MS +thuggee +thulium +thumb/DGS +thumbnail +thump/DG +thunder/DGRSZ +thunderbolt/MS +thunderclap +thunderflower +thunderous +thunderstorm/MS +thursday/MS +thus/Y +thwack +thwart/DG +thy +thyratron +thyroglobulin +thyroid +thyroidal +thyronine +thyrotoxic +thyroxine +thyself +ti/DRZ +tibet +tibia +tic +tick/DGRSZ +ticket/MS +tickle/DGS +ticklish +tid/DGJ +tidal/Y +tidbit +tide/DGJS +tideland +tidewater +tidy/DGP +tie/DRSZ +tift +tiger/MS +tight/NPRTXY +tighten/DGJRZ +tigress +til/DGH +tilde +tile/DGS +till/DGRSZ +tillable +tilt/DGS +timber/DGS +timberland +timbre +time/DGJRSYZ +timeout +timepiece +timeshare/G +timetable/MS +timeworn +timid/Y +timidity +timothy +tin/MS +tincture +tinder +tine +tinfoil +tinge/D +tingle/DGS +tinily +tinker/DGS +tinkle/DGS +tinnily +tinning +tinny/PRT +tinsel +tint/DGS +tintype +tiny/PRT +tip/MS +tipoff +tipped +tipper/MS +tipping +tipple +tippy +tipsy +tiptoe +tirade +tire/DGS +tired/Y +tireless/PY +tiresome/PY +tissue/MS +tit/RSZ +titanate +titanic +titanium +tithe/RS +titian +titillate +title/DS +titmouse +titrate +titular +to +toad/MS +toady +toast/DGRS +tobacco +toccata +today +today'll +toddle +toe/MS +toenail +toffee +tofu +tog/S +together/P +togging +toggle/DGS +toil/DGRS +toilet/MS +toilsome +tokamak +token/MS +told +tolerability +tolerable +tolerably +tolerance/S +tolerant/Y +tolerate/DGNS +toll/DS +tollgate +tollhouse +toluene +tomahawk/MS +tomato +tomatoes +tomb/MS +tomblike +tombstone +tome +tommy +tomography +tomorrow +ton/DGMRS +tonal +tone/DGRS +tong/S +tongue/DS +tonic/MS +tonight +tonk +tonnage +tonsil +tonsillitis +tony +too/H +toodle +took +tool/DGRSZ +toolkit +toolmake +toolsmith +toot +toothbrush/MS +toothpaste +toothpick/MS +tootle +top/RS +topaz +topcoat +topgallant +topic/MS +topical/Y +topmost +topnotch +topocentric +topography +topological +topology/S +topping +topple/DGS +topsoil +tor +torah +torch/MS +tore +tori +torment/DGRZ +torn +tornado +tornadoes +toroid +toroidal +torpedo +torpedoes +torpid +torpor +torque +torr +torrent/MS +torrid +torsion +torso +tort +tortoise/MS +tortoiseshell +tortuous +torture/DGRSZ +torus/MS +tory +toss/DGS +tot +total/DGSY +totalitarian +totality/MS +totalled +totaller/S +totalling +tote +totem +totemic +totter/DGS +touch/DGS +touchable +touchdown +touchily +touching/Y +touchstone +touchy/PRT +tough/NPRTY +tour/DGS +tourist/MS +tournament/MS +tousle +tout +tow/DRZ +toward/S +towboat +towel/GS +towelled +towelling +tower/DG +towhead +towhee +town/MS +townhouse +township/MS +townsman +townsmen +toxic +toxicology +toxin +toy/DGS +trace/DGJRSZ +traceable +tracery +trachea +track/DGRSZ +trackage +tract/MSV +tractability +tractable +tractor/MS +trade/DGRSZ +trademark/MS +tradeoff +tradesman +tradesmen +tradition/MS +traditional/Y +traffic/MS +trafficked +trafficker/MS +trafficking +trag +tragedian +tragedy/MS +tragic +tragically +tragicomic +trail/DGJRSZ +trailside +train/DGRSZ +trainee/MS +trainman +trainmen +traipse +trait/MS +traitor/MS +traitorous +trajectory/MS +tram +trammel +tramp/DGS +trample/DGRS +tramway +trance/MS +tranquil/Y +tranquility +tranquillity +transact +transaction/MS +transalpine +transatlantic +transceiver +transcend/DGS +transcendent +transcendental +transconductance +transcontinental +transcribe/DGRSZ +transcript/MS +transcription/MS +transducer +transduction +transect +transept +transfer/MS +transferable +transferal/MS +transferee +transference +transferor +transferral +transferred +transferrer/MS +transferring +transfinite +transfix +transform/DGRSZ +transformable +transformation/MS +transformational +transfusable +transfuse/N +transgress/D +transgression/MS +transgressor +transient/SY +transistor/MS +transit/V +transition/DS +transitional +transitive/PY +transitivity +transitory +translatability +translatable +translate/DGNSX +translational +translator/MS +transliterate +translucent +transmissible +transmission/MS +transmit/S +transmittable +transmittal +transmittance +transmitted +transmitter/MS +transmitting +transmogrify/N +transmutation +transmute +transoceanic +transom +transpacific +transparency/MS +transparent/Y +transpiration +transpire/DGS +transplant/DGS +transplantation +transport/DGRSZ +transportability +transportation +transposable +transpose/DGS +transposition +transship +transshipping +transversal +transverse +transvestite +trap/MS +trapezium +trapezoid/MS +trapezoidal +trapped +trapper/MS +trapping/S +trash +trashy +trauma +traumatic +travail +travel/DGJRSZ +travelogue +traversable +traversal/MS +traverse/DGS +travertine +travesty/MS +trawl +tray/MS +treacherous/Y +treachery/MS +tread/GS +treadle +treadmill +treason +treasonous +treasure/DGRS +treasury/MS +treat/DGS +treatise/MS +treatment/MS +treaty/MS +treble +tree/MS +treelike +treetop/MS +trefoil +trek/MS +trekking +trellis +tremble/DGS +tremendous/Y +tremor/MS +tremulous +trench/RS +trenchant +trencherman +trenchermen +trend/GS +trendy +trepidation +trespass/DRSZ +tress/MS +trestle +triable +triad +trial/MS +triangle/MS +triangular/Y +triangulate +triatomic +tribal +tribe/MS +tribesman +tribesmen +tribulate +tribunal/MS +tribune/MS +tributary +tribute/MS +trichloroacetic +trichloroethane +trichotomy +trichrome +trick/DGS +trickery +trickle/DGS +trickster +tricky/PRT +trident +tridiagonal +triennial +trifle/GRS +trifluouride +trig +trigger/DGS +trigonal +trigonometric +trigonometry +trigram +trihedral +trill/D +trillion/HS +trilobite +trilogy +trim/PRSY +trimester +trimmed +trimmer +trimmest +trimming/S +trinitarian +trinity +trinket/MS +trio +triode +trioxide +trip/MS +tripartite +tripe +triphenylphosphine +triple/DGS +triplet/MS +triplex +triplicate +tripod +tripoli +tripping +triptych +trisodium +tristate +trisyllable +trite +tritium +triton +triumph/DG +triumphal +triumphant/Y +triumphs +triune +trivalent +trivia +trivial/Y +triviality/S +trivium +trod +trodden +troglodyte +troika +troll/MS +trolley/MS +trollop +trombone +trompe +troop/RSZ +trophic +trophy/MS +tropic/MS +tropical +tropopause +troposphere +tropospheric +trot/S +trotting +trouble/DGS +troublemaker/MS +troubleshoot/GRSZ +troublesome/Y +trough +trounce +troupe +trouser/S +trout +trowel/MS +troy +truancy +truant/MS +truce +truck/DGRSZ +truculent +trudge/D +true/DGRST +truism/MS +truly +trump/DS +trumpery +trumpet/R +truncate/DGNSX +truncation/M +trundle +trunk/MS +truss +trust/DGS +trustee/MS +trustful/PY +trusting/Y +trustworthy/P +trusty +truth +truthful/PY +truths +try/DGRSZ +trypsin +tsar +tsarina +tsunami +tub/GMRSZ +tuba +tube/GRSZ +tuberculin +tuberculosis +tubular +tubule +tuck/DGRS +tuesday/MS +tuff +tuft/MS +tug/S +tugging +tuition +tularemia +tulip/MS +tulle +tum +tumble/DGRSZ +tumbrel +tumor/S +tumult/MS +tumultuous +tun/DGRZ +tuna +tunable +tundra +tune/DGRSZ +tuneful +tung +tungstate +tungsten +tunic/MS +tunnel/DS +tupelo +tuple/MS +turban/MS +turbinate +turbine +turbofan +turbojet +turbulent/Y +turf +turing +turk +turkey/MS +turmoil/MS +turn/DGJRSZ +turnable +turnabout +turnaround +turnery +turnip/MS +turnkey +turnoff +turnout +turnover +turnpike +turnstone +turntable +turpentine +turpitude +turquoise +turret/MS +turtle/MS +turtleback +turtleneck +turvy +tusk +tussle +tutelage +tutor/DGS +tutorial/MS +tutu +tuxedo +twaddle +twain +twang +twas +tweak +tweed +tweedy +tweeze +twelfth +twelve/S +twentieth +twenty/S +twice +twiddle +twig/MS +twigging +twilight/MS +twill +twin/DMRS +twine/DR +twinge +twinkle/DGRS +twinning +twirl/DGRS +twirly +twist/DGRSZ +twisty +twit +twitch/DG +twitchy +twitter/DG +twitting +two/MS +twofold +twosome +tycoon +tying +type/DGMS +typeface +typeout +typescript +typeset +typesetter +typesetting +typewrite/RZ +typewriter/M +typewritten +typhoid +typhoon +typhus +typic +typical/PY +typify/DGS +typist/MS +typo +typographer +typographical/Y +typography +typology +tyrannic +tyrannicide +tyranny +tyrant/MS +tyrosine +u's +ubiquitous/Y +ubiquity +ugh +ugly/PRT +ulcer/MS +ulcerate +ulterior +ultimate/Y +ultimatum +ultra +ultracentrifuge +ultraconservative +ultrafast +ultramarine +ultramodern +ultrashort +ultrasonic +ultrasound +ultrastructure +ultraviolet +umber +umbilical +umbilici +umbilicus +umbra +umbrage +umbrella/MS +umpire/MS +unabated +unabbreviated +unable +unacceptability +unacceptable +unacceptably +unaccustomed +unacknowledged +unadulterated +unaesthetically +unaffected/PY +unaided +unalienability +unalienable +unalterably +unaltered +unambiguous/Y +unambitious +unanalyzable +unanimity +unanimous/Y +unanswered +unanticipated +unarmed +unary +unassailable +unassigned +unattainability +unattainable +unattended +unattractive/Y +unauthorized +unavailability +unavailable +unavoidable +unavoidably +unaware/PS +unbalanced +unbearable +unbeknownst +unbelievable +unbiased +unbidden +unblock/DGS +unborn +unbound/D +unbreakable +unbroken +unbuffered +uncancelled +uncanny +uncapitalized +uncaught +uncertain/Y +uncertainty/S +unchangeable +unchanged +unchanging +unchristian +unclaimed +uncle/MS +unclean/PY +unclear/D +unclosed +uncomfortable +uncomfortably +uncommitted +uncommon/Y +uncompromising +uncomputable +unconcerned/Y +unconditional/Y +unconnected +unconscious/PY +unconstrained +uncontrollability +uncontrollable +uncontrollably +uncontrolled +unconventional/Y +unconvinced +unconvincing +uncorrectable +uncorrected +uncountable +uncountably +uncouth +uncover/DGS +unction +undaunted/Y +undecidable +undecided +undeclared +undecomposable +undefinability +undefined +undeleted +undeniably +under +underbrush +underclassman +underclassmen +underdone +underestimate/DGNS +underflow/DGS +underfoot +undergo/GS +undergoes +undergone +undergraduate/MS +underground +underlie/S +underline/DGJS +underling/MS +underlying +undermine/DGS +underneath +underpinning/S +underplay/DGS +underscore/DS +understand/GJS +understandability +understandable +understandably +understanding/Y +understated +understood +undertake/GJRSZ +undertaken +undertook +underway +underwear +underwent +underworld +underwrite/GRSZ +undesirability +undesirable +undetectable +undetected +undetermined +undeveloped +undid +undirected +undisciplined +undiscovered +undisturbed +undivided +undo/GJ +undocumented +undoes +undone +undoubtedly +undress/DGS +undue +undulate +unduly +uneasily +uneasy/P +uneconomical +unembellished +unemployed +unemployment +unending +unenlightening +unequal/DY +unequivocal/Y +unessential +unevaluated +uneven/PY +uneventful +unexcused +unexpanded +unexpected/Y +unexplained +unexplored +unextended +unfair/PY +unfaithful/PY +unfamiliar/Y +unfamiliarity +unfavorable +unfettered +unfinished +unfit/P +unflagging +unfold/DGS +unforeseen +unforgeable +unforgiving +unformatted +unfortunate/SY +unfounded +unfriendly/P +unfulfilled +ungrammatical +ungrateful/PY +ungrounded +unguarded +unguided +unhappily +unhappy/PRT +unhealthy +unheeded +uniaxial +unicorn/MS +unidentified +unidimensional +unidirectional/Y +unidirectionality +uniform/DSY +uniformity +unify/DGNRSXZ +unilateral +unilluminating +unimaginable +unimodal +unimpeded +unimplemented +unimportant +unindented +uninitialized +uninominal +unintelligible +unintended +unintentional/Y +uninteresting/Y +uninterpreted +uninterrupted/Y +union/MS +unionization +unionize/DGRSZ +uniplex +unipolar +uniprocessor +unique/PY +unison +unit/DGMS +unitarian +unitary +unite/DGS +unity/MS +univalent +univalve/MS +univariate +universal/SY +universality +universe/MS +university/MS +unjust/Y +unjustified +unkempt +unkind/PY +unknowable +unknowing/Y +unknown/S +unlabeled +unlawful/Y +unleash/DGS +unless +unlike/PY +unlimited +unlink/DGS +unload/DGS +unlock/DGS +unlucky +unmanageable +unmanageably +unmanned +unmarked +unmarried +unmasked +unmatched +unmistakable +unmodified +unmoved +unnamed +unnatural/PY +unnecessarily +unnecessary +unneeded +unnoticed +unobservable +unobserved +unobtainable +unoccupied +unofficial/Y +unopened +unordered +unpack/DGS +unparalleled +unparsed +unplanned +unpleasant/PY +unpopular +unpopularity +unprecedented +unpredictable +unprescribed +unpreserved +unprimed +unprofitable +unprojected +unprotected +unprovability +unprovable +unproven +unpublished +unqualified/Y +unquestionably +unquestioned +unquoted +unravel/DGS +unreachable +unreal +unrealistic +unrealistically +unreasonable/P +unreasonably +unrecognizable +unrecognized +unrelated +unreliability +unreliable +unreported +unrepresentable +unresolved +unresponsive +unrest +unrestrained +unrestricted/Y +unrestrictive +unroll/DGS +unruly +unsafe/Y +unsanitary +unsatisfactory +unsatisfiability +unsatisfiable +unsatisfied +unsatisfying +unscrupulous +unseeded +unseen +unselected +unselfish/PY +unsent +unsettled +unsettling +unshaken +unshared +unsigned +unskilled +unsolvable +unsolved +unsophisticated +unsound +unspeakable +unspecified +unstable +unsteady/P +unstructured +unsuccessful/Y +unsuitable +unsuited +unsupported +unsure +unsurprising/Y +unsynchronized +untapped +unterminated +untested +unthinkable +untidy/P +untie/DS +until +untimely +unto +untold +untouchable/MS +untouched +untoward +untrained +untranslated +untreated +untried +untrue +untruthful/P +untying +unusable +unused +unusual/Y +unvarying +unveil/DGS +unwanted +unwelcome +unwholesome +unwieldy/P +unwilling/PY +unwind/GRSZ +unwise/Y +unwitting/Y +unworthy/P +unwound +unwritten +up +upbeat +upbraid +upbring +upcome +update/DGRS +updraft +upend +upgrade/DGS +upheaval +upheld +uphill +uphold/GRSZ +upholster/DGRS +upholstery +upkeep +upland/S +uplift +upon +upper +upperclassman +upperclassmen +uppercut +uppermost +upraise +upright/PY +uprise/GJ +uprising/M +upriver +uproar +uproarious +uproot/DGS +upset/S +upsetting +upshot/MS +upside +upsilon +upslope +upstair/S +upstand +upstate/R +upstream +upsurge +upswing +uptake +uptown +uptrend +upturn/DGS +upward/S +upwind +urania +uranium +uranyl +urban +urbane +urbanite +urchin/MS +urea +uremia +urethane +urethra +urge/DGJS +urgency +urgent/Y +urinal +urinary +urinate/DGNS +urine +urn/MS +us/DGRSZ +usability +usable +usably +usage/S +use/DGRSZ +useful/PY +useless/PY +user/M +usher/DGS +usual/Y +usurer +usurious +usurp/DR +usurpation +usury +utah +utensil/MS +uterine +utile +utilitarian +utility/MS +utilization/MS +utilize/DGS +utmost +utopia +utopian/MS +utter/DGSY +utterance/MS +uttermost +v's +vacancy/MS +vacant/Y +vacate/DGNSX +vacation/DGRZ +vacationland +vaccinate +vaccine +vacillate +vacua +vacuo +vacuolate +vacuole +vacuous/Y +vacuum/DG +vade +vagabond/MS +vagary/MS +vagina/MS +vaginal +vagrant/Y +vague/PRTY +vain/Y +vainglorious +vale/MS +valedictorian +valedictory +valence/MS +valent +valentine/MS +valet/MS +valeur +valiant/Y +valid/PY +validate/DGNS +validity +valley/MS +valor +valuable/S +valuably +valuate/NX +valuation/M +value/DGRSZ +valve/MS +vamp +vampire +van/MS +vanadium +vandal +vandalize/DGS +vane/MS +vanguard +vanilla +vanish/DGRS +vanishing/Y +vanity/S +vanquish/DGS +vantage +vapor/GS +variability +variable/MPS +variably +variac +variance/MS +variant/SY +variate/NX +variation/M +variegate +variety/MS +various/Y +varistor +varnish/MS +vary/DGJS +vascular +vase/MS +vassal +vast/PRTY +vat/MS +vaudeville +vault/DGRS +vaunt/D +veal +vector/MS +vectorial +vectorization +vectorizing +vee/RZ +veer/DG +veery +vegetable/MS +vegetarian/MS +vegetate/DGNSV +vehemence +vehement/Y +vehicle/MS +vehicular +veil/DGS +vein/DGS +veldt +vellum +velocity/MS +velours +velvet +velvety +venal +vend +vendetta +vendible +vendor/MS +veneer +venerable +venerate +venereal +vengeance +vengeful +venial +venison +venom +venomous/Y +vent/DS +ventilate/DGNS +ventricle/MS +venture/DGJRSZ +venturesome +venturi +veracious +veracity +veranda/MS +verandah +verb/MS +verbal/Y +verbatim +verbena +verbiage +verbose +verbosity +verdant +verdict +verdure +verge/RS +veridic +verifiability +verifiable +verify/DGNRSXZ +verily +verisimilitude +veritable +verity +vermeil +vermiculite +vermilion +vermin +vermouth +vernacular +vernal +vernier +versa +versatile +versatility +verse/DGNSX +versus +vertebra +vertebrae +vertebral +vertebrate/MS +vertex +vertical/PY +vertices +vertigo +verve +very +vesicular +vesper +vessel/MS +vest/DS +vestal +vestibule +vestige/MS +vestigial +vestry +vet +vetch +veteran/MS +veterinarian/MS +veterinary +veto/DR +vetoes +vetting +vex/DGS +vexation +vexatious +vi/DR +via +viability +viable +viably +viaduct +vial/MS +vibrant +vibrate/DGNX +vibrato +viburnum +vicar +vicarious +vice/MS +vicelike +viceroy +vicinal +vicinity +vicious/PY +vicissitude/MS +victim/MS +victimize/DGRSZ +victor/MS +victorious/Y +victory/MS +victrola +victual/RS +video +videotape/MS +vie/DRS +view/DGRSZ +viewable +viewpoint/MS +vigil +vigilance +vigilant/Y +vigilante/MS +vigilantism +vignette/MS +vigor +vigorous/Y +vii +viii +vile/PY +vilify/DGNSX +villa/MS +village/RSZ +villain/MS +villainous/PY +villainy +villein +vindicate +vindictive/PY +vine/MS +vinegar +vineyard/MS +vintage +vintner +vinyl +viola +violate/DGNSX +violator/MS +violence +violent/Y +violet/MS +violin/MS +violinist/MS +viper/MS +virgin/MS +virginal +virginia +virginity +virgule +virile +virtual/Y +virtue/MS +virtuosi +virtuosity +virtuoso/MS +virtuous/Y +virulent +virus/MS +vis +visa/S +visage +viscera +visceral +viscoelastic +viscometer +viscosity +viscount/MS +viscous +vise/NX +viselike +visibility +visible +visibly +vision/M +visionary +visit/DGS +visitation/MS +visitor/MS +visor/MS +vista/MS +visual/Y +visualize/DGRS +vita +vitae +vital/SY +vitality +vitamin +vitiate +vitreous +vitrify +vitriol +vitriolic +vitro +viva +vivace +vivacious +vivacity +vivid/PY +vivify +vivo +vixen +viz +vizier +vocable +vocabularian +vocabulary/S +vocal/SY +vocalic +vocate/NX +vocation/M +vocational/Y +vociferous +vogue +voice/DGRSZ +voiceband +void/DGRS +volatile +volatility/S +volcanic +volcanism +volcano/MS +volition +volley +volleyball/MS +volt/S +voltage/S +voltaic +voltmeter +voluble +volume/MS +volumetric +voluminous +voluntarily +voluntary +volunteer/DGS +voluptuous +vomit/DGS +von +voodoo +voracious +voracity +vortex +vortices +vorticity +votary +vote/DGRSVZ +vouch/GRSZ +vouchsafe +vow/DGRS +vowel/MS +voyage/DGJRSZ +vulgar/Y +vulnerability/S +vulnerable +vulpine +vulture/MS +vulturelike +vying +w's +w/JV +wack +wacke +wacky +wad/DGR +waddle +wade/DGRS +wadi +wafer/MS +waffle/MS +waft +wag/DGRSZ +wage/DGRSZ +wagging +waggle +wagon/RS +wagoneer +wah +wail/DGS +wainscot +waist/MS +waistcoat/MS +waistline +wait/DGRSZ +waitress/MS +waive/DGRS +waiverable +wake/DGS +wakeful +waken/DG +wakerobin +wakeup +wale +walk/DGRSZ +walkie +walkout +walkover +walkway +wall/DGS +wallaby +wallboard +wallet/MS +wallop +wallow/DGS +wallpaper +wally +walnut/MS +walrus/MS +waltz/DGS +wan/DGY +wand/RZ +wander/DGJRZ +wane/DGS +wangle +want/DGS +wanton/PY +wapato +wapiti +war/MS +warble/DGRS +ward/NRSX +wardrobe/MS +wardroom +ware/S +warehouse/GS +warehouseman +warfare +warhead +warily +warlike +warm/DGHRSTYZ +warmhearted +warmish +warmonger +warmup +warn/DGJRS +warning/Y +warp/DGS +warrant/DGS +warranty/MS +warred +warren +warring +warrior/MS +warship/MS +wart/MS +wartime +warty +wary/P +was +wash/DGJRSZ +washbasin +washboard +washbowl +washington +washout +washy +wasn't +wasp/MS +waspish +wast/DG +wastage +waste/DGS +wastebasket +wasteful/PY +wasteland +wastewater +wastrel +watch/DGJRSZ +watchband +watchdog +watchful/PY +watchmake +watchman +watchmen +watchword/MS +water/DGJS +watercourse +waterfall/MS +waterfront +waterline +watermelon +waterproof/G +watershed +waterside +waterway/MS +watery +watt +wattage +wattle +wave/DGRSZ +waveform/MS +wavefront/MS +waveguide +wavelength +wavelengths +wavenumber +wavy +wax/DGNRSZ +waxwork +waxy +way/MS +waybill +waylaid +waylay +wayside +wayward +we'd +we'll +we're +we've +we/GJTV +weak/NPRTXY +weaken/DG +weakness/MS +weal/H +wealths +wealthy/T +wean/DG +weapon/MS +weaponry +wear/GRS +wearable +wearily +wearisome/Y +weary/DGPRT +weasel/MS +weather/DGS +weatherbeaten +weathercock/MS +weatherproof +weatherstrip +weatherstripping +weave/GRS +web/MRS +webbing +wed/S +wedded +wedding/MS +wedge/DGS +wedlock +wednesday/MS +wee/D +weed/S +weedy +week/SY +weekday +weekend/MS +weep/DGRS +weigh/DGJ +weighs +weight/DGS +weighty +weir +weird/Y +welcome/DGS +weld/DGRS +welfare +well/DGS +wellbeing +wellington +welsh +welt +wench/MS +went +wept +were +weren't +wert +westbound +westerly +western/RZ +westernmost +westward/S +wet/PSY +wetland +wetted +wetter +wettest +wetting +whack/DGS +whale/GRS +wham +whamming +wharf +wharves +what'd +what're +what/M +whatever +whatnot +whatsoever +wheat/N +whee +wheedle +wheel/DGJRSZ +wheelbase +wheelchair +wheelhouse +wheeze +wheezy +whelk +whelm +whelp +when +whence +whenever +where'd +where're +where/M +whereabout/S +whereas +whereby +wherefore +wherein +whereof +whereon +wheresoever +whereupon +wherever +wherewith +whet +whether +whetting +which +whichever +whiff +whig +while +whim/MS +whimper/DGS +whimsey +whimsic +whimsical/Y +whimsy/MS +whine/DGS +whinny +whip/MS +whiplash +whipped +whipper/MS +whippet +whipping/MS +whipsaw +whir +whirl/DGS +whirligig +whirlpool/MS +whirlwind +whirr/G +whisk/DGRSZ +whiskey +whisper/DGJS +whistle/DGRSZ +whistleable +whit/GNRTX +white/GPRSTY +whiteface +whitehead +whiten/DGRZ +whitespace +whitetail +whitewash/D +whither +whittle/DGS +whiz +whizzed +whizzes +whizzing +who'd +who'll +who/M +whoa +whoever +whole/PS +wholehearted/Y +wholesale/RZ +wholesome/P +wholly +whom +whomever +whomsoever +whoop/DGS +whoosh +whop +whopping +whore/MS +whorl/MS +whose +whosoever +whup +why +wick/DRS +wicked/PY +wicket +wide/RTY +widen/DGRS +widespread +widgeon +widget +widow/DRSZ +widowhood +width +widths +widthwise +wield/DGRS +wiener +wife/MY +wig/MS +wigging +wiggle +wiggly +wigmake +wigwam +wild/PRTY +wildcat/MS +wildcatter +wilder/P +wildfire +wildlife +wile/S +wilful +will/DGS +willful/Y +willing/PY +willow/MS +willowy +wilt/DGS +wily/P +win/DGRSZ +wince/DGS +winch +wind/DGRSZ +windbag +windbreak +windfall +windmill/MS +window/MS +windowpane +windowsill +windshield +windstorm +windup +windward +windy +wine/DGRSZ +winemake +winemaster +winery +wineskin +wing/DG +wingback +wingman +wingmen +wingspan +wingtip +wink/DGRS +winkle +winner/MS +winning/SY +winnow +wino +winsome +winter/DGS +wintertime +wintry +winy +wipe/DGRSZ +wire/DGS +wireless +wireman +wiremen +wiretap/MS +wiry/P +wisdom/S +wise/DRTY +wiseacre +wisecrack +wisenheimer +wish/DGRSZ +wishbone +wishful +wishy +wisp/MS +wispy +wistful/PY +wit/MPS +witch/GS +witchcraft +with/RZ +withal +withdraw/GS +withdrawal/MS +withdrawn +withdrew +withe/RZ +withheld +withhold/GJRSZ +within +without +withstand/GS +withstood +withy +witness/DGS +witting +witty +wive/S +wizard/MS +wobble +woe +woebegone +woeful/Y +wok +woke +wold +wolf +wolfish +wolve/S +woman/MY +womanhood +womb/MS +women/M +won +won't +wonder/DGS +wonderful/PY +wondering/Y +wonderland +wonderment +wondrous/Y +wont/D +woo/DGRS +wood/DNS +woodcarver +woodchuck/MS +woodcock/MS +woodcut +wooden/PY +woodgrain +woodhen +woodland +woodlot +woodman +woodpeck/RZ +woodpecker/M +woodrow +woodruff +woodshed +woodside +woodward +woodwind +woodwork/G +woody +woodyard +woof/DGRSZ +wool/NSY +woolgather +wop +word/DGMS +wordily +wordy/P +wore +work/DGJRSZ +workable +workably +workbench/MS +workbook/MS +workday +workhorse/MS +workingman +workload +workman +workmanlike +workmanship +workmen +workout +workpiece +worksheet +workshop/MS +workspace +worktable +world/MSY +worldly/P +worldwide +worm/DGS +wormy +worn +worrisome +worry/DGRSZ +worrying/Y +worse +worsen +worship/DGRS +worshipful +worst/D +worth +worthless/P +worths +worthwhile/P +worthy/PT +would +wouldn't +wound/DGS +wove +woven +wow +wrack +wraith +wrangle/DR +wrap/MS +wrapped +wrapper/MS +wrapping/S +wrapup +wrath +wrathful +wreak/S +wreath/DS +wreathe/DS +wreck/DGRSZ +wreckage +wren/MS +wrench/DGS +wrest +wrestle/GJRS +wretch/DS +wretched/P +wriggle/DGRS +wright +wring/RS +wrinkle/DS +wrist/MS +wristband +wristwatch/MS +writ/GJMRSZ +writable +write/GJRSZ +writer/M +writeup +writhe/DGS +written +wrong/DGSY +wrongdoer +wrongdoing +wrongful +wrote +wrought +wrung +wry +wynn +x's +xenon +xenophobia +xerography +xi +xylem +xylene +xylophone +y's +yacht +yachtsman +yachtsmen +yah +yak +yam +yang +yank/DGS +yap +yapping +yard/MS +yardage +yardstick/MS +yarmulke +yarn/MS +yarrow +yaw +yawl +yawn/GR +ye +yea/S +yeah +year/MSY +yearbook +yearn/DGJ +yeast/MS +yeasty +yell/DGR +yellow/DGPRST +yellowish +yelp/DGS +yen +yeoman +yeomanry +yeomen +yes +yeshiva +yesterday +yesteryear +yet +yield/DGS +yin +yip +yipping +yodel +yoga +yogi +yoke/MS +yokel +yolk +yon +yond/R +yore +york/RZ +you'd +you'll +you're +you've +you/H +young/RTY +youngish +youngster/MS +your/S +yourself +yourselves +youthful/PY +youths +yow +ytterbium +yttrium +yucca +yuh +yule +z's +zag +zagging +zap +zapping +zeal +zealot +zealous/PY +zebra/MS +zenith +zero/DGHS +zeroes +zest +zesty +zeta +zig +zigging +zigzag +zigzagging +zilch +zinc +zing +zip +zipping +zircon +zirconium +zloty +zodiac +zodiacal +zombie +zonal/Y +zone/DGS +zoo/MS +zoological/Y +zoology +zoom +zounds diff --git a/src/kits/Jamfile b/src/kits/Jamfile index 9ea2a38c4f..2cc28d9ee5 100644 --- a/src/kits/Jamfile +++ b/src/kits/Jamfile @@ -89,6 +89,7 @@ SEARCH on [ FGristFiles SubInclude OBOS_TOP src kits app ; SubInclude OBOS_TOP src kits interface ; +SubInclude OBOS_TOP src kits mail ; SubInclude OBOS_TOP src kits media ; SubInclude OBOS_TOP src kits midi ; SubInclude OBOS_TOP src kits midi2 ; diff --git a/src/kits/mail/ChainRunner.cpp b/src/kits/mail/ChainRunner.cpp new file mode 100644 index 0000000000..e8597eff82 --- /dev/null +++ b/src/kits/mail/ChainRunner.cpp @@ -0,0 +1,608 @@ +/* BMailChainRunner - runs the mail inbound and outbound chains +** +** Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +class _EXPORT BMailChainRunner; + +#include +#include +#include +#include "ErrorLogWindow.h" + +struct filter_image { + BMessage *settings; + BMailFilter *filter; + image_id id; +}; + +BLocker list_lock("mdr_chainrunner_lock"); +BList running_chains, running_chain_pointers; + + +static void +show_error(alert_type type, const char *message, const char *tag) +{ + static ErrorLogWindow *window = NULL; + static BLocker lock("error window"); + + lock.Lock(); + + if (window == NULL) { + window = new ErrorLogWindow(BRect(200, 200, 500, 250), + "Mail Daemon Status Log", B_TITLED_WINDOW); + } + + lock.Unlock(); + + window->AddError(type, message, tag); +} + + +// #pragma mark - + + +#if USE_NASTY_SYNC_THREAD_HACK + /* There is a memory leak in gethostbyname() that causes structures it + allocates not to be freed if they were allocated from a BLooper. So + we have this awful hack to ensure that gethostbyname() is not, in + fact, called from a BLooper. It works fairly well, too. Hopefully, + the OpenBeOS net stack will rectify this problem and then I can + turn it off. That will be nice. */ + + int32 BMailChainRunner::thread_sync_func(void *arg) { + BMailChainRunner *us = ((BMailChainRunner *)(arg)); + us->Lock(); + status_t val = us->Init(); + us->Unlock(); + return val; + } + + status_t BMailChainRunner::init_addons() { + thread_id thread = spawn_thread(&thread_sync_func, + "ChainRunnerGetHostByNameHack",10,this); + Unlock(); + resume_thread(thread); + status_t result; + wait_for_thread(thread,&result); + Lock(); + return result; + } +#else + #define init_addons Init +#endif + +_EXPORT BMailChainRunner * +GetMailChainRunner(int32 chain_id, BMailStatusWindow *status, bool selfDestruct) +{ + list_lock.Lock(); + if (running_chains.HasItem((void *)(chain_id))) { + BMailChainRunner *runner = (BMailChainRunner *)running_chain_pointers.ItemAt(running_chains.IndexOf((void *)(chain_id))); + list_lock.Unlock(); + return runner; + } + list_lock.Unlock(); + + BMailChainRunner *runner = new BMailChainRunner(GetMailChain(chain_id), status, + selfDestruct, false, selfDestruct); + + runner->RunChain(); + return runner; +} + + +class DeathFilter : public BMessageFilter { + public: + DeathFilter() + : BMessageFilter(B_QUIT_REQUESTED) + { + } + + virtual filter_result Filter(BMessage *, BHandler **) + { + be_app->MessageReceived(new BMessage('enda')); //---Stop new chains from starting + + list_lock.Lock(); + for (int32 i = 0; i < running_chain_pointers.CountItems(); i++) + ((BMailChainRunner *)(running_chain_pointers.ItemAt(i)))->Stop(); + list_lock.Unlock(); + + while(running_chains.CountItems() > 0) + snooze(10000); // 1/100th of a second to avoid wasting CPU. + + return B_DISPATCH_MESSAGE; + } +}; + + +BMailChainRunner::BMailChainRunner(BMailChain *chain, BMailStatusWindow *status, + bool selfDestruct, bool saveChain, bool destructChain) + : BLooper(chain->Name()), + _chain(chain), + destroy_self(selfDestruct), + destroy_chain(destructChain), + save_chain(saveChain), + _status(status), + _statview(NULL), + suicide(false) +{ + static DeathFilter *filter = NULL; + + if (filter == NULL) + be_app->AddFilter(filter = new DeathFilter); + + list_lock.Lock(); + if (running_chains.HasItem((void *)(_chain->ID()))) + suicide = true; + running_chains.AddItem((void *)(_chain->ID())); + running_chain_pointers.AddItem(this); + list_lock.Unlock(); +} + + +BMailChainRunner::~BMailChainRunner() +{ + //--- Delete any remaining callbacks + for (int32 i = message_cb.CountItems();i-- > 0;) + delete (BMailChainCallback *)message_cb.ItemAt(i); + for (int32 i = process_cb.CountItems();i-- > 0;) + delete (BMailChainCallback *)process_cb.ItemAt(i); + for (int32 i = chain_cb.CountItems();i-- > 0;) + delete (BMailChainCallback *)chain_cb.ItemAt(i); + + //--- Delete any filter images + for (int32 i = 0; i < addons.CountItems(); i++) { + filter_image *image = (filter_image *)(addons.ItemAt(i)); + delete image->filter; + delete image->settings; + + unload_add_on(image->id); + + delete image; + } + + //--- Remove ourselves from the window if we haven't been already + if ((_status != NULL) && (_statview != NULL)) { + _status->Lock(); + if (_statview->Window()) + _status->RemoveView(_statview); + + delete _statview; + _status->Unlock(); + } + + //--- Remove ourselves from the lists + list_lock.Lock(); + running_chains.RemoveItem((void *)(_chain->ID())); + running_chain_pointers.RemoveItem(this); + list_lock.Unlock(); + + //--- And delete our chain + if (destroy_chain) + delete _chain; +} + + +void +BMailChainRunner::RegisterMessageCallback(BMailChainCallback *callback) +{ + message_cb.AddItem(callback); +} + + +void +BMailChainRunner::RegisterProcessCallback(BMailChainCallback *callback) +{ + process_cb.AddItem(callback); +} + + +void +BMailChainRunner::RegisterChainCallback(BMailChainCallback *callback) +{ + chain_cb.AddItem(callback); +} + + +BMailChain * +BMailChainRunner::Chain() +{ + return _chain; +} + + +status_t +BMailChainRunner::RunChain(bool asynchronous) +{ + if (suicide) { + Quit(); + return B_NAME_IN_USE; + } + + Run(); + + PostMessage('INIT'); + + if (!asynchronous) { + status_t result; + wait_for_thread(Thread(),&result); + return result; + } + + return B_OK; +} + + +void +BMailChainRunner::CallCallbacksFor(BList &list, status_t code) +{ + for (int32 i = 0; i < list.CountItems(); i++) { + BMailChainCallback *callback = static_cast(list.ItemAt(i)); + + callback->Callback(code); + delete callback; + } + list.MakeEmpty(); +} + +status_t BMailChainRunner::Init() { + status_t big_err = B_OK; + BString desc; + entry_ref addon; + + MDR_DIALECT_CHOICE ( + desc << ((_chain->ChainDirection() == inbound) ? "Fetching" : "Sending") << " mail for " << _chain->Name(), + desc << _chain->Name() << ((_chain->ChainDirection() == inbound) ? "より受信中..." : "へ送信中...") + ); + + _status->Lock(); + _statview = _status->NewStatusView(desc.String(),_chain->ChainDirection() == outbound); + _status->Unlock(); + + BMessage settings; + for (int32 i = 0; _chain->GetFilter(i,&settings,&addon) >= B_OK; i++) { + struct filter_image *image = new struct filter_image; + BPath path(&addon); + BMailFilter *(* instantiate)(BMessage *,BMailChainRunner *); + + image->id = load_add_on(path.Path()); + + if (image->id < B_OK) { + BString error; + MDR_DIALECT_CHOICE ( + error << "Error loading the mail addon " << path.Path() << " from chain " << _chain->Name() << ": " << strerror(image->id); + ShowError(error.String());, + error << "メールアドオン " << path.Path() << " を " << _chain->Name() << "から読み込む際にエラーが発生しました: " << strerror(image->id); + ShowError(error.String()); + ) + return image->id; + } + + status_t err = get_image_symbol(image->id,"instantiate_mailfilter",B_SYMBOL_TYPE_TEXT,(void **)&instantiate); + if (err < B_OK) { + BString error; + MDR_DIALECT_CHOICE ( + error << "Error loading the mail addon " << path.Path() << " from chain " << _chain->Name() + << ": the addon does not seem to be a mail addon (missing symbol instantiate_mailfilter)."; + ShowError(error.String());, + error << "メールアドオン " << path.Path() << " を " << _chain->Name() << "から読み込む際にエラーが発生しました" + << ": そのアドオンはメールアドオンではないようです(instantiate_mailfilterシンボルがありません)"; + ShowError(error.String()); + ) + + err = -1; + return err; + } + + image->settings = new BMessage(settings); + + image->settings->AddInt32("chain",_chain->ID()); + image->filter = (*instantiate)(image->settings,this); + addons.AddItem(image); + + if ((big_err = image->filter->InitCheck()) != B_OK) { + //printf("InitCheck() failed (%s) in add-on %s\n",strerror(big_err),path.Path()); + break; + } + } + return big_err; +} + +void +BMailChainRunner::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case 'INIT': + if (init_addons() == B_OK) + break; + case 'STOP': { + + CallCallbacksFor(chain_cb, B_OK); + // who knows what the code was? + + BMessage settings; + entry_ref addon; + + for (int32 i = 0; i < addons.CountItems(); i++) { + filter_image *image = (filter_image *)(addons.ItemAt(i)); + delete image->filter; + + if (save_chain) { + image->settings->RemoveName("chain"); + _chain->GetFilter(i,&settings,&addon); + _chain->SetFilter(i,*(image->settings),addon); + } + + delete image->settings; + + unload_add_on(image->id); + + delete image; + } + + addons.MakeEmpty(); + + if ((_status != NULL) && (_statview != NULL)) { + _status->Lock(); + if (_statview->Window()) + _status->RemoveView(_statview); + else + delete _statview; + + _statview = NULL; + _status->Unlock(); + } + + /*list_lock.Lock(); + running_chains.RemoveItem((void *)(_chain->ID())); + running_chain_pointers.RemoveItem(this); + list_lock.Unlock();*/ + + if (save_chain) + _chain->Save(); + + if (destroy_self) + Quit(); + break; + } + case 'GETM': { + BStringList list; + msg->FindFlat("messages",&list); + _statview->SetTotalItems(list.CountItems()); + _statview->SetMaximum(msg->FindInt32("bytes")); + get_messages(&list); } + break; + case 'GTSM': { + const char *uid; + status_t err = B_OK; + msg->FindString("uid",&uid); + BEntry *entry = new BEntry(msg->FindString("into")); + _statview->SetTotalItems(1); + _statview->SetMaximum(msg->FindInt32("bytes")); + BPositionIO *file = new BFile(entry, B_READ_WRITE | B_CREATE_FILE); + BPath *folder = new BPath; + BMessage *headers = new BMessage; + headers->AddBool("ENTIRE_MESSAGE",true); + + for (int32 j = 0; j < addons.CountItems(); j++) { + struct filter_image *current = (struct filter_image *)(addons.ItemAt(j)); + + err = current->filter->ProcessMailMessage(&file,entry,headers,folder,uid); + if (err != B_OK) + break; + } + + CallCallbacksFor(message_cb, err); + + delete file; + delete entry; + delete headers; + delete folder; + + CallCallbacksFor(process_cb, err); + + if (save_chain) { + entry_ref addon; + BMessage settings; + for (int32 i = 0; i < addons.CountItems(); i++) { + filter_image *image = (filter_image *)(addons.ItemAt(i)); + + BMessage *temp = new BMessage(*(image->settings)); + temp->RemoveName("chain"); + _chain->GetFilter(i, &settings, &addon); + _chain->SetFilter(i, *temp, addon); + + delete temp; + } + + _chain->Save(); + } + + ResetProgress(); + break; + } + } +} + + +bool +BMailChainRunner::QuitRequested() +{ + Stop(); + return true; +} + + +void +BMailChainRunner::get_messages(BStringList *list) +{ + const char *uid; + + status_t err = B_OK; + const char *glort; + bool using_tmp = (_chain->MetaData()->FindString("path",&glort) < B_OK); + + BDirectory tmp(using_tmp ? "/tmp" : glort); + + for (int i = 0; i < list->CountItems(); i++) { + uid = (*list)[i]; + + char *path; + if (using_tmp) + path = tempnam("/tmp","mail_temp_"); + else { + BPath pathy(glort); + pathy.Append("Downloading"); + path = (char *)malloc(B_PATH_NAME_LENGTH); + sprintf(path,"%s (%s: %ld)...",pathy.Path(), _chain->Name(), _chain->ID()); + } + + BEntry *entry = new BEntry(path); + free(path); + BPositionIO *file = (_chain->ChainDirection() == inbound) ? new BFile(entry, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE) : NULL; + BPath *folder = new BPath; + BMessage *headers = new BMessage; + + for (int32 j = 0; j < addons.CountItems(); j++) { + struct filter_image *current = (struct filter_image *)(addons.ItemAt(j)); + + err = current->filter->ProcessMailMessage(&file,entry,headers,folder,uid); + + if (err != B_OK) + break; + } + + CallCallbacksFor(message_cb, err); + + if (err == B_MAIL_DISCARD) + entry->Remove(); + + if (file != NULL) + delete file; + + delete entry; + delete headers; + delete folder; + + if (err == B_MAIL_END_FETCH || err == B_MAIL_END_CHAIN) + break; + } + + CallCallbacksFor(process_cb, err); + + if (save_chain) { + + entry_ref addon; + BMessage settings; + for (int32 i = 0; i < addons.CountItems(); i++) { + filter_image *image = (filter_image *)(addons.ItemAt(i)); + + BMessage *temp = new BMessage(*(image->settings)); + temp->RemoveName("chain"); + _chain->GetFilter(i,&settings,&addon); + _chain->SetFilter(i,*temp,addon); + + delete temp; + } + + _chain->Save(); + } + + ResetProgress(); + + if (err == B_MAIL_END_CHAIN) + Stop(); + +} + + +void +BMailChainRunner::Stop() +{ + PostMessage('STOP'); +} + + +void +BMailChainRunner::GetMessages(BStringList *list, int32 bytes) +{ + if (list->CountItems() < 1) + return; + + BMessage msg('GETM'); + msg.AddFlat("messages",list); + msg.AddInt32("bytes",bytes); + PostMessage(&msg); +} + + +void +BMailChainRunner::GetSingleMessage(const char *uid, int32 length, BPath *into) +{ + BMessage msg('GTSM'); + msg.AddString("uid",uid); + msg.AddInt32("bytes",length); + msg.AddString("into",into->Path()); + PostMessage(&msg); +} + + +void +BMailChainRunner::ReportProgress(int bytes, int messages, const char *message) +{ + if (bytes != 0) + _statview->AddProgress(bytes); + + for (int i = 0; i < messages; i++) + _statview->AddItem(); + + if (message != NULL) + _statview->SetMessage(message); +} + + +void +BMailChainRunner::ResetProgress(const char *message) +{ + _statview->Reset(); + if (message != NULL) + _statview->SetMessage(message); +} + + +void +BMailChainRunner::ShowError(const char *error) +{ + BString tag = Chain()->Name(); + tag << ": "; + show_error(B_WARNING_ALERT, error, tag.String()); +} + + +void +BMailChainRunner::ShowMessage(const char *error) +{ + BString tag = Chain()->Name(); + tag << ": "; + show_error(B_INFO_ALERT, error, tag.String()); +} diff --git a/src/kits/mail/ErrorLogWindow.cpp b/src/kits/mail/ErrorLogWindow.cpp new file mode 100644 index 0000000000..c2d7467ef7 --- /dev/null +++ b/src/kits/mail/ErrorLogWindow.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include + +#include "ErrorLogWindow.h" + +rgb_color white = {255,255,255,255}; +rgb_color notwhite = {255,255,200,255}; + +class Error : public BView { + public: + Error(BRect rect,alert_type type,const char *tag,const char *message,bool timestamp,rgb_color bkg); + + void GetPreferredSize(float *width, float *height); + void Draw(BRect updateRect); + void FrameResized(float w, float h); + private: + alert_type type; +}; + +class ErrorPanel : public BView { + public: + ErrorPanel(BRect rect) : BView(rect,"ErrorScrollPanel",B_FOLLOW_ALL_SIDES,B_DRAW_ON_CHILDREN | B_FRAME_EVENTS), alerts_displayed(0), add_next_at(0) {} + + void GetPreferredSize(float *width, float *height) { + *width = Bounds().Width(); + *height = add_next_at; + } + + void TargetedByScrollView(BScrollView *scroll_view) { scroll = scroll_view; /*scroll->ScrollBar(B_VERTICAL)->SetRange(0,add_next_at);*/ } + void FrameResized(float w, float /*h*/) { + if (w == Frame().Width()) + return; + + add_next_at = 0; + for (int32 i = 0; i < CountChildren(); i++) { + ChildAt(i)->MoveTo(BPoint(0,add_next_at)); + ChildAt(i)->ResizeTo(w,ChildAt(i)->Frame().Height()); + ChildAt(i)->ResizeToPreferred(); + add_next_at += ChildAt(i)->Bounds().Height(); + } + ResizeTo(w,add_next_at); + } + + int32 alerts_displayed; + float add_next_at; + BScrollView *scroll; +}; + + +// #pragma mark - + + +ErrorLogWindow::ErrorLogWindow(BRect rect, const char *name, window_type type) + : BWindow(rect, name, type, + B_NO_WORKSPACE_ACTIVATION | B_NOT_MINIMIZABLE | B_ASYNCHRONOUS_CONTROLS) +{ + rect = Bounds(); + rect.right -= B_V_SCROLL_BAR_WIDTH; + + view = new ErrorPanel(rect); + AddChild(new BScrollView("ErrorScroller", view, B_FOLLOW_ALL_SIDES, 0, false, true)); +} + + +void +ErrorLogWindow::AddError(alert_type type, const char *message, const char *tag, bool timestamp) +{ + ErrorPanel *panel = (ErrorPanel *)view; + + Lock(); + + Error *newError = new Error(BRect(0, panel->add_next_at, panel->Bounds().right, + panel->add_next_at + 1), type, tag, message, timestamp, + (panel->alerts_displayed++ % 2 == 0) ? white : notwhite); + + newError->ResizeToPreferred(); + panel->add_next_at += newError->Bounds().Height(); + panel->AddChild(newError); + panel->ResizeToPreferred(); + + if (panel->add_next_at > Frame().Height()) { + BScrollBar *bar = panel->scroll->ScrollBar(B_VERTICAL); + + bar->SetRange(0, panel->add_next_at - Frame().Height()); + bar->SetSteps(1, Frame().Height()); + bar->SetProportion(Frame().Height() / panel->add_next_at); + } else + panel->scroll->ScrollBar(B_VERTICAL)->SetRange(0,0); + + if (IsHidden()) + Show(); + + Unlock(); +} + + +bool +ErrorLogWindow::QuitRequested() +{ + Hide(); + + while (view->CountChildren() != 0) + view->RemoveChild(view->ChildAt(0)); + + ErrorPanel *panel = (ErrorPanel *)(view); + panel->add_next_at = 0; + panel->alerts_displayed = 0; + + view->ResizeToPreferred(); + return false; +} + + +void +ErrorLogWindow::FrameResized(float newWidth, float newHeight) +{ + ErrorPanel *panel = (ErrorPanel *)view; + panel->ResizeTo(newWidth, panel->add_next_at); + panel->FrameResized(newWidth - B_V_SCROLL_BAR_WIDTH, panel->add_next_at); + panel->Invalidate(); + + if (panel->add_next_at > newHeight) { + BScrollBar *bar = panel->scroll->ScrollBar(B_VERTICAL); + + bar->SetRange(0, panel->add_next_at - Frame().Height()); + bar->SetSteps(1, Frame().Height()); + bar->SetProportion(Frame().Height() / panel->add_next_at); + } else + panel->scroll->ScrollBar(B_VERTICAL)->SetRange(0,0); +} + + +// #pragma mark - + + +Error::Error(BRect rect,alert_type atype,const char *tag,const char *message,bool timestamp,rgb_color bkg) : BView(rect,"error",B_FOLLOW_LEFT | B_FOLLOW_RIGHT | B_FOLLOW_TOP,B_NAVIGABLE | B_WILL_DRAW | B_FRAME_EVENTS), type(atype) { + SetViewColor(bkg); + SetLowColor(bkg); + + text_run_array array; + array.count = 1; + array.runs[0].offset = 0; + array.runs[0].font = *be_bold_font; + array.runs[0].color = HighColor(); + + BTextView *view = new BTextView(BRect(20,0,rect.Width(),rect.Height()),"error_display",BRect(0,3,rect.Width() - 20 - 3,LONG_MAX),B_FOLLOW_ALL_SIDES); + view->SetLowColor(bkg); + view->SetViewColor(bkg); + view->SetText(message); + view->MakeSelectable(true); + view->SetStylable(true); + view->MakeEditable(false); + + if (tag != NULL) + view->Insert(0,tag,strlen(tag),&array); + + if (timestamp) { + array.runs[0].color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),B_DARKEN_2_TINT); + array.runs[0].font.SetSize(9); + time_t thetime = time(NULL); + BString atime = asctime(localtime(&thetime)); + atime.Prepend(" ["); + atime.RemoveAll("\n"); + atime.Append("]"); + view->Insert(view->TextLength(),atime.String(),atime.Length(),&array); + } + + float height,width; + width = view->Frame().Width(); + height = view->TextHeight(0,view->CountLines()) + 3; + view->ResizeTo(width,height); + AddChild(view); +} + + +void +Error::GetPreferredSize(float *width, float *height) +{ + BTextView *view = static_cast(FindView("error_display")); + + *width = view->Frame().Width() + 20; + *height = view->TextHeight(0, LONG_MAX) + 3; +} + + +void +Error::Draw(BRect updateRect) +{ + FillRect(updateRect, B_SOLID_LOW); +} + + +void +Error::FrameResized(float w, float h) +{ + BTextView *view = static_cast(FindView("error_display")); + + view->ResizeTo(w - 20, h); + view->SetTextRect(BRect(0, 3, w - 20, h)); +} diff --git a/src/kits/mail/ErrorLogWindow.h b/src/kits/mail/ErrorLogWindow.h new file mode 100644 index 0000000000..0f8ca3f43b --- /dev/null +++ b/src/kits/mail/ErrorLogWindow.h @@ -0,0 +1,20 @@ +#ifndef ZOIDBERG_MAIL_ERRORLOGWINDOW_H +#define ZOIDBERG_MAIL_ERRORLOGWINDOW_H + +#include +#include + +class ErrorLogWindow : public BWindow { + public: + ErrorLogWindow(BRect rect, const char *name, window_type type); + + void AddError(alert_type type,const char *message,const char *tag = NULL,bool timestamp = true); + + bool QuitRequested(); + void FrameResized(float new_width, float new_height); + + private: + BView *view; +}; + +#endif // ZOIDBERG_MAIL_ERRORLOGWINDOW_H diff --git a/src/kits/mail/FileConfigView.cpp b/src/kits/mail/FileConfigView.cpp new file mode 100644 index 0000000000..52a77f82ff --- /dev/null +++ b/src/kits/mail/FileConfigView.cpp @@ -0,0 +1,168 @@ +/* BMailFileConfigView - a file configuration view for filters +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +#include + +class _EXPORT BFileControl; +class _EXPORT BMailFileConfigView; + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +const uint32 kMsgSelectButton = 'fsel'; + +BFileControl::BFileControl(BRect rect,const char *name,const char *label,const char *pathOfFile,uint32 flavors) + : BView(rect,name,B_FOLLOW_LEFT | B_FOLLOW_TOP,0) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + // determine font height + font_height fontHeight; + GetFontHeight(&fontHeight); + float itemHeight = (int32)(fontHeight.ascent + fontHeight.descent + fontHeight.leading) + 13; + float labelWidth = StringWidth("Select" B_UTF8_ELLIPSIS) + 20; + rect = Bounds(); + rect.right -= labelWidth; + rect.top = 4; rect.bottom = itemHeight + 2; + fText = new BTextControl(rect,"file_path",label,pathOfFile,NULL); + if (label) + fText->SetDivider(fText->StringWidth(label) + 6); + AddChild(fText); + + rect.left = rect.right + 6; + rect.right += labelWidth; + rect.OffsetBy(0,-3); + fButton = new BButton(rect,"select_file",MDR_DIALECT_CHOICE ("Select","選択") B_UTF8_ELLIPSIS,new BMessage(kMsgSelectButton)); + AddChild(fButton); + + fPanel = new BFilePanel(B_OPEN_PANEL,NULL,NULL,flavors,false); + + ResizeToPreferred(); +} + + +BFileControl::~BFileControl() +{ + delete fPanel; +} + + +void BFileControl::AttachedToWindow() +{ + fButton->SetTarget(this); + + BMessenger messenger(this); + if (messenger.IsValid()) + fPanel->SetTarget(messenger); +} + + +void BFileControl::MessageReceived(BMessage *msg) +{ + switch (msg->what) + { + case kMsgSelectButton: + { + fPanel->Hide(); + //fPanel->Window()->SetTitle(title); + + BPath path(fText->Text()); + if (path.InitCheck() >= B_OK) + if (path.GetParent(&path) >= B_OK) + fPanel->SetPanelDirectory(path.Path()); + + fPanel->Show(); + break; + } + case B_REFS_RECEIVED: + { + entry_ref ref; + if (msg->FindRef("refs",&ref) >= B_OK) + { + BEntry entry(&ref); + if (entry.InitCheck() >= B_OK) + { + BPath path; + entry.GetPath(&path); + + fText->SetText(path.Path()); + } + } + break; + } + default: + BView::MessageReceived(msg); + break; + } +} + + +void BFileControl::SetText(const char *pathOfFile) +{ + fText->SetText(pathOfFile); +} + + +const char *BFileControl::Text() const +{ + return fText->Text(); +} + + +void BFileControl::SetEnabled(bool enabled) +{ + fText->SetEnabled(enabled); + fButton->SetEnabled(enabled); +} + + +void BFileControl::GetPreferredSize(float *width, float *height) +{ + *width = fButton->Frame().right + 5; + *height = fText->Bounds().Height() + 8; +} + + +//-------------------------------------------------------------------------- +// #pragma mark - + +BMailFileConfigView::BMailFileConfigView(const char *label,const char *name,bool useMeta,const char *defaultPath,uint32 flavors) + : BFileControl(BRect(5,0,255,10),name,label,defaultPath,flavors), + fUseMeta(useMeta), + fName(name) +{ +} + + +void BMailFileConfigView::SetTo(BMessage *archive, BMessage *meta) +{ + fMeta = meta; + BString path = (fUseMeta ? meta : archive)->FindString(fName); + + if (path != "") + SetText(path.String()); +} + + +status_t BMailFileConfigView::Archive(BMessage *into, bool /*deep*/) const +{ + const char *path = Text(); + BMessage *archive = fUseMeta ? fMeta : into; + + if (archive->ReplaceString(fName,path) != B_OK) + archive->AddString(fName,path); + + return B_OK; +} + diff --git a/src/kits/mail/Jamfile b/src/kits/mail/Jamfile new file mode 100644 index 0000000000..92c4664f06 --- /dev/null +++ b/src/kits/mail/Jamfile @@ -0,0 +1,49 @@ +SubDir OBOS_TOP src kits mail ; + +UsePrivateHeaders mail ; + +SubDirHdrs [ FDirName $(OBOS_TOP) headers os add-ons mail_daemon ] ; + +if $(CHECK_MALLOC) { + SubDirC++Flags -D_NO_INLINE_ASM -fcheck-memory-usage ; +} + +SubDirC++Flags -D_BUILDING_mail=1 -DUSE_NASTY_SYNC_THREAD_HACK=1 ; + +SharedLibrary mail : + b_mail_message.cpp + c_mail_api.cpp + ChainRunner.cpp + cpp_abi_base64.c + crypt.cpp + des.c + ErrorLogWindow.cpp + FileConfigView.cpp + mail_encoding.c + mail_util.cpp + MailAddon.cpp + MailAttachment.cpp + MailChain.cpp + MailComponent.cpp + MailContainer.cpp + MailDaemon.cpp + MailMessage.cpp + MailProtocol.cpp + MailSettings.cpp + NodeMessage.cpp + numailkit.cpp + ProtocolConfigView.cpp + RemoteStorageProtocol.cpp + StatusWindow.cpp + StringList.cpp +; + +LinkSharedOSLibs libmail.so : + be + textencoding + tracker + stdc++.r4 +; + +MakeLocate libmail.so : $(OBOS_STLIB_DIR) ; +RelSymLink libmail.so : libmail.so ; diff --git a/src/kits/mail/MailAddon.cpp b/src/kits/mail/MailAddon.cpp new file mode 100644 index 0000000000..638baf74ab --- /dev/null +++ b/src/kits/mail/MailAddon.cpp @@ -0,0 +1,27 @@ +/* Filter - the base class for all mail filters +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +class _EXPORT BMailFilter; + +#include + +BMailFilter::BMailFilter(BMessage *) +{ + //----do nothing----- +} + +BMailFilter::~BMailFilter() +{ +} + + +void BMailFilter::_ReservedFilter1() {} +void BMailFilter::_ReservedFilter2() {} +void BMailFilter::_ReservedFilter3() {} +void BMailFilter::_ReservedFilter4() {} + diff --git a/src/kits/mail/MailAttachment.cpp b/src/kits/mail/MailAttachment.cpp new file mode 100644 index 0000000000..42b6b0e838 --- /dev/null +++ b/src/kits/mail/MailAttachment.cpp @@ -0,0 +1,674 @@ +/* BMailAttachment - classes which handle mail attachments +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include + +class _EXPORT BSimpleMailAttachment; +class _EXPORT BAttributedMailAttachment; +class _EXPORT BMailAttachment; + +#include +#include +#include + +//--------------BSimpleMailAttachment-No attributes or awareness of the file system at large----- + +BSimpleMailAttachment::BSimpleMailAttachment() + : BMailAttachment(), + fStatus(B_NO_INIT), + _data(NULL), + _raw_data(NULL), + _we_own_data(false) +{ + Initialize(base64); +} + +BSimpleMailAttachment::BSimpleMailAttachment(BPositionIO *data, mail_encoding encoding) + : BMailAttachment(), + _data(data), + _raw_data(NULL), + _we_own_data(false) +{ + fStatus = data == NULL ? B_BAD_VALUE : B_OK; + + Initialize(encoding); +} + +BSimpleMailAttachment::BSimpleMailAttachment(const void *data, size_t length, mail_encoding encoding) + : BMailAttachment(), + _data(new BMemoryIO(data,length)), + _raw_data(NULL), + _we_own_data(true) +{ + fStatus = data == NULL ? B_BAD_VALUE : B_OK; + + Initialize(encoding); +} + +BSimpleMailAttachment::BSimpleMailAttachment(BFile *file, bool delete_when_done) + : BMailAttachment(), + _data(NULL), + _raw_data(NULL), + _we_own_data(false) +{ + Initialize(base64); + SetTo(file,delete_when_done); +} + +BSimpleMailAttachment::BSimpleMailAttachment(entry_ref *ref) + : BMailAttachment(), + _data(NULL), + _raw_data(NULL), + _we_own_data(false) +{ + Initialize(base64); + SetTo(ref); +} + +BSimpleMailAttachment::~BSimpleMailAttachment() +{ + if (_we_own_data) + delete _data; +} + +void BSimpleMailAttachment::Initialize(mail_encoding encoding) +{ + SetEncoding(encoding); + SetHeaderField("Content-Disposition","BMailAttachment"); +} + +status_t BSimpleMailAttachment::SetTo(BFile *file, bool delete_file_when_done) +{ + char type[B_MIME_TYPE_LENGTH] = "application/octet-stream"; + + BNodeInfo nodeInfo(file); + if (nodeInfo.InitCheck() == B_OK) + nodeInfo.GetType(type); + + SetHeaderField("Content-Type",type); + //---No way to get file name (see SetTo(entry_ref *)) + //SetFileName(ref->name); + + if (delete_file_when_done) + SetDecodedDataAndDeleteWhenDone(file); + else + SetDecodedData(file); + + return fStatus = B_OK; +} + +status_t BSimpleMailAttachment::SetTo(entry_ref *ref) +{ + BFile *file = new BFile(ref,B_READ_ONLY); + + if ((fStatus = file->InitCheck()) < B_OK) + { + delete file; + return fStatus; + } + if (SetTo(file,true) < B_OK) + // fStatus is set by SetTo() + return fStatus; + + SetFileName(ref->name); + return fStatus = B_OK; +} + +status_t BSimpleMailAttachment::InitCheck() +{ + return fStatus; +} + +status_t BSimpleMailAttachment::FileName(char *text) { + BMessage content_type; + HeaderField("Content-Type",&content_type); + + const char *fileName = content_type.FindString("name"); + if (!fileName) + fileName = content_type.FindString("filename"); + if (!fileName) + { + content_type.MakeEmpty(); + HeaderField("Content-Disposition",&content_type); + fileName = content_type.FindString("name"); + } + if (!fileName) + fileName = content_type.FindString("filename"); + if (!fileName) + { + content_type.MakeEmpty(); + HeaderField("Content-Location",&content_type); + fileName = content_type.FindString("unlabeled"); + } + if (!fileName) + return B_NAME_NOT_FOUND; + + strncpy(text,fileName,B_FILE_NAME_LENGTH); + return B_OK; +} + + +void BSimpleMailAttachment::SetFileName(const char *name) { + BMessage content_type; + + HeaderField("Content-Type",&content_type); + + if (content_type.ReplaceString("name",name) != B_OK) + content_type.AddString("name",name); + + // Request that the file name header be encoded in UTF-8 if it has weird + // characters. If it is just a plain name, the header will appear normal. + if (content_type.ReplaceInt32(kHeaderCharsetString, B_MAIL_UTF8_CONVERSION) != B_OK) + content_type.AddInt32(kHeaderCharsetString, B_MAIL_UTF8_CONVERSION); + + SetHeaderField ("Content-Type", &content_type); +} + + +status_t +BSimpleMailAttachment::GetDecodedData(BPositionIO *data) +{ + ParseNow(); + + if (!_data) + return B_IO_ERROR; + if (data == NULL) + return B_BAD_VALUE; + + char buffer[256]; + ssize_t length; + _data->Seek(0,SEEK_SET); + + while ((length = _data->Read(buffer,sizeof(buffer))) > 0) + data->Write(buffer,length); + + return B_OK; +} + + +BPositionIO * +BSimpleMailAttachment::GetDecodedData() +{ + ParseNow(); + + return _data; +} + +status_t BSimpleMailAttachment::SetDecodedDataAndDeleteWhenDone(BPositionIO *data) { + _raw_data = NULL; + + if (_we_own_data) + delete _data; + + _data = data; + _we_own_data = true; + + return B_OK; +} + +status_t BSimpleMailAttachment::SetDecodedData(BPositionIO *data) { + _raw_data = NULL; + + if (_we_own_data) + delete _data; + + _data = data; + _we_own_data = false; + + return B_OK; +} + +status_t BSimpleMailAttachment::SetDecodedData(const void *data, size_t length) { + _raw_data = NULL; + + if (_we_own_data) + delete _data; + + _data = new BMemoryIO(data,length); + _we_own_data = true; + + return B_OK; +} + +void BSimpleMailAttachment::SetEncoding(mail_encoding encoding) { + _encoding = encoding; + + char *cte = NULL; //--Content Transfer Encoding + switch (_encoding) { + case base64: + cte = "base64"; + break; + case seven_bit: + case no_encoding: + cte = "7bit"; + break; + case eight_bit: + cte = "8bit"; + break; + case uuencode: + cte = "uuencode"; + break; + case quoted_printable: + cte = "quoted-printable"; + break; + default: + cte = "bug-not-implemented"; + break; + } + + SetHeaderField("Content-Transfer-Encoding",cte); +} + +mail_encoding BSimpleMailAttachment::Encoding() { + return _encoding; +} + +status_t BSimpleMailAttachment::SetToRFC822(BPositionIO *data, size_t length, bool parse_now) { + //---------Massive memory squandering!---ALERT!---------- + if (_we_own_data) + delete _data; + + off_t position = data->Position(); + BMailComponent::SetToRFC822(data,length,parse_now); + + // this actually happens... + if (data->Position() - position > length) + return B_ERROR; + + length -= (data->Position() - position); + + _raw_data = data; + _raw_length = length; + _raw_offset = data->Position(); + + BString encoding = HeaderField("Content-Transfer-Encoding"); + if (encoding.IFindFirst("base64") >= 0) + _encoding = base64; + else if (encoding.IFindFirst("quoted-printable") >= 0) + _encoding = quoted_printable; + else if (encoding.IFindFirst("uuencode") >= 0) + _encoding = uuencode; + else if (encoding.IFindFirst("7bit") >= 0) + _encoding = seven_bit; + else if (encoding.IFindFirst("8bit") >= 0) + _encoding = eight_bit; + else + _encoding = no_encoding; + + if (parse_now) + ParseNow(); + + return B_OK; +} + +void BSimpleMailAttachment::ParseNow() { + if (_raw_data == NULL || _raw_length == 0) + return; + + _raw_data->Seek(_raw_offset,SEEK_SET); + + char *src = (char *)malloc(_raw_length); + size_t size = _raw_length; + + size = _raw_data->Read(src,_raw_length); + + BMallocIO *buffer = new BMallocIO; + buffer->SetSize(size); //-------8bit is *always* more efficient than an encoding, so the buffer will *never* be larger than before + + size = decode(_encoding,(char *)(buffer->Buffer()),src,size,0); + free(src); + + buffer->SetSize(size); + + _data = buffer; + _we_own_data = true; + + _raw_data = NULL; + + return; +} + +status_t BSimpleMailAttachment::RenderToRFC822(BPositionIO *render_to) { + BMailComponent::RenderToRFC822(render_to); + //---------Massive memory squandering!---ALERT!---------- + // now with error checks, dumb :-) -- axeld. + + _data->Seek(0,SEEK_END); + off_t size = _data->Position(); + char *src = (char *)malloc(size); + if (src == NULL) + return B_NO_MEMORY; + + _data->Seek(0,SEEK_SET); + + ssize_t read = _data->Read(src,size); + if (read < B_OK) + return read; // Return an error code and leak memory. + + // The encoded text will never be more than twice as large with any + // conceivable encoding. But just in case, there's a function call which + // will tell us how much space is needed. + ssize_t destSize = max_encoded_length(_encoding,read); + if (destSize < B_OK) // Invalid encodings like uuencode rejected here. + return destSize; + char *dest = (char *)malloc(destSize); + if (dest == NULL) + return B_NO_MEMORY; + + destSize = encode (_encoding, dest, src, read, false /* headerMode */); + if (destSize < B_OK) + return destSize; + if (destSize > 0) + read = render_to->Write(dest,destSize); + free (src); + free (dest); + return (read > 0) ? B_OK : read; +} + + +//-------BAttributedMailAttachment--Awareness of bfs, sends attributes-- +// #pragma mark - + + +BAttributedMailAttachment::BAttributedMailAttachment() + : BMailAttachment(), + fContainer(NULL), + fStatus(B_NO_INIT), + _data(NULL), + _attributes_attach(NULL) +{ +} + +BAttributedMailAttachment::BAttributedMailAttachment(BFile *file, bool delete_when_done) + : BMailAttachment(), + fContainer(NULL), + _data(NULL), + _attributes_attach(NULL) +{ + SetTo(file,delete_when_done); +} + +BAttributedMailAttachment::BAttributedMailAttachment(entry_ref *ref) + : BMailAttachment(), + fContainer(NULL), + _data(NULL), + _attributes_attach(NULL) +{ + SetTo(ref); +} + +BAttributedMailAttachment::~BAttributedMailAttachment() { + // Our SimpleAttachments are deleted by fContainer + delete fContainer; +} + + +status_t BAttributedMailAttachment::Initialize() +{ + // _data & _attributes_attach will be deleted by the container + if (fContainer != NULL) + delete fContainer; + + fContainer = new BMIMEMultipartMailContainer("++++++BFile++++++"); + + _data = new BSimpleMailAttachment(); + fContainer->AddComponent(_data); + + _attributes_attach = new BSimpleMailAttachment(); + _attributes.MakeEmpty(); + _attributes_attach->SetHeaderField("Content-Type","application/x-be_attribute; name=\"BeOS Attributes\""); + fContainer->AddComponent(_attributes_attach); + + fContainer->SetHeaderField("Content-Type","multipart/x-bfile"); + fContainer->SetHeaderField("Content-Disposition","BMailAttachment"); + + // also set the header fields of this component, in case someone asks + SetHeaderField("Content-Type","multipart/x-bfile"); + SetHeaderField("Content-Disposition","BMailAttachment"); + + return B_OK; +} + + +status_t BAttributedMailAttachment::SetTo(BFile *file, bool delete_file_when_done) +{ + if (file == NULL) + return fStatus = B_BAD_VALUE; + + if ((fStatus = Initialize()) < B_OK) + return fStatus; + + _attributes << *file; + + if ((fStatus = _data->SetTo(file,delete_file_when_done)) < B_OK) + return fStatus; + + // Set boundary + + //---Also, we have the make up the boundary out of whole cloth + //------This is likely to give a completely random string--- + BString boundary; + boundary << "BFile--" << (int32(file) ^ time(NULL)) << "-" << ~((int32)file ^ (int32)&fStatus ^ (int32)&_attributes) << "--"; + fContainer->SetBoundary(boundary.String()); + + return fStatus = B_OK; +} + +status_t BAttributedMailAttachment::SetTo(entry_ref *ref) +{ + if (ref == NULL) + return fStatus = B_BAD_VALUE; + + if ((fStatus = Initialize()) < B_OK) + return fStatus; + + BNode node(ref); + if ((fStatus = node.InitCheck()) < B_OK) + return fStatus; + + _attributes << node; + + if ((fStatus = _data->SetTo(ref)) < B_OK) + return fStatus; + + // Set boundary + + //------This is likely to give a completely random string--- + BString boundary; + char buffer[512]; + strcpy(buffer, ref->name); + for (int32 i = strlen(buffer);i-- > 0;) + { + if (buffer[i] & 0x80) + buffer[i] = 'x'; + else if (buffer[i] == ' ' || buffer[i] == ':') + buffer[i] = '_'; + } + buffer[32] = '\0'; + boundary << "BFile-" << buffer << "--" << ((int32)_data ^ time(NULL)) << "-" << ~((int32)_data ^ (int32)&buffer ^ (int32)&_attributes) << "--"; + fContainer->SetBoundary(boundary.String()); + + return fStatus = B_OK; +} + +status_t BAttributedMailAttachment::InitCheck() +{ + return fStatus; +} + +void BAttributedMailAttachment::SaveToDisk(BEntry *entry) { + BString path = "/tmp/"; + char name[255] = ""; + _data->FileName(name); + path << name; + + BFile file(path.String(),B_READ_WRITE | B_CREATE_FILE); + (BNode&)file << _attributes; + _data->GetDecodedData(&file); + file.Sync(); + + entry->SetTo(path.String()); +} + +void BAttributedMailAttachment::SetEncoding(mail_encoding encoding) { + _data->SetEncoding(encoding); + if (_attributes_attach != NULL) + _attributes_attach->SetEncoding(encoding); +} + +mail_encoding BAttributedMailAttachment::Encoding() { + return _data->Encoding(); +} + +status_t BAttributedMailAttachment::FileName(char *name) { + return _data->FileName(name); +} + +void BAttributedMailAttachment::SetFileName(const char *name) { + _data->SetFileName(name); +} + +status_t BAttributedMailAttachment::GetDecodedData(BPositionIO *data) { + BNode *node = dynamic_cast(data); + if (node != NULL) + *node << _attributes; + + _data->GetDecodedData(data); + return B_OK; +} + +status_t BAttributedMailAttachment::SetDecodedData(BPositionIO *data) { + BNode *node = dynamic_cast(data); + if (node != NULL) + _attributes << *node; + + _data->SetDecodedData(data); + return B_OK; +} + +status_t BAttributedMailAttachment::SetToRFC822(BPositionIO *data, size_t length, bool parse_now) +{ + status_t err = Initialize(); + if (err < B_OK) + return err; + + err = fContainer->SetToRFC822(data,length,parse_now); + if (err < B_OK) + return err; + + BMimeType type; + fContainer->MIMEType(&type); + if (strcmp(type.Type(),"multipart/x-bfile") != 0) + return B_BAD_TYPE; + + // get data and attributes + if ((_data = dynamic_cast(fContainer->GetComponent(0))) == NULL) + return B_BAD_VALUE; + if (parse_now) + _data->GetDecodedData(); // Force it to make a copy of the data. Needed for forwarding messages hack. + + if ((_attributes_attach = dynamic_cast(fContainer->GetComponent(1))) == NULL + || _attributes_attach->GetDecodedData() == NULL) + return B_OK; + + // Convert the attribute binary attachment into a convenient easy to use BMessage. + + int32 len = ((BMallocIO *)(_attributes_attach->GetDecodedData()))->BufferLength(); + char *start = (char *)malloc(len); + if (start == NULL) + return B_NO_MEMORY; + + if (_attributes_attach->GetDecodedData()->ReadAt(0,start,len) < len) + return B_IO_ERROR; + + int32 index = 0; + while (index < len) { + char *name = &start[index]; + index += strlen(name) + 1; + + type_code code; + memcpy(&code, &start[index], sizeof(type_code)); + code = B_BENDIAN_TO_HOST_INT32(code); + index += sizeof(type_code); + + int64 buf_length; + memcpy(&buf_length, &start[index], sizeof(buf_length)); + buf_length = B_BENDIAN_TO_HOST_INT64(buf_length); + index += sizeof(buf_length); + + swap_data(code, &start[index], buf_length, B_SWAP_BENDIAN_TO_HOST); + _attributes.AddData(name, code, &start[index], buf_length); + index += buf_length; + } + free(start); + + return B_OK; +} + +status_t BAttributedMailAttachment::RenderToRFC822(BPositionIO *render_to) { + BMallocIO *io = new BMallocIO; +#if B_BEOS_VERSION_DANO + const +#endif + char *name; + type_code type, swap_typed; + for (int32 i = 0; _attributes.GetInfo(B_ANY_TYPE,i,&name,&type) == B_OK; i++) { + const void *data; + ssize_t dataLen; + _attributes.FindData(name,type,&data,&dataLen); + io->Write(name,strlen(name) + 1); + swap_typed = B_HOST_TO_BENDIAN_INT32(type); + io->Write(&swap_typed,sizeof(type_code)); + + int64 length, swapped; + length = dataLen; + swapped = B_HOST_TO_BENDIAN_INT64(length); + io->Write(&swapped,sizeof(int64)); + + void *allocd = malloc(dataLen); + if (allocd == NULL) + return B_NO_MEMORY; + memcpy(allocd,data,dataLen); + swap_data(type, allocd, dataLen, B_SWAP_HOST_TO_BENDIAN); + io->Write(allocd,dataLen); + free(allocd); + } + if (_attributes_attach == NULL) + _attributes_attach = new BSimpleMailAttachment; + + _attributes_attach->SetDecodedDataAndDeleteWhenDone(io); + + return fContainer->RenderToRFC822(render_to); +} + +status_t BAttributedMailAttachment::MIMEType(BMimeType *mime) { + return _data->MIMEType(mime); +} + + +// The reserved function stubs +// #pragma mark - + +void BMailAttachment::_ReservedAttachment1() {} +void BMailAttachment::_ReservedAttachment2() {} +void BMailAttachment::_ReservedAttachment3() {} +void BMailAttachment::_ReservedAttachment4() {} + +void BSimpleMailAttachment::_ReservedSimple1() {} +void BSimpleMailAttachment::_ReservedSimple2() {} +void BSimpleMailAttachment::_ReservedSimple3() {} + +void BAttributedMailAttachment::_ReservedAttributed1() {} +void BAttributedMailAttachment::_ReservedAttributed2() {} +void BAttributedMailAttachment::_ReservedAttributed3() {} diff --git a/src/kits/mail/MailChain.cpp b/src/kits/mail/MailChain.cpp new file mode 100644 index 0000000000..be2e2d09ba --- /dev/null +++ b/src/kits/mail/MailChain.cpp @@ -0,0 +1,432 @@ +/* BMailChain - the mail account's inbound and outbound chain +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class _EXPORT BMailChain; + +namespace MailInternal { + status_t WriteMessageFile(const BMessage& archive, const BPath& path, const char* name); +} + +#include +#include +#include + +BMailChain::BMailChain(uint32 i) + : id(i), meta_data(NULL), _err(B_OK), direction(inbound), settings_ct(0), addons_ct(0) +{ + name[0] = 0; + Reload(); +} + +BMailChain::BMailChain(BMessage* settings) + : id(settings->FindInt32("id")), meta_data(NULL), _err(B_OK), direction(inbound), settings_ct(0), addons_ct(0) +{ + name[0] = 0; + Load(settings); +} + + +BMailChain::~BMailChain() { + if (meta_data != NULL) + delete meta_data; + + for (int32 i = 0; filter_settings.ItemAt(i); i++) + delete (BMessage *)filter_settings.ItemAt(i); + + for (int32 i = 0; filter_addons.ItemAt(i); i++) + delete (entry_ref *)filter_addons.ItemAt(i); +} + +status_t BMailChain::Load(BMessage* settings) +{ + if (meta_data != NULL) + delete meta_data; + + meta_data = new BMessage; + if (settings->HasMessage("meta_data")) + settings->FindMessage("meta_data",meta_data); + + const char* n; + status_t ret = settings->FindString("name",&n); + if (ret == B_OK) strncpy(name,n,sizeof(name)); + else name[0]='\0'; + + type_code t; + settings->GetInfo("filter_settings",&t,(int32 *)(&settings_ct)); + settings->GetInfo("filter_addons",&t,(int32 *)(&addons_ct)); + if (settings_ct!=addons_ct) return B_MISMATCHED_VALUES; + + for (int i = 0;;i++) + { + BMessage *filter = new BMessage(); + entry_ref *ref = new entry_ref(); + char *addon_path; + + if (settings->FindMessage("filter_settings",i,filter) < B_OK + || (settings->FindString("filter_addons",i,(const char **)&addon_path) < B_OK + || get_ref_for_path(addon_path,ref) < B_OK) + && settings->FindRef("filter_addons",i,ref) < B_OK) + { + delete filter; + delete ref; + break; + } + + if (!filter_settings.AddItem(filter) || !filter_addons.AddItem(ref)) + break; + } + + if (filter_settings.CountItems()!=settings_ct + || filter_addons.CountItems()!=addons_ct) + return B_NO_MEMORY; + else + return B_OK; +} + +status_t BMailChain::InitCheck() const +{ + if (settings_ct!=addons_ct) + return B_MISMATCHED_VALUES; + if (filter_settings.CountItems()!=settings_ct + || filter_addons.CountItems()!=addons_ct) + return B_NO_MEMORY; + if (_err < B_OK) + return _err; + + return B_OK; +} + + +status_t BMailChain::Archive(BMessage* archive, bool deep) const +{ + status_t ret = B_OK; + + ret = InitCheck(); + if ((ret!=B_OK) && (ret != B_FILE_ERROR)) return ret; + + ret = BArchivable::Archive(archive,deep); + if (ret!=B_OK) return ret; + + ret = archive->AddString("class","BMailChain"); + if (ret!=B_OK) return ret; + + + ret = archive->AddInt32("id",id); + if (ret!=B_OK) return ret; + + ret = archive->AddString("name",name); + if (ret!=B_OK) return ret; + + ret = archive->AddMessage("meta_data",meta_data); + if (ret!=B_OK) return ret; + + if (ret==B_OK && deep) + { + BMessage* settings; + entry_ref* ref; + + int32 i; + for (i = 0;((settings = (BMessage*)filter_settings.ItemAt(i)) != NULL) + && ((ref = (entry_ref*)filter_addons.ItemAt(i)) != NULL); + ++i) + { + ret = archive->AddMessage("filter_settings",settings); + if (ret < B_OK) + return ret; + + BPath path(ref); + if ((ret = path.InitCheck()) < B_OK) + return ret; + ret = archive->AddString("filter_addons",path.Path()); + if (ret < B_OK) + return ret; + } + if (i != settings_ct) + return B_MISMATCHED_VALUES; + } + + return B_OK; +} + +BArchivable* BMailChain::Instantiate(BMessage* archive) +{ + return validate_instantiation(archive, "BMailChain")? + new BMailChain(archive) : NULL; +} + +status_t BMailChain::Path(BPath *path) const +{ + status_t status = find_directory(B_USER_SETTINGS_DIRECTORY,path); + if (status < B_OK) + { + fprintf(stderr, "Couldn't find user settings directory: %s\n", + strerror(status)); + return status; + } + + path->Append("Mail/chains"); + + if (ChainDirection() == outbound) + path->Append("outbound"); + else + path->Append("inbound"); + + BString leaf; + leaf << id; + path->Append(leaf.String()); + + return B_OK; +} + +status_t BMailChain::Save(bigtime_t /*timeout*/) +{ + status_t ret; + + BMessage archive; + ret = Archive(&archive,true); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't archive chain %ld: %s\n", + id, strerror(ret)); + return ret; + } + + BPath path; + if ((ret = Path(&path)) < B_OK) + return ret; + + BPath directory; + if ((ret = path.GetParent(&directory)) < B_OK) + return ret; + + return MailInternal::WriteMessageFile(archive,directory,path.Leaf()/*,timeout*/); +} + +status_t BMailChain::Delete() const +{ + status_t status; + BPath path; + if ((status = Path(&path)) < B_OK) + return status; + + BEntry entry(path.Path()); + if ((status = entry.InitCheck()) < B_OK) + return status; + + return entry.Remove(); +} + +b_mail_chain_direction BMailChain::ChainDirection() const { + return direction; +} + +void BMailChain::SetChainDirection(b_mail_chain_direction dir) { + direction = dir; +} + +status_t BMailChain::Reload() +{ + status_t ret; + + BPath path; + ret = find_directory(B_USER_SETTINGS_DIRECTORY, &path); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't find user settings directory: %s\n", + strerror(ret)); + _err = ret; + return ret; + } + + path.Append("Mail/chains"); + { + //---Determine whether chain is outbound or inbound and glean full path. + BPath working = path; + working.Append("inbound"); + BString leaf; + leaf << id; + + //puts(path.Path()); + //puts(leaf.String()); + + if (BDirectory(working.Path()).Contains(leaf.String())) { + path = working; + direction = inbound; + } else { + working = path; + working.Append("outbound"); + if (BDirectory(working.Path()).Contains(leaf.String())) { + path = working; + direction = outbound; + } + } + + //puts(path.Path()); + + path.Append(leaf.String()); + + //puts(path.Path()); + } + + // open + BFile settings(path.Path(),B_READ_ONLY); + ret = settings.InitCheck(); + + if (ret != B_OK) + { + BMessage empty; + fprintf(stderr, "Couldn't open chain settings file '%s': %s\n", + path.Path(), strerror(ret)); + Load(&empty); + _err = B_FILE_ERROR; + return ret; + } + + // read settings + BMessage tmp; + ret = tmp.Unflatten(&settings); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't read settings from '%s': %s\n", + path.Path(), strerror(ret)); + Load(&tmp); + _err = ret; + return ret; + } + + // clobber old settings + _err = ret = Load(&tmp); + return ret; +} + +uint32 BMailChain::ID() const { return id; } + +const char *BMailChain::Name() const { return name; } +status_t BMailChain::SetName(const char* n) +{ + if (n) strncpy(name,n,sizeof(name)); + else name[0]='\0'; + + return B_OK; +} + +BMessage *BMailChain::MetaData() const { + return meta_data; +} + +int32 BMailChain::CountFilters() const +{ + return filter_settings.CountItems(); +} + +status_t BMailChain::GetFilter(int32 index, BMessage* out_settings, entry_ref *addon) const +{ + if (index >= filter_settings.CountItems()) + return B_BAD_INDEX; + + BMessage *settings = (BMessage *)filter_settings.ItemAt(index); + if (settings) *out_settings = *settings; + else return B_BAD_INDEX; + + if (addon) + { + entry_ref* ref = (entry_ref* )filter_addons.ItemAt(index); + if (ref) *addon = *ref; + else return B_BAD_INDEX; + } + return B_OK; +} + +status_t BMailChain::SetFilter(int32 index, const BMessage& s, const entry_ref& addon) +{ + BMessage *settings = (BMessage *)filter_settings.ItemAt(index); + if (settings) *settings = s; + else return B_BAD_INDEX; + + entry_ref* ref = (entry_ref* )filter_addons.ItemAt(index); + if (ref) *ref = addon; + else return B_BAD_INDEX; + + return B_OK; +} + +status_t BMailChain::AddFilter(const BMessage& settings, const entry_ref& addon) +{ + BMessage *s = new BMessage(settings); + entry_ref*a = new entry_ref(addon); + + if (!filter_settings.AddItem(s)) + { + delete s; + delete a; + return B_BAD_INDEX; + } + else if (!filter_addons.AddItem(a)) + { + filter_settings.RemoveItem(settings_ct); + delete s; + delete a; + return B_BAD_INDEX; + } + // else + + ++settings_ct; + ++addons_ct; + + return B_OK; +} +status_t BMailChain::AddFilter(int32 index, const BMessage& settings, const entry_ref& addon) +{ + BMessage *s = new BMessage(settings); + entry_ref*a = new entry_ref(addon); + + if (!filter_settings.AddItem(s,index)) + { + delete s; + delete a; + return B_BAD_INDEX; + } + else if (!filter_addons.AddItem(a,index)) + { + filter_settings.RemoveItem(index); + delete s; + delete a; + return B_BAD_INDEX; + } + ++settings_ct; + ++addons_ct; + + return B_OK; +} + +status_t BMailChain::RemoveFilter(int32 index) +{ + BMessage* s = (BMessage*)filter_settings.RemoveItem(index); + delete s; + + entry_ref*a = (entry_ref*)filter_addons.RemoveItem(index); + delete a; + + --settings_ct; + --addons_ct; + + return s||a?B_OK:B_BAD_INDEX; +} + +void BMailChain::RunChain(BMailStatusWindow *window, bool async, bool save_when_done, bool delete_when_done) { + (new BMailChainRunner(this,window,true,save_when_done,delete_when_done))->RunChain(async); +} diff --git a/src/kits/mail/MailComponent.cpp b/src/kits/mail/MailComponent.cpp new file mode 100644 index 0000000000..36cb4e783d --- /dev/null +++ b/src/kits/mail/MailComponent.cpp @@ -0,0 +1,680 @@ +/* (Text)Component - message component base class and plain text +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include + +#include +#include + +class _EXPORT BMailComponent; +class _EXPORT BTextMailComponent; + +#include +#include +#include +#include + +struct CharsetConversionEntry +{ + const char *charset; + uint32 flavor; +}; + +extern const CharsetConversionEntry mail_charsets[]; + + +extern const char *kHeaderCharsetString = "header-charset"; +extern const char *kHeaderEncodingString = "header-encoding"; +// Special field names in the headers which specify the character set (int32) +// and encoding (int8) to use when converting the headers from UTF-8 to the +// output e-mail format (rfc2047). Since they are numbers, not strings, the +// extra fields won't be output. + + +BMailComponent::BMailComponent(uint32 defaultCharSet) + : _charSetForTextDecoding (defaultCharSet) +{ +} + +BMailComponent::~BMailComponent() +{ +} + +uint32 BMailComponent::ComponentType() +{ + if (NULL != dynamic_cast (this)) + return B_MAIL_ATTRIBUTED_ATTACHMENT; + + BMimeType type, super; + MIMEType(&type); + type.GetSupertype(&super); + + //---------ATT-This code *desperately* needs to be improved + if (super == "multipart") { + if (type == "multipart/x-bfile") // Not likely, they have the MIME + return B_MAIL_ATTRIBUTED_ATTACHMENT; // of their data contents. + else + return B_MAIL_MULTIPART_CONTAINER; + } else if (!IsAttachment() && (super == "text" || type.Type() == NULL)) + return B_MAIL_PLAIN_TEXT_BODY; + else + return B_MAIL_SIMPLE_ATTACHMENT; +} + +BMailComponent *BMailComponent::WhatIsThis() { + switch (ComponentType()) + { + case B_MAIL_SIMPLE_ATTACHMENT: + return new BSimpleMailAttachment; + case B_MAIL_ATTRIBUTED_ATTACHMENT: + return new BAttributedMailAttachment; + case B_MAIL_MULTIPART_CONTAINER: + return new BMIMEMultipartMailContainer (NULL, NULL, _charSetForTextDecoding); + case B_MAIL_PLAIN_TEXT_BODY: + default: + return new BTextMailComponent (NULL, _charSetForTextDecoding); + } +} + +bool BMailComponent::IsAttachment() { + const char *disposition = HeaderField("Content-Disposition"); + if ((disposition != NULL) && (strncasecmp(disposition,"Attachment",strlen("Attachment")) == 0)) + return true; + + BMessage header; + HeaderField("Content-Type",&header); + if (header.HasString("name")) + return true; + + if (HeaderField("Content-Location",&header) == B_OK) + return true; + + BMimeType type; + MIMEType(&type); + if (type == "multipart/x-bfile") + return true; + + return false; +} + + +void BMailComponent::SetHeaderField(const char *key, const char *value, uint32 charset, mail_encoding encoding, bool replace_existing) { + if (replace_existing) + headers.RemoveName(key); + if (value != NULL && value[0] != 0) // Empty or NULL strings mean delete header. + headers.AddString(key,value); + + // Latest setting of the character set and encoding to use when outputting + // the headers is the one which affects all the headers. There used to be + // separate settings for each item in the headers, but it never actually + // worked (can't store multiple items of different types in a BMessage). + if (charset != B_MAIL_NULL_CONVERSION && + headers.ReplaceInt32 (kHeaderCharsetString, charset) != B_OK) + headers.AddInt32 (kHeaderCharsetString, charset); + if (encoding != null_encoding && + headers.ReplaceInt8 (kHeaderEncodingString, encoding) != B_OK) + headers.AddInt8 (kHeaderEncodingString, encoding); +} + + +void BMailComponent::SetHeaderField(const char *key, BMessage *structure, bool replace_existing) { + int32 charset = B_MAIL_NULL_CONVERSION; + int8 encoding = null_encoding; + const char *unlabeled = "unlabeled"; + + if (replace_existing) + headers.RemoveName(key); + + BString value; + if (structure->HasString(unlabeled)) + value << structure->FindString(unlabeled) << "; "; + + const char *name, *sub_val; + type_code type; + for (int32 i = 0; structure->GetInfo(B_STRING_TYPE,i, + #ifndef B_BEOS_VERSION_DANO + (char**) + #endif + &name,&type) == B_OK; i++) + { + if (strcasecmp(name, unlabeled) == 0) + continue; + + structure->FindString(name, &sub_val); + value << name << '='; + if (BString(sub_val).FindFirst(' ') > 0) + value << '\"' << sub_val << "\"; "; + else + value << sub_val << "; "; + } + + value.Truncate(value.Length() - 2); //-----Remove the last "; " + + if (structure->HasInt32(kHeaderCharsetString)) + structure->FindInt32(kHeaderCharsetString, &charset); + if (structure->HasInt8(kHeaderEncodingString)) + structure->FindInt8(kHeaderEncodingString, &encoding); + + SetHeaderField(key,value.String(),(uint32) charset, (mail_encoding) encoding); +} + +const char *BMailComponent::HeaderField(const char *key, int32 index) { + const char *string = NULL; + + headers.FindString(key,index,&string); + return string; +} + +status_t BMailComponent::HeaderField(const char *key, BMessage *structure, int32 index) { + BString string = HeaderField(key,index); + if (string == "") + return B_NAME_NOT_FOUND; + + BString sub_cat,end_piece; + int32 i = 0, end = 0; + + // Break the header into parts, they're separated by semicolons, like this: + // Content-Type: multipart/mixed;boundary= "----=_NextPart_000_00AA_354DB459.5977A1CA" + // There's also white space and quotes to be removed, and even comments in + // parenthesis like this, which can appear anywhere white space is: (header comment) + + while (end < string.Length()) { + end = string.FindFirst(';',i); + if (end < 0) + end = string.Length(); + + string.CopyInto(sub_cat,i,end - i); + i = end + 1; + + //-------Trim spaces off of beginning and end of text + for (int32 h = 0; h < sub_cat.Length(); h++) { + if (!isspace(sub_cat.ByteAt(h))) { + sub_cat.Remove(0,h); + break; + } + } + for (int32 h = sub_cat.Length()-1; h >= 0; h--) { + if (!isspace(sub_cat.ByteAt(h))) { + sub_cat.Truncate(h+1); + break; + } + } + //--------Split along '=' + int32 first_equal = sub_cat.FindFirst('='); + if (first_equal >= 0) { + sub_cat.CopyInto(end_piece,first_equal+1,sub_cat.Length() - first_equal - 1); + sub_cat.Truncate(first_equal); + // Remove leading spaces from part after the equals sign. + while (isspace (end_piece.ByteAt(0))) + end_piece.Remove (0 /* index */, 1 /* number of chars */); + // Remove quote marks. + if (end_piece.ByteAt(0) == '\"') { + end_piece.Remove(0,1); + end_piece.Truncate(end_piece.Length() - 1); + } + sub_cat.ToLower(); + structure->AddString(sub_cat.String(),end_piece.String()); + } else { + structure->AddString("unlabeled",sub_cat.String()); + } + } + + return B_OK; +} + +status_t BMailComponent::RemoveHeader(const char *key) { + return headers.RemoveName(key); +} + +const char *BMailComponent::HeaderAt(int32 index) { +#if B_BEOS_VERSION_DANO + const +#endif + char *name = NULL; + type_code type; + + headers.GetInfo(B_STRING_TYPE,index,&name,&type); + return name; +} + +status_t BMailComponent::GetDecodedData(BPositionIO *) {return B_OK;} +status_t BMailComponent::SetDecodedData(BPositionIO *) {return B_OK;} + +status_t +BMailComponent::SetToRFC822(BPositionIO *data, size_t /*length*/, bool /*parse_now*/) +{ + headers.MakeEmpty(); + + // Only parse the header here + return parse_header(headers, *data); +} + + +status_t +BMailComponent::RenderToRFC822(BPositionIO *render_to) { + int32 charset = B_ISO1_CONVERSION; + int8 encoding = quoted_printable; + const char *key, *value; + char *allocd; + ssize_t amountWritten; + BString concat; + type_code stupidity_personified = B_STRING_TYPE; + int32 count = 0; + + if (headers.HasInt32 (kHeaderCharsetString)) + headers.FindInt32 (kHeaderCharsetString, &charset); + if (headers.HasInt8 (kHeaderEncodingString)) + headers.FindInt8 (kHeaderEncodingString, &encoding); + + for (int32 index = 0; headers.GetInfo(B_STRING_TYPE,index, +#ifndef B_BEOS_VERSION_DANO + (char**) +#endif + &key,&stupidity_personified,&count) == B_OK; index++) { + for (int32 g = 0; g < count; g++) { + headers.FindString(key,g,(const char **)&value); + allocd = (char *)malloc(strlen(value) + 1); + strcpy(allocd,value); + + concat << key << ": "; + concat.CapitalizeEachWord(); + + concat.Append(allocd,utf8_to_rfc2047(&allocd, strlen(value), charset, encoding)); + free(allocd); + FoldLineAtWhiteSpaceAndAddCRLF (concat); + + amountWritten = render_to->Write(concat.String(), concat.Length()); + if (amountWritten < 0) + return amountWritten; // IO error happened, usually disk full. + concat = ""; + } + } + + render_to->Write("\r\n", 2); + + return B_OK; +} + + +status_t BMailComponent::MIMEType(BMimeType *mime) { + bool foundBestHeader; + const char *boundaryString; + unsigned int i; + BMessage msg; + const char *typeAsString = NULL; + char typeAsLowerCaseString [B_MIME_TYPE_LENGTH]; + + // Find the best Content-Type header to use. There should really be just + // one, but evil spammers sneakily insert one for multipart (with no + // boundary string), then one for text/plain. We'll scan through them and + // only use the multipart one if there are no others, and it has a + // boundary. + + foundBestHeader = false; + for (i = 0; msg.MakeEmpty(), HeaderField("Content-Type", &msg, i) == B_OK; i++) { + typeAsString = msg.FindString("unlabeled"); + if (typeAsString != NULL && strncasecmp (typeAsString, "multipart", 9) != 0) { + foundBestHeader = true; + break; + } + } + if (!foundBestHeader) { + for (i = 0; msg.MakeEmpty(), HeaderField("Content-Type", &msg, i) == B_OK; i++) { + typeAsString = msg.FindString("unlabeled"); + if (typeAsString != NULL && strncasecmp (typeAsString, "multipart", 9) == 0) { + boundaryString = msg.FindString("boundary"); + if (boundaryString != NULL && strlen (boundaryString) > 0) { + foundBestHeader = true; + break; + } + } + } + } + // At this point we have the good MIME type in typeAsString, but only if + // foundBestHeader is true. + + if (!foundBestHeader) { + strcpy (typeAsLowerCaseString, "text/plain"); // Hope this is an OK default. + } else { + // Some extra processing to convert mixed or upper case MIME types into + // lower case, since the BeOS R5 BMimeType is case sensitive (but OpenBeOS + // isn't). Also truncate the string if it is too long. + for (i = 0; i < sizeof (typeAsLowerCaseString) - 1 && typeAsString[i] != 0; i++) + typeAsLowerCaseString[i] = tolower (typeAsString[i]); + typeAsLowerCaseString[i] = 0; + + // Some old e-mail programs saved the type as just "TEXT", which we need to + // convert to "text/plain" since the rest of the code looks for that. + if (strcmp (typeAsLowerCaseString, "text") == 0) + strcpy (typeAsLowerCaseString, "text/plain"); + } + mime->SetTo(typeAsLowerCaseString); + return B_OK; +} + + +void BMailComponent::_ReservedComponent1() {} +void BMailComponent::_ReservedComponent2() {} +void BMailComponent::_ReservedComponent3() {} +void BMailComponent::_ReservedComponent4() {} +void BMailComponent::_ReservedComponent5() {} + + +//------------------------------------------------------------------------- +// #pragma mark - + + +BTextMailComponent::BTextMailComponent(const char *text, uint32 defaultCharSet) + : BMailComponent(defaultCharSet), + encoding(quoted_printable), + charset(B_ISO1_CONVERSION), + raw_data(NULL) +{ + if (text != NULL) + SetText(text); + + SetHeaderField("MIME-Version","1.0"); +} + +BTextMailComponent::~BTextMailComponent() +{ +} + +void BTextMailComponent::SetEncoding(mail_encoding encoding, int32 charset) { + this->encoding = encoding; + this->charset = charset; +} + +void BTextMailComponent::SetText(const char *text) { + this->text.SetTo(text); + + raw_data = NULL; +} + +void BTextMailComponent::AppendText(const char *text) { + ParseRaw(); + + this->text << text; +} + +const char *BTextMailComponent::Text() { + ParseRaw(); + + return text.String(); +} + +BString *BTextMailComponent::BStringText() { + ParseRaw(); + + return &text; +} + +void BTextMailComponent::Quote(const char *message, const char *quote_style) { + ParseRaw(); + + BString string; + string << '\n' << quote_style; + text.ReplaceAll("\n",string.String()); + + string = message; + string << '\n'; + text.Prepend(string.String()); +} + +status_t BTextMailComponent::GetDecodedData(BPositionIO *data) { + ParseRaw(); + + BMimeType type; + ssize_t written; + if (MIMEType(&type) == B_OK && type == "text/plain") + written = data->Write(text.String(),text.Length()); + else + written = data->Write(decoded.String(), decoded.Length()); + + return written >= 0 ? B_OK : written; +} + +status_t BTextMailComponent::SetDecodedData(BPositionIO *data) { + char buffer[255]; + size_t buf_len; + + while ((buf_len = data->Read(buffer,254)) > 0) { + buffer[buf_len] = 0; + this->text << buffer; + } + + raw_data = NULL; + + return B_OK; +} + + +status_t +BTextMailComponent::SetToRFC822(BPositionIO *data, size_t length, bool parseNow) +{ + off_t position = data->Position(); + BMailComponent::SetToRFC822(data, length); + + // Some malformed MIME headers can have the header running into the + // boundary of the next MIME chunk, resulting in a negative length. + length -= data->Position() - position; + if ((ssize_t) length < 0) + length = 0; + + raw_data = data; + raw_length = length; + raw_offset = data->Position(); + + if (parseNow) { + // copies the data stream and sets the raw_data variable to NULL + return ParseRaw(); + } + + return B_OK; +} + + +status_t +BTextMailComponent::ParseRaw() +{ + if (raw_data == NULL) + return B_OK; + + raw_data->Seek(raw_offset, SEEK_SET); + + BMessage content_type; + HeaderField("Content-Type", &content_type); + + charset = _charSetForTextDecoding; + if (charset == B_MAIL_NULL_CONVERSION && content_type.HasString("charset")) { + for (int32 i = 0; mail_charsets[i].charset != NULL; i++) { + if (strcasecmp(content_type.FindString("charset"), mail_charsets[i].charset) == 0) { + charset = mail_charsets[i].flavor; + break; + } + } + } + + encoding = encoding_for_cte(HeaderField("Content-Transfer-Encoding")); + + char *buffer = (char *)malloc(raw_length + 1); + if (buffer == NULL) + return B_NO_MEMORY; + + ssize_t bytes; + if ((bytes = raw_data->Read(buffer, raw_length)) < 0) + return B_IO_ERROR; + + char *string = decoded.LockBuffer(bytes + 1); + bytes = decode(encoding, string, buffer, bytes, 0); + free (buffer); + buffer = NULL; + + // Change line ends from \r\n to just \n. Though this won't work properly + // for UTF-16 because \r takes up two bytes rather than one. + char *dest, *src; + char *end = string + bytes; + for (dest = src = string; src < end; src++) { + if (*src != '\r') + *dest++ = *src; + } + decoded.UnlockBuffer(dest - string); + bytes = decoded.Length(); // Might have shrunk a bit. + + // If the character set wasn't specified, try to guess. ISO-2022-JP + // contains the escape sequences ESC $ B or ESC $ @ to turn on 2 byte + // Japanese, and ESC ( J to switch to Roman, or sometimes ESC ( B for + // ASCII. We'll just try looking for the two switch to Japanese sequences. + + if (charset == B_MAIL_NULL_CONVERSION) { + if (decoded.FindFirst ("\e$B") >= 0 || decoded.FindFirst ("\e$@") >= 0) + charset = B_JIS_CONVERSION; + else // Just assume the usual Latin-1 character set. + charset = B_ISO1_CONVERSION; + } + + int32 state = 0; + int32 destLength = bytes * 3 /* in case it grows */ + 1 /* +1 so it isn't zero which crashes */; + string = text.LockBuffer(destLength); + mail_convert_to_utf8(charset, decoded.String(), &bytes, string, &destLength, &state); + if (destLength > 0) + text.UnlockBuffer(destLength); + else { + text.UnlockBuffer(0); + text.SetTo(decoded); + } + + raw_data = NULL; + return B_OK; +} + + +status_t +BTextMailComponent::RenderToRFC822(BPositionIO *render_to) +{ + status_t status = ParseRaw(); + if (status < B_OK) + return status; + + BString content_type; + content_type << "text/plain"; + + for (uint32 i = 0; mail_charsets[i].charset != NULL; i++) { + if (mail_charsets[i].flavor == charset) { + content_type << "; charset=\"" << mail_charsets[i].charset << "\""; + break; + } + } + + SetHeaderField("Content-Type", content_type.String()); + + const char *transfer_encoding = NULL; + switch (encoding) { + case base64: + transfer_encoding = "base64"; + break; + case quoted_printable: + transfer_encoding = "quoted-printable"; + break; + case eight_bit: + transfer_encoding = "8bit"; + break; + case seven_bit: + default: + transfer_encoding = "7bit"; + break; + } + + SetHeaderField("Content-Transfer-Encoding",transfer_encoding); + + BMailComponent::RenderToRFC822(render_to); + + BString modified = this->text; + BString alt; + + int32 len = this->text.Length(); + if (len > 0) { + int32 dest_len = len * 5; + // Shift-JIS can have a 3 byte escape sequence and a 2 byte code for + // each character (which could just be 2 bytes in UTF-8, or even 1 byte + // if it's regular ASCII), so it can get quite a bit larger than the + // original text. Multiplying by 5 should make more than enough space. + char *raw = alt.LockBuffer(dest_len); + int32 state = 0; + mail_convert_from_utf8(charset,this->text.String(),&len,raw,&dest_len,&state); + alt.UnlockBuffer(dest_len); + + raw = modified.LockBuffer((alt.Length()*3)+1); + switch (encoding) { + case base64: + len = encode_base64(raw,alt.String(),alt.Length(),false); + raw[len] = 0; + break; + case quoted_printable: + len = encode_qp(raw,alt.String(),alt.Length(),false); + raw[len] = 0; + break; + case eight_bit: + case seven_bit: + default: + len = alt.Length(); + strcpy(raw,alt.String()); + } + modified.UnlockBuffer(len); + + if (encoding != base64) // encode_base64 already does CRLF line endings. + modified.ReplaceAll("\n","\r\n"); + + // There seem to be a possibility of NULL bytes in the text, so lets + // filter them out, shouldn't be any after the encoding stage. + + char *string = modified.LockBuffer(modified.Length()); + for (int32 i = modified.Length();i-- > 0;) + { + if (string[i] != '\0') + continue; + + puts("BTextMailComponent::RenderToRFC822: NULL byte in text!!"); + string[i] = ' '; + } + modified.UnlockBuffer(); + + // word wrapping is already done by BeMail (user-configurable) + // and it does it *MUCH* nicer. + +// //------Desperate bid to wrap lines +// int32 curr_line_length = 0; +// int32 last_space = 0; +// +// for (int32 i = 0; i < modified.Length(); i++) { +// if (isspace(modified.ByteAt(i))) +// last_space = i; +// +// if ((modified.ByteAt(i) == '\r') && (modified.ByteAt(i+1) == '\n')) +// curr_line_length = 0; +// else +// curr_line_length++; +// +// if (curr_line_length > 80) { +// if (last_space >= 0) { +// modified.Insert("\r\n",last_space); +// last_space = -1; +// curr_line_length = 0; +// } +// } +// } + } + modified << "\r\n"; + + render_to->Write(modified.String(),modified.Length()); + + return B_OK; +} + + +void BTextMailComponent::_ReservedText1() {} +void BTextMailComponent::_ReservedText2() {} diff --git a/src/kits/mail/MailContainer.cpp b/src/kits/mail/MailContainer.cpp new file mode 100644 index 0000000000..5ca5df50d7 --- /dev/null +++ b/src/kits/mail/MailContainer.cpp @@ -0,0 +1,453 @@ +/* Container - message part container class +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include + +#include +#include +#include + +class _EXPORT BMIMEMultipartMailContainer; + +#include +#include + +typedef struct message_part { + message_part(off_t start, off_t end) { this->start = start; this->end = end; } + + // Offset where the part starts (includes MIME sub-headers but not the + // boundary line) in the message file. + int32 start; + + // Offset just past the last byte of data, so total length == end - start. + // Note that the CRLF that starts the next boundary isn't included in the + // data, the end points at the start of the next CRLF+Boundary. This can + // lead to weird things like the blank line ending the subheader being the + // same as the boundary starting CRLF. So if you have something malformed + // like this: + // ------=_NextPart_005_0040_ENBYSXVW.VACTSCVC + // Content-Type: text/plain; charset="ISO-8859-1" + // + // ------=_NextPart_005_0040_ENBYSXVW.VACTSCVC + // If you subtract the header length (which includes the blank line) from + // the MIME part total length (which doesn't include the blank line - it's + // part of the next boundary), you get -2. + int32 end; +} message_part; + + +BMIMEMultipartMailContainer::BMIMEMultipartMailContainer( + const char *boundary, + const char *this_is_an_MIME_message_text, + uint32 defaultCharSet) + : + BMailContainer (defaultCharSet), + _boundary(NULL), + _MIME_message_warning(this_is_an_MIME_message_text), + _io_data(NULL) +{ + // Definition of the MIME version in the mail header should be enough + SetHeaderField("MIME-Version","1.0"); + SetHeaderField("Content-Type","multipart/mixed"); + SetBoundary(boundary); +} + +/*BMIMEMultipartMailContainer::BMIMEMultipartMailContainer(BMIMEMultipartMailContainer ©) : + BMailComponent(copy), + _boundary(copy._boundary), + _MIME_message_warning(copy._MIME_message_warning), + _io_data(copy._io_data) { + AddHeaderField("MIME-Version","1.0"); + AddHeaderField("Content-Type","multipart/mixed"); + SetBoundary(boundary); + }*/ + + +BMIMEMultipartMailContainer::~BMIMEMultipartMailContainer() { + for (int32 i = 0; i < _components_in_raw.CountItems(); i++) + delete (message_part *)_components_in_raw.ItemAt(i); + + for (int32 i = 0; i < _components_in_code.CountItems(); i++) + delete (BMailComponent *)_components_in_code.ItemAt(i); + + free((void *)_boundary); +} + + +void BMIMEMultipartMailContainer::SetBoundary(const char *boundary) { + free ((void *) _boundary); + _boundary = NULL; + if (boundary != NULL) + _boundary = strdup(boundary); + + BMessage structured; + HeaderField("Content-Type",&structured); + + if (_boundary == NULL) + structured.RemoveName("boundary"); + else if (structured.ReplaceString("boundary",_boundary) != B_OK) + structured.AddString("boundary",_boundary); + + SetHeaderField("Content-Type",&structured); +} + + +void BMIMEMultipartMailContainer::SetThisIsAnMIMEMessageText(const char *text) { + _MIME_message_warning = text; +} + + +status_t BMIMEMultipartMailContainer::AddComponent(BMailComponent *component) { + if (!_components_in_code.AddItem(component)) + return B_ERROR; + if (_components_in_raw.AddItem(NULL)) + return B_OK; + + _components_in_code.RemoveItem(component); + return B_ERROR; +} + + +BMailComponent *BMIMEMultipartMailContainer::GetComponent(int32 index, bool parse_now) { + if (index >= CountComponents()) + return NULL; + + if (BMailComponent *component = (BMailComponent *)_components_in_code.ItemAt(index)) + return component; //--- Handle easy case + + message_part *part = (message_part *)(_components_in_raw.ItemAt(index)); + if (part == NULL) + return NULL; + + _io_data->Seek(part->start,SEEK_SET); + + BMailComponent component (_charSetForTextDecoding); + if (component.SetToRFC822(_io_data,part->end - part->start) < B_OK) + return NULL; + + BMailComponent *piece = component.WhatIsThis(); + + /* Debug code + _io_data->Seek(part->start,SEEK_SET); + char *data = new char[part->end - part->start + 1]; + _io_data->Read(data,part->end - part->start); + data[part->end - part->start] = 0; + puts((char *)(data)); + printf("Instantiating from %d to %d (%d octets)\n",part->start, part->end, part->end - part->start); + */ + _io_data->Seek(part->start,SEEK_SET); + if (piece->SetToRFC822(_io_data,part->end - part->start, parse_now) < B_OK) + { + delete piece; + return NULL; + } + _components_in_code.ReplaceItem(index,piece); + + return piece; +} + + +int32 +BMIMEMultipartMailContainer::CountComponents() const +{ + return _components_in_code.CountItems(); +} + + +status_t +BMIMEMultipartMailContainer::RemoveComponent(BMailComponent *component) +{ + if (component == NULL) + return B_BAD_VALUE; + + int32 index = _components_in_code.IndexOf(component); + if (component == NULL) + return B_ENTRY_NOT_FOUND; + + delete (BMailComponent *)_components_in_code.RemoveItem(index); + delete (message_part *)_components_in_raw.RemoveItem(index); + + return B_OK; +} + + +status_t +BMIMEMultipartMailContainer::RemoveComponent(int32 index) +{ + if (index >= CountComponents()) + return B_BAD_INDEX; + + delete (BMailComponent *)_components_in_code.RemoveItem(index); + delete (message_part *)_components_in_raw.RemoveItem(index); + + return B_OK; +} + + +status_t BMIMEMultipartMailContainer::GetDecodedData(BPositionIO *) +{ + return B_BAD_TYPE; //------We don't play dat +} + + +status_t BMIMEMultipartMailContainer::SetDecodedData(BPositionIO *) { + return B_BAD_TYPE; //------We don't play dat +} + + +status_t BMIMEMultipartMailContainer::SetToRFC822(BPositionIO *data, size_t length, bool copy_data) +{ + typedef enum LookingForEnum { + FIRST_NEWLINE, + INITIAL_DASHES, + BOUNDARY_BODY, + LAST_NEWLINE, + MAX_LOOKING_STATES + } LookingFor; + + ssize_t amountRead; + ssize_t amountToRead; + ssize_t boundaryLength; + char buffer [4096]; + ssize_t bufferIndex; + off_t bufferOffset; + ssize_t bufferSize; + BMessage content_type; + const char *content_type_string; + bool finalBoundary = false; + bool finalComponentCompleted = false; + int i; + off_t lastBoundaryOffset; + LookingFor state; + off_t startOfBoundaryOffset; + off_t topLevelEnd; + off_t topLevelStart; + + // Clear out old components. Maybe make a MakeEmpty method? + + for (i = _components_in_code.CountItems(); i-- > 0;) + delete (BMailComponent *)_components_in_code.RemoveItem(i); + + for (i = _components_in_raw.CountItems(); i-- > 0;) + delete (message_part *)_components_in_raw.RemoveItem(i); + + // Start by reading the headers and getting the boundary string. + + _io_data = data; + topLevelStart = data->Position(); + topLevelEnd = topLevelStart + length; + + BMailComponent::SetToRFC822(data,length); + + HeaderField("Content-Type",&content_type); + content_type_string = content_type.FindString("unlabeled"); + if (content_type_string == NULL || + strncasecmp(content_type_string,"multipart",9) != 0) + return B_BAD_TYPE; + + if (!content_type.HasString("boundary")) + return B_BAD_TYPE; + free ((void *) _boundary); + _boundary = strdup(content_type.FindString("boundary")); + boundaryLength = strlen(_boundary); + if (boundaryLength > (ssize_t) sizeof (buffer) / 2) + return B_BAD_TYPE; // Boundary is way too long, should be max 70 chars. + + // Find container parts by scanning through the given portion of the file + // for the boundary marker lines. The stuff between the header and the + // first boundary is ignored, the same as the stuff after the last + // boundary. The rest get stored away as our sub-components. See RFC2046 + // section 5.1 for details. + + bufferOffset = data->Position(); // File offset of the start of the buffer. + bufferIndex = 0; // Current position we are examining in the buffer. + bufferSize = 0; // Amount of data actually in the buffer, not including NUL. + startOfBoundaryOffset = -1; + lastBoundaryOffset = -1; + state = INITIAL_DASHES; // Starting just after a new line so don't search for it. + while (((bufferOffset + bufferIndex < topLevelEnd) + || (state == LAST_NEWLINE /* No EOF test in LAST_NEWLINE state */)) + && !finalComponentCompleted) + { + // Refill the buffer if the remaining amount of data is less than a + // boundary's worth, plus four dashes and two CRLFs. + if (bufferSize - bufferIndex < boundaryLength + 8) + { + // Shuffle the remaining bit of data in the buffer over to the front. + if (bufferSize - bufferIndex > 0) + memmove (buffer, buffer + bufferIndex, bufferSize - bufferIndex); + bufferOffset += bufferIndex; + bufferSize = bufferSize - bufferIndex; + bufferIndex = 0; + + // Fill up the rest of the buffer with more data. Also leave space + // for a NUL byte just past the last data in the buffer so that + // simple string searches won't go off past the end of the data. + amountToRead = topLevelEnd - (bufferOffset + bufferSize); + if (amountToRead > (ssize_t) sizeof (buffer) - 1 - bufferSize) + amountToRead = sizeof (buffer) - 1 - bufferSize; + if (amountToRead > 0) { + amountRead = data->Read (buffer + bufferSize, amountToRead); + if (amountRead < 0) + return amountRead; + bufferSize += amountRead; + } + buffer [bufferSize] = 0; // Add an end of string NUL byte. + } + + // Search for whatever parts of the boundary we are currently looking + // for in the buffer. It starts with a newline (officially CRLF but we + // also accept just LF for off-line e-mail files), followed by two + // hyphens or dashes "--", followed by the unique boundary string + // specified earlier in the header, followed by two dashes "--" for the + // final boundary (or zero dashes for intermediate boundaries), + // followed by white space (possibly including header style comments in + // brackets), and then a newline. + + switch (state) { + case FIRST_NEWLINE: + // The newline before the boundary is considered to be owned by + // the boundary, not part of the previous MIME component. + startOfBoundaryOffset = bufferOffset + bufferIndex; + if (buffer[bufferIndex] == '\r' && buffer[bufferIndex + 1] == '\n') { + bufferIndex += 2; + state = INITIAL_DASHES; + } else if (buffer[bufferIndex] == '\n') { + bufferIndex += 1; + state = INITIAL_DASHES; + } else + bufferIndex++; + break; + + case INITIAL_DASHES: + if (buffer[bufferIndex] == '-' && buffer[bufferIndex + 1] == '-') { + bufferIndex += 2; + state = BOUNDARY_BODY; + } else + state = FIRST_NEWLINE; + break; + + case BOUNDARY_BODY: + if (strncmp (buffer + bufferIndex, _boundary, boundaryLength) != 0) { + state = FIRST_NEWLINE; + break; + } + bufferIndex += boundaryLength; + finalBoundary = false; + if (buffer[bufferIndex] == '-' && buffer[bufferIndex + 1] == '-') { + bufferIndex += 2; + finalBoundary = true; + } + state = LAST_NEWLINE; + break; + + case LAST_NEWLINE: + // Just keep on scanning until the next new line or end of file. + if (buffer[bufferIndex] == '\r' && buffer[bufferIndex + 1] == '\n') + bufferIndex += 2; + else if (buffer[bufferIndex] == '\n') + bufferIndex += 1; + else if (buffer[bufferIndex] != 0 /* End of file is like a newline */) { + // Not a new line or end of file, just skip over + // everything. White space or not, we don't really care. + bufferIndex += 1; + break; + } + // Got to the end of the boundary line and maybe now have + // another component to add. + if (lastBoundaryOffset >= 0) { + _components_in_raw.AddItem (new message_part (lastBoundaryOffset, startOfBoundaryOffset)); + _components_in_code.AddItem (NULL); + } + // Next component's header starts just after the boundary line. + lastBoundaryOffset = bufferOffset + bufferIndex; + if (finalBoundary) + finalComponentCompleted = true; + state = FIRST_NEWLINE; + break; + + default: // Should not happen. + state = FIRST_NEWLINE; + } + } + + // Some bad MIME encodings (usually spam, or damaged files) don't put on + // the trailing boundary. Dump whatever is remaining into a final + // component if there wasn't a trailing boundary and there is some data + // remaining. + + if (!finalComponentCompleted + && lastBoundaryOffset >= 0 && lastBoundaryOffset < topLevelEnd) { + _components_in_raw.AddItem (new message_part (lastBoundaryOffset, topLevelEnd)); + _components_in_code.AddItem (NULL); + } + + // If requested, actually read the data inside each component, otherwise + // only the positions in the BPositionIO are recorded. + + if (copy_data) { + for (i = 0; GetComponent(i, true /* parse_now */) != NULL; i++) {} + } + + data->Seek (topLevelEnd, SEEK_SET); + return B_OK; +} + + +status_t BMIMEMultipartMailContainer::RenderToRFC822(BPositionIO *render_to) { + BMailComponent::RenderToRFC822(render_to); + + BString delimiter; + delimiter << "\r\n--" << _boundary << "\r\n"; + + if (_MIME_message_warning != NULL) { + render_to->Write(_MIME_message_warning,strlen(_MIME_message_warning)); + render_to->Write("\r\n",2); + } + + for (int32 i = 0; i < _components_in_code.CountItems() /* both have equal length, so pick one at random */; i++) { + render_to->Write(delimiter.String(),delimiter.Length()); + if (_components_in_code.ItemAt(i) != NULL) { //---- _components_in_code has precedence + + BMailComponent *code = (BMailComponent *)_components_in_code.ItemAt(i); + status_t status = code->RenderToRFC822(render_to); //----Easy enough + if (status < B_OK) + return status; + } else { + // copy message contents + + uint8 buffer[1024]; + ssize_t amountWritten, length; + message_part *part = (message_part *)_components_in_raw.ItemAt(i); + + for (off_t begin = part->start; begin < part->end; begin += sizeof(buffer)) { + length = ((part->end - begin) >= sizeof(buffer)) ? sizeof(buffer) : (part->end - begin); + + _io_data->ReadAt(begin,buffer,length); + amountWritten = render_to->Write(buffer,length); + if (amountWritten < 0) + return amountWritten; // IO error of some sort. + } + } + } + + render_to->Write(delimiter.String(),delimiter.Length() - 2); // strip CRLF + render_to->Write("--\r\n",4); + + return B_OK; +} + +void BMIMEMultipartMailContainer::_ReservedMultipart1() {} +void BMIMEMultipartMailContainer::_ReservedMultipart2() {} +void BMIMEMultipartMailContainer::_ReservedMultipart3() {} + +void BMailContainer::_ReservedContainer1() {} +void BMailContainer::_ReservedContainer2() {} +void BMailContainer::_ReservedContainer3() {} +void BMailContainer::_ReservedContainer4() {} + diff --git a/src/kits/mail/MailDaemon.cpp b/src/kits/mail/MailDaemon.cpp new file mode 100644 index 0000000000..99c1e2355b --- /dev/null +++ b/src/kits/mail/MailDaemon.cpp @@ -0,0 +1,91 @@ +/* Daemon - talking to the mail daemon +** +** Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include + +#include +#include + +#include + +_EXPORT status_t +BMailDaemon::CheckMail(bool send_queued_mail,const char *account) +{ + BMessenger daemon("application/x-vnd.Be-POST"); + if (!daemon.IsValid()) + return B_MAIL_NO_DAEMON; + + BMessage message(send_queued_mail ? 'mbth' : 'mnow'); + if (account != NULL) { + BList list; + + GetInboundMailChains(&list); + for (int32 i = list.CountItems();i-- > 0;) { + BMailChain *chain = (BMailChain *)list.ItemAt(i); + + if (!strcmp(chain->Name(),account)) + message.AddInt32("chain",chain->ID()); + + delete chain; + } + + if (send_queued_mail) { + list.MakeEmpty(); + GetOutboundMailChains(&list); + for (int32 i = list.CountItems();i-- > 0;) { + BMailChain *chain = (BMailChain *)list.ItemAt(i); + + if (!strcmp(chain->Name(),account)) + message.AddInt32("chain",chain->ID()); + + delete chain; + } + } + } + daemon.SendMessage(&message); + + return B_OK; +} + + +_EXPORT status_t BMailDaemon::SendQueuedMail() { + BMessenger daemon("application/x-vnd.Be-POST"); + if (!daemon.IsValid()) + return B_MAIL_NO_DAEMON; + + daemon.SendMessage('msnd'); + + return B_OK; +} + +_EXPORT int32 BMailDaemon::CountNewMessages(bool wait_for_fetch_completion) { + BMessenger daemon("application/x-vnd.Be-POST"); + if (!daemon.IsValid()) + return B_MAIL_NO_DAEMON; + + BMessage reply; + BMessage first('mnum'); + + if (wait_for_fetch_completion) + first.AddBool("wait_for_fetch_done",true); + + daemon.SendMessage(&first,&reply); + + return reply.FindInt32("num_new_messages"); +} + +_EXPORT status_t BMailDaemon::Quit() { + BMessenger daemon("application/x-vnd.Be-POST"); + if (!daemon.IsValid()) + return B_MAIL_NO_DAEMON; + + daemon.SendMessage(B_QUIT_REQUESTED); + + return B_OK; +} + diff --git a/src/kits/mail/MailMessage.cpp b/src/kits/mail/MailMessage.cpp new file mode 100644 index 0000000000..bf42ff1834 --- /dev/null +++ b/src/kits/mail/MailMessage.cpp @@ -0,0 +1,944 @@ +/* Message - the main general purpose mail message class +** +** Copyright 2001-2004 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BONE + #ifdef _KERNEL_MODE + #undef _KERNEL_MODE + #include + #define _KERNEL_MODE 1 + #endif + #include + #include +#endif + +class _EXPORT BEmailMessage; + +#include +#include +#include +#include +#include +#include + +//-------Change the following!---------------------- +#define mime_boundary "----------Zoidberg-BeMail-temp--------" +#define mime_warning "This is a multipart message in MIME format." + + +BEmailMessage::BEmailMessage(BPositionIO *file, bool own, uint32 defaultCharSet) + : + BMailContainer (defaultCharSet), + fData(NULL), + _status(B_NO_ERROR), + _bcc(NULL), + _num_components(0), + _body(NULL), + _text_body(NULL) +{ + BMailSettings settings; + _chain_id = settings.DefaultOutboundChainID(); + + if (own) + fData = file; + + if (file != NULL) + SetToRFC822(file,-1); +} + + +BEmailMessage::BEmailMessage(entry_ref *ref, uint32 defaultCharSet) + : + BMailContainer (defaultCharSet), + _bcc(NULL), + _num_components(0), + _body(NULL), + _text_body(NULL) +{ + BMailSettings settings; + _chain_id = settings.DefaultOutboundChainID(); + + fData = new BFile(); + _status = static_cast(fData)->SetTo(ref,B_READ_ONLY); + + if (_status == B_OK) + SetToRFC822(fData,-1); +} + + +BEmailMessage::~BEmailMessage() +{ + if (_bcc != NULL) + free(_bcc); + + delete _body; + delete fData; +} + + +status_t BEmailMessage::InitCheck() const +{ + return _status; +} + + +BEmailMessage * +BEmailMessage::ReplyMessage(mail_reply_to_mode replyTo, bool accountFromMail, const char *quoteStyle) +{ + BEmailMessage *to_return = new BEmailMessage; + + // Set ReplyTo: + + if (replyTo == B_MAIL_REPLY_TO_ALL) { + to_return->SetTo(From()); + + BList list; + get_address_list(list, CC(), extract_address); + get_address_list(list, To(), extract_address); + + // Filter out the sender + BString sender = BMailChain(Account()).MetaData()->FindString("reply_to"); + extract_address(sender); + + BString cc; + + for (int32 i = list.CountItems(); i-- > 0;) { + char *address = (char *)list.RemoveItem(0L); + + // add everything which is not the sender and not already in the list + if (sender.ICompare(address) && cc.FindFirst(address) < 0) { + if (cc.Length() > 0) + cc << ", "; + + cc << address; + } + + free(address); + } + + if (cc.Length() > 0) + to_return->SetCC(cc.String()); + } else if (replyTo == B_MAIL_REPLY_TO_SENDER || ReplyTo() == NULL) + to_return->SetTo(From()); + else + to_return->SetTo(ReplyTo()); + + // Set special "In-Reply-To:" header (used for threading) + const char *messageID = _body ? _body->HeaderField("Message-Id") : NULL; + if (messageID != NULL) + to_return->SetHeaderField("In-Reply-To", messageID); + + // quote body text + to_return->SetBodyTextTo(BodyText()); + if (quoteStyle) + to_return->Body()->Quote(quoteStyle); + + // Set the subject (and add a "Re:" if needed) + BString string = Subject(); + if (string.ICompare("re:", 3) != 0) + string.Prepend("Re: "); + to_return->SetSubject(string.String()); + + // set the matching outbound chain + if (accountFromMail) + to_return->SendViaAccountFrom(this); + + return to_return; +} + + +BEmailMessage * +BEmailMessage::ForwardMessage(bool accountFromMail, bool includeAttachments) +{ + BString header = "------ Forwarded Message: ------\n"; + header << "To: " << To() << '\n'; + header << "From: " << From() << '\n'; + if (CC() != NULL) + header << "CC: " << CC() << '\n'; // Can use CC rather than "Cc" since display only. + header << "Subject: " << Subject() << '\n'; + header << "Date: " << Date() << "\n\n"; + if (_text_body != NULL) + header << _text_body->Text() << '\n'; + BEmailMessage *message = new BEmailMessage(); + message->SetBodyTextTo(header.String()); + + // set the subject + BString subject = Subject(); + if (subject.IFindFirst("fwd") == B_ERROR + && subject.IFindFirst("forward") == B_ERROR + && subject.FindFirst("FW") == B_ERROR) + subject << " (fwd)"; + message->SetSubject(subject.String()); + + if (includeAttachments) { + for (int32 i = 0; i < CountComponents(); i++) { + BMailComponent *cmpt = GetComponent(i); + if (cmpt == _text_body) + continue; + + //---I am ashamed to have the written the code between here and the next comment + cmpt->GetDecodedData(NULL); + // ... and you still managed to get it wrong ;-)), axeld. + // we should really move this stuff into copy constructors + // or something like that + + BMallocIO io; + cmpt->RenderToRFC822(&io); + BMailComponent *clone = cmpt->WhatIsThis(); + io.Seek(0, SEEK_SET); + clone->SetToRFC822(&io, io.BufferLength(), true); + message->AddComponent(clone); + //--- + } + } + + if (accountFromMail) + message->SendViaAccountFrom(this); + + return message; +} + + +const char * +BEmailMessage::To() +{ + return HeaderField("To"); +} + + +const char * +BEmailMessage::From() +{ + return HeaderField("From"); +} + + +const char * +BEmailMessage::ReplyTo() +{ + return HeaderField("Reply-To"); +} + + +const char * +BEmailMessage::CC() +{ + return HeaderField("Cc"); // Note case of CC is "Cc" in our internal headers. +} + + +const char * +BEmailMessage::Subject() +{ + return HeaderField("Subject"); +} + + +const char * +BEmailMessage::Date() +{ + return HeaderField("Date"); +} + +int +BEmailMessage::Priority() +{ + int priorityNumber; + const char *priorityString; + + /* The usual values are a number from 1 to 5, or one of three words: + X-Priority: 1 and/or X-MSMail-Priority: High + X-Priority: 3 and/or X-MSMail-Priority: Normal + X-Priority: 5 and/or X-MSMail-Priority: Low + Also plain Priority: is "normal", "urgent" or "non-urgent", see RFC 1327. */ + + priorityString = HeaderField("Priority"); + if (priorityString == NULL) + priorityString = HeaderField("X-Priority"); + if (priorityString == NULL) + priorityString = HeaderField("X-Msmail-Priority"); + if (priorityString == NULL) + return 3; + priorityNumber = atoi (priorityString); + if (priorityNumber != 0) { + if (priorityNumber > 5) + priorityNumber = 5; + if (priorityNumber < 1) + priorityNumber = 1; + return priorityNumber; + } + if (strcasecmp (priorityString, "Low") == 0 || + strcasecmp (priorityString, "non-urgent") == 0) + return 5; + if (strcasecmp (priorityString, "High") == 0 || + strcasecmp (priorityString, "urgent") == 0) + return 1; + return 3; +} + +void BEmailMessage::SetSubject(const char *subject, uint32 charset, mail_encoding encoding) { + SetHeaderField("Subject", subject, charset, encoding); +} + +void BEmailMessage::SetReplyTo(const char *reply_to, uint32 charset, mail_encoding encoding) { + SetHeaderField("Reply-To", reply_to, charset, encoding); +} + +void BEmailMessage::SetFrom(const char *from, uint32 charset, mail_encoding encoding) { + SetHeaderField("From", from, charset, encoding); +} + +void BEmailMessage::SetTo(const char *to, uint32 charset, mail_encoding encoding) { + SetHeaderField("To", to, charset, encoding); +} + +void BEmailMessage::SetCC(const char *cc, uint32 charset, mail_encoding encoding) { + // For consistency with our header names, use Cc as the name. + SetHeaderField("Cc", cc, charset, encoding); +} + +void BEmailMessage::SetBCC(const char *bcc) { + if (_bcc != NULL) + free(_bcc); + + _bcc = strdup(bcc); +} + +void BEmailMessage::SetPriority(int to) { + char tempString [20]; + + if (to < 1) + to = 1; + if (to > 5) + to = 5; + sprintf (tempString, "%d", to); + SetHeaderField("X-Priority", tempString); + if (to <= 2) { + SetHeaderField("Priority", "urgent"); + SetHeaderField("X-Msmail-Priority", "High"); + } else if (to >= 4) { + SetHeaderField("Priority", "non-urgent"); + SetHeaderField("X-Msmail-Priority", "Low"); + } else { + SetHeaderField("Priority", "normal"); + SetHeaderField("X-Msmail-Priority", "Normal"); + } +} + + +status_t +BEmailMessage::GetName(char *name, int32 maxLength) const +{ + if (name == NULL || maxLength <= 0) + return B_BAD_VALUE; + + if (BFile *file = dynamic_cast(fData)) { + status_t status = file->ReadAttr(B_MAIL_ATTR_NAME,B_STRING_TYPE,0,name,maxLength); + name[maxLength - 1] = '\0'; + + return status >= 0 ? B_OK : status; + } + // ToDo: look at From header? But usually there is + // a file since only the BeMail GUI calls this. + return B_ERROR; +} + + +status_t +BEmailMessage::GetName(BString *name) const +{ + char *buffer = name->LockBuffer(B_FILE_NAME_LENGTH); + status_t status = GetName(buffer,B_FILE_NAME_LENGTH); + name->UnlockBuffer(); + + return status; +} + + +void +BEmailMessage::SendViaAccountFrom(BEmailMessage *message) +{ + char name[B_FILE_NAME_LENGTH]; + if (message->GetAccountName(name, B_FILE_NAME_LENGTH) < B_OK) { + // just return the message with the default account + return; + } + + BList chains; + GetOutboundMailChains(&chains); + for (int32 i = chains.CountItems();i-- > 0;) { + BMailChain *chain = (BMailChain *)chains.ItemAt(i); + if (!strcmp(chain->Name(), name)) + SendViaAccount(chain->ID()); + + delete chain; + } +} + + +void +BEmailMessage::SendViaAccount(const char *account_name) +{ + BList chains; + GetOutboundMailChains(&chains); + + for (int32 i = 0; i < chains.CountItems(); i++) { + if (strcmp(((BMailChain *)(chains.ItemAt(i)))->Name(),account_name) == 0) { + SendViaAccount(((BMailChain *)(chains.ItemAt(i)))->ID()); + break; + } + } + + while (chains.CountItems() > 0) + delete (BMailChain *)chains.RemoveItem(0L); +} + + +void +BEmailMessage::SendViaAccount(int32 chain_id) +{ + _chain_id = chain_id; + + BMailChain chain(_chain_id); + BString from; + from << '\"' << chain.MetaData()->FindString("real_name") << "\" <" << chain.MetaData()->FindString("reply_to") << '>'; + SetFrom(from.String()); +} + + +int32 +BEmailMessage::Account() const +{ + return _chain_id; +} + + +status_t +BEmailMessage::GetAccountName(char *account,int32 maxLength) const +{ + if (account == NULL || maxLength <= 0) + return B_BAD_VALUE; + + if (BFile *file = dynamic_cast(fData)) { + status_t status = file->ReadAttr(B_MAIL_ATTR_ACCOUNT,B_STRING_TYPE,0,account,maxLength); + account[maxLength - 1] = '\0'; + + return status >= 0 ? B_OK : status; + } + + // ToDo: try to get account name out of the chain lists + return B_ERROR; +} + + +status_t +BEmailMessage::GetAccountName(BString *account) const +{ + char *buffer = account->LockBuffer(B_FILE_NAME_LENGTH); + status_t status = GetAccountName(buffer,B_FILE_NAME_LENGTH); + account->UnlockBuffer(); + + return status; +} + + +status_t +BEmailMessage::AddComponent(BMailComponent *component) +{ + status_t status = B_OK; + + if (_num_components == 0) + _body = component; + else if (_num_components == 1) { + BMIMEMultipartMailContainer *container = new BMIMEMultipartMailContainer ( + mime_boundary, mime_warning, _charSetForTextDecoding); + if ((status = container->AddComponent(_body)) == B_OK) + status = container->AddComponent(component); + _body = container; + } else { + BMIMEMultipartMailContainer *container = dynamic_cast(_body); + if (container == NULL) + return B_MISMATCHED_VALUES; //---This really needs a B_WTF constant... + + status = container->AddComponent(component); + } + + if (status == B_OK) + _num_components++; + return status; +} + + +status_t +BEmailMessage::RemoveComponent(BMailComponent */*component*/) +{ + // not yet implemented + // BeMail/Enclosures.cpp:169: contains a warning about this fact + return B_ERROR; +} + + +status_t +BEmailMessage::RemoveComponent(int32 /*index*/) +{ + // not yet implemented + return B_ERROR; +} + + +BMailComponent * +BEmailMessage::GetComponent(int32 i, bool parse_now) +{ + if (BMIMEMultipartMailContainer *container = dynamic_cast(_body)) + return container->GetComponent(i, parse_now); + + if (i < _num_components) + return _body; + + return NULL; +} + +int32 BEmailMessage::CountComponents() const { + return _num_components; +} + +void BEmailMessage::Attach(entry_ref *ref, bool includeAttributes) +{ + if (includeAttributes) + AddComponent(new BAttributedMailAttachment(ref)); + else + AddComponent(new BSimpleMailAttachment(ref)); +} + +bool BEmailMessage::IsComponentAttachment(int32 i) { + if ((i >= _num_components) || (_num_components == 0)) + return false; + + if (_num_components == 1) + return _body->IsAttachment(); + + BMIMEMultipartMailContainer *container = dynamic_cast(_body); + if (container == NULL) + return false; //-----This should never, ever, ever, ever, happen + + BMailComponent *component = container->GetComponent(i); + if (component == NULL) + return false; + return component->IsAttachment(); +} + +void BEmailMessage::SetBodyTextTo(const char *text) { + if (_text_body == NULL) { + _text_body = new BTextMailComponent; + AddComponent(_text_body); + } + + _text_body->SetText(text); +} + + +BTextMailComponent *BEmailMessage::Body() +{ + if (_text_body == NULL) + _text_body = RetrieveTextBody(_body); + + return _text_body; +} + + +const char *BEmailMessage::BodyText() { + if (Body() == NULL) + return NULL; + + return _text_body->Text(); +} + + +status_t BEmailMessage::SetBody(BTextMailComponent *body) { + if (_text_body != NULL) { + return B_ERROR; +// removing doesn't exist for now +// RemoveComponent(_text_body); +// delete _text_body; + } + _text_body = body; + AddComponent(_text_body); + + return B_OK; +} + + +BTextMailComponent *BEmailMessage::RetrieveTextBody(BMailComponent *component) +{ + BTextMailComponent *body = dynamic_cast(component); + if (body != NULL) + return body; + + BMIMEMultipartMailContainer *container = dynamic_cast(component); + if (container != NULL) { + for (int32 i = 0; i < container->CountComponents(); i++) { + if ((component = container->GetComponent(i)) == NULL) + continue; + + switch (component->ComponentType()) + { + case B_MAIL_PLAIN_TEXT_BODY: + // AttributedAttachment returns the MIME type of its contents, so + // we have to use dynamic_cast here + body = dynamic_cast(container->GetComponent(i)); + if (body != NULL) + return body; + break; + + case B_MAIL_MULTIPART_CONTAINER: + body = RetrieveTextBody(container->GetComponent(i)); + if (body != NULL) + return body; + break; + } + } + } + return NULL; +} + + +status_t +BEmailMessage::SetToRFC822(BPositionIO *mail_file, size_t length, bool parse_now) +{ + if (BFile *file = dynamic_cast(mail_file)) + file->ReadAttr("MAIL:chain",B_INT32_TYPE,0,&_chain_id,sizeof(_chain_id)); + + mail_file->Seek(0,SEEK_END); + length = mail_file->Position(); + mail_file->Seek(0,SEEK_SET); + + _status = BMailComponent::SetToRFC822(mail_file,length,parse_now); + if (_status < B_OK) + return _status; + + _body = WhatIsThis(); + + mail_file->Seek(0,SEEK_SET); + _status = _body->SetToRFC822(mail_file,length,parse_now); + if (_status < B_OK) + return _status; + + //------------Move headers that we use to us, everything else to _body + const char *name; + for (int32 i = 0; (name = _body->HeaderAt(i)) != NULL; i++) { + if ((strcasecmp(name,"Subject") != 0) + && (strcasecmp(name,"To") != 0) + && (strcasecmp(name,"From") != 0) + && (strcasecmp(name,"Reply-To") != 0) + && (strcasecmp(name,"Cc") != 0) + && (strcasecmp(name,"Priority") != 0) + && (strcasecmp(name,"X-Priority") != 0) + && (strcasecmp(name,"X-Msmail-Priority") != 0) + && (strcasecmp(name,"Date") != 0)) { + RemoveHeader(name); + } + } + + _body->RemoveHeader("Subject"); + _body->RemoveHeader("To"); + _body->RemoveHeader("From"); + _body->RemoveHeader("Reply-To"); + _body->RemoveHeader("Cc"); + _body->RemoveHeader("Priority"); + _body->RemoveHeader("X-Priority"); + _body->RemoveHeader("X-Msmail-Priority"); + _body->RemoveHeader("Date"); + + _num_components = 1; + if (BMIMEMultipartMailContainer *container = dynamic_cast(_body)) + _num_components = container->CountComponents(); + + return B_OK; +} + + +status_t +BEmailMessage::RenderToRFC822(BPositionIO *file) +{ + if (_body == NULL) + return B_MAIL_INVALID_MAIL; + + //------Do real rendering + + if (From() == NULL) + SendViaAccount(_chain_id); //-----Set the from string + + BList recipientList; + get_address_list(recipientList, To(), extract_address); + get_address_list(recipientList, CC(), extract_address); + get_address_list(recipientList, _bcc, extract_address); + + BString recipients; + for (int32 i = recipientList.CountItems(); i-- > 0;) { + char *address = (char *)recipientList.RemoveItem(0L); + + recipients << '<' << address << '>'; + if (i) + recipients << ','; + + free(address); + } + + // add the date field + int32 creationTime = time(NULL); + { + char date[128]; + struct tm tm; + localtime_r(&creationTime, &tm); + + strftime(date, 128, "%a, %d %b %Y %H:%M:%S",&tm); + + // GMT offsets are full hours, yes, but you never know :-) + if (tm.tm_gmtoff) + sprintf(date + strlen(date)," %+03d%02d",tm.tm_gmtoff / 3600,(tm.tm_gmtoff / 60) % 60); + + uint32 length = strlen(date); + if (length < sizeof(date) - 5) + strftime(date + length, length - sizeof(date), " %Z", &tm); + + SetHeaderField("Date", date); + } + + /* add a message-id */ + BString message_id; + /* empirical evidence indicates message id must be enclosed in + ** angle brackets and there must be an "at" symbol in it + */ + message_id << "<"; + message_id << system_time(); + message_id << "-BeMail@"; + + #if BONE + utsname uinfo; + uname(&uinfo); + message_id << uinfo.nodename; + #else + char host[255]; + gethostname(host,255); + message_id << host; + #endif + + message_id << ">"; + SetHeaderField("Message-ID", message_id.String()); + + status_t err = BMailComponent::RenderToRFC822(file); + if (err < B_OK) + return err; + + file->Seek(-2, SEEK_CUR); //-----Remove division between headers + + err = _body->RenderToRFC822(file); + if (err < B_OK) + return err; + + // Set the message file's attributes. Do this after the rest of the file + // is filled in, in case the daemon attempts to send it before it is ready + // (since the daemon may send it when it sees the status attribute getting + // set to "Pending"). + + if (BFile *attributed = dynamic_cast (file)) { + + BNodeInfo(attributed).SetType(B_MAIL_TYPE); + + attributed->WriteAttrString(B_MAIL_ATTR_RECIPIENTS,&recipients); + + BString attr; + + attr = To(); + attributed->WriteAttrString(B_MAIL_ATTR_TO,&attr); + attr = CC(); + attributed->WriteAttrString(B_MAIL_ATTR_CC,&attr); + attr = Subject(); + attributed->WriteAttrString(B_MAIL_ATTR_SUBJECT,&attr); + attr = ReplyTo(); + attributed->WriteAttrString(B_MAIL_ATTR_REPLY,&attr); + attr = From(); + attributed->WriteAttrString(B_MAIL_ATTR_FROM,&attr); + if (Priority() != 3 /* Normal is 3 */) { + sprintf (attr.LockBuffer (40), "%d", Priority()); + attr.UnlockBuffer(-1); + attributed->WriteAttrString(B_MAIL_ATTR_PRIORITY,&attr); + } + attr = "Pending"; + attributed->WriteAttrString(B_MAIL_ATTR_STATUS,&attr); + attr = "1.0"; + attributed->WriteAttrString(B_MAIL_ATTR_MIME,&attr); + attr = BMailChain(_chain_id).Name(); + attributed->WriteAttrString(B_MAIL_ATTR_ACCOUNT,&attr); + + attributed->WriteAttr(B_MAIL_ATTR_WHEN,B_TIME_TYPE,0,&creationTime,sizeof(int32)); + int32 flags = B_MAIL_PENDING | B_MAIL_SAVE; + attributed->WriteAttr(B_MAIL_ATTR_FLAGS,B_INT32_TYPE,0,&flags,sizeof(int32)); + + attributed->WriteAttr("MAIL:chain",B_INT32_TYPE,0,&_chain_id,sizeof(int32)); + } + + return B_OK; +} + + +status_t +BEmailMessage::RenderTo(BDirectory *dir,BEntry *msg) +{ + time_t currentTime; + char numericDateString [40]; + struct tm timeFields; + BString worker; + + // Generate a file name for the outgoing message. See also + // FolderFilter::ProcessMailMessage which does something similar for + // incoming messages. + + BString name = Subject(); + SubjectToThread (name); // Extract the core subject words. + if (name.Length() <= 0) + name = "No Subject"; + if (name[0] == '.') + name.Prepend ("_"); // Avoid hidden files, starting with a dot. + + // Convert the date into a year-month-day fixed digit width format, so that + // sorting by file name will give all the messages with the same subject in + // order of date. + time (¤tTime); + localtime_r (¤tTime, &timeFields); + sprintf (numericDateString, "%04d%02d%02d%02d%02d%02d", + timeFields.tm_year + 1900, + timeFields.tm_mon + 1, + timeFields.tm_mday, + timeFields.tm_hour, + timeFields.tm_min, + timeFields.tm_sec); + name << " " << numericDateString; + + worker = From(); + extract_address_name(worker); + name << " " << worker; + + name.Truncate(222); // reserve space for the uniquer + + // Get rid of annoying characters which are hard to use in the shell. + name.ReplaceAll('/','_'); + name.ReplaceAll('\'','_'); + name.ReplaceAll('"','_'); + name.ReplaceAll('!','_'); + name.ReplaceAll('<','_'); + name.ReplaceAll('>','_'); + while (name.FindFirst(" ") >= 0) // Remove multiple spaces. + name.Replace(" " /* Old */, " " /* New */, 1024 /* Count */); + + int32 uniquer = time(NULL); + worker = name; + + int32 tries = 30; + bool exists; + while (((exists = dir->Contains(worker.String())) == true) /* pacify mwcc */ && + (--tries > 0)) { + srand(rand()); + uniquer += (rand() >> 16) - 16384; + + worker = name; + worker << ' ' << uniquer; + } + + if (exists) + printf("could not create mail! (should be: %s)\n", worker.String()); + + BFile file; + status_t status = dir->CreateFile(worker.String(), &file); + if (status < B_OK) + return status; + + if (msg != NULL) + msg->SetTo(dir,worker.String()); + + return RenderToRFC822(&file); +} + + +status_t +BEmailMessage::Send(bool send_now) +{ + BMailChain *via = new BMailChain(_chain_id); + if ((via->InitCheck() != B_OK) || (via->ChainDirection() != outbound)) { + delete via; + via = new BMailChain(BMailSettings().DefaultOutboundChainID()); + SendViaAccount(via->ID()); + } + + create_directory(via->MetaData()->FindString("path"),0777); + BDirectory directory(via->MetaData()->FindString("path")); + + BEntry message; + + status_t status = RenderTo(&directory,&message); + delete via; + if (status >= B_OK && send_now) { + BMailSettings settings_file; + if (settings_file.SendOnlyIfPPPUp()) { +#ifdef BONE + int s = socket(AF_INET, SOCK_DGRAM, 0); + bsppp_status_t ppp_status; + + strcpy(ppp_status.if_name, "ppp0"); + if (ioctl(s, BONE_SERIAL_PPP_GET_STATUS, &ppp_status, sizeof(ppp_status)) != 0) { + close(s); + return B_OK; + } else { + if (ppp_status.connection_status != BSPPP_CONNECTED) { + close(s); + return B_OK; + } + } + close(s); +#else + if (find_thread("tty_thread") <= 0) + return B_OK; +#endif + } + + BMessenger daemon("application/x-vnd.Be-POST"); + if (!daemon.IsValid()) + return B_MAIL_NO_DAEMON; + + BMessage msg('msnd'); + msg.AddInt32("chain",_chain_id); + BPath path; + message.GetPath(&path); + msg.AddString("message_path",path.Path()); + daemon.SendMessage(&msg); + } + + return status; +} + +void BEmailMessage::_ReservedMessage1() {} +void BEmailMessage::_ReservedMessage2() {} +void BEmailMessage::_ReservedMessage3() {} + diff --git a/src/kits/mail/MailProtocol.cpp b/src/kits/mail/MailProtocol.cpp new file mode 100644 index 0000000000..18980ce542 --- /dev/null +++ b/src/kits/mail/MailProtocol.cpp @@ -0,0 +1,370 @@ +/* BMailProtocol - the base class for protocol filters +** +** Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +class BMailProtocol; + +#include +#include +#include + +namespace { + +class ManifestAdder : public BMailChainCallback { + public: + ManifestAdder(BStringList *list,BStringList **list2, const char *id) : manifest(list), uids_on_disk(list2), uid(id) {} + virtual void Callback(status_t result) { + if (result == B_OK) { + (*manifest) += uid; + if (*uids_on_disk != NULL) + (**uids_on_disk) += uid; + } + } + + private: + BStringList *manifest,**uids_on_disk; + const char *uid; +}; + +class MessageDeletion : public BMailChainCallback { + public: + MessageDeletion(BMailProtocol *home, const char *uid, BEntry *io_entry, bool delete_anyway); + virtual void Callback(status_t result); + + private: + BMailProtocol *us; + bool always; + const char *message_id; + BEntry *entry; +}; + + +inline void +BMailProtocol::error_alert(const char *process, status_t error) +{ + BString string; + MDR_DIALECT_CHOICE ( + string << "Error while " << process << ": " << strerror(error); + runner->ShowError(string.String()); + , + string << process << "中にエラーが発生しました: " << strerror(error); + runner->ShowError(string.String()); + ) +} + + +class DeleteHandler : public BHandler { + public: + DeleteHandler(BMailProtocol *a) + : us(a) + { + } + + void MessageReceived(BMessage *msg) + { + if ((msg->what == 'DELE') && (us->InitCheck() == B_OK)) { + us->CheckForDeletedMessages(); + Looper()->RemoveHandler(this); + delete this; + } + } + + private: + BMailProtocol *us; +}; + +class TrashMonitor : public BHandler { + public: + TrashMonitor(BMailProtocol *a, int32 chain_id) + : us(a), trash("/boot/home/Desktop/Trash"), messages_for_us(0), id(chain_id) + { + } + + void MessageReceived(BMessage *msg) + { + if (msg->what == 'INIT') { + node_ref to_watch; + trash.GetNodeRef(&to_watch); + watch_node(&to_watch,B_WATCH_DIRECTORY,this); + return; + } + if ((msg->what == B_NODE_MONITOR) && (us->InitCheck() == B_OK)) { + int32 opcode; + if (msg->FindInt32("opcode",&opcode) < B_OK) + return; + + if (opcode == B_ENTRY_MOVED) { + int64 node(msg->FindInt64("to directory")); + dev_t device(msg->FindInt32("device")); + node_ref item_ref; + item_ref.node = node; + item_ref.device = device; + + BDirectory moved_to(&item_ref); + + BNode trash_item(&moved_to,msg->FindString("name")); + int32 chain; + if (trash_item.ReadAttr("MAIL:chain",B_INT32_TYPE,0,&chain,sizeof(chain)) < B_OK) + return; + + if (chain == id) + messages_for_us += (moved_to == trash) ? 1 : -1; + } + + if (messages_for_us < 0) + messages_for_us = 0; // Guard against weirdness + + if (trash.CountEntries() == 0) { + if (messages_for_us > 0) + us->CheckForDeletedMessages(); + + messages_for_us = 0; + } + } + } + + private: + BMailProtocol *us; + BDirectory trash; + int32 messages_for_us; + int32 id; +}; + +BMailProtocol::BMailProtocol(BMessage *settings, BMailChainRunner *run) + : BMailFilter(settings), + runner(run), trash_monitor(NULL), uids_on_disk(NULL) +{ + unique_ids = new BStringList; + BMailProtocol::settings = settings; + + manifest = new BStringList; + + { + BString attr_name = "MAIL:"; + attr_name << runner->Chain()->ID() << ":manifest"; //--- In case someone puts multiple accounts in the same directory + + if (runner->Chain()->MetaData()->HasString("path")) { + BNode node(runner->Chain()->MetaData()->FindString("path")); + if (node.InitCheck() >= B_OK) { + attr_info info; + if (node.GetAttrInfo(attr_name.String(),&info) < B_OK) { + if (runner->Chain()->MetaData()->FindFlat("manifest", manifest) == B_OK) { + runner->Chain()->MetaData()->RemoveName("manifest"); + runner->Chain()->Save(); //--- Not having this code made an earlier version of MDR delete all my *(&(*& mail + } + } else { + void *flatmanifest = malloc(info.size); + node.ReadAttr(attr_name.String(),manifest->TypeCode(),0,flatmanifest,info.size); + manifest->Unflatten(manifest->TypeCode(),flatmanifest,info.size); + free(flatmanifest); + } + } else runner->ShowError("Error while reading account manifest: cannot use destination directory."); + } else runner->ShowError("Error while reading account manifest: no destination directory exists."); + } + + uids_on_disk = new BStringList; + BVolumeRoster volumes; + BVolume volume; + while (volumes.GetNextVolume(&volume) == B_OK) { + BQuery fido; + entry_ref entry; + + fido.SetVolume(&volume); + fido.PushAttr("MAIL:chain"); + fido.PushInt32(settings->FindInt32("chain")); + fido.PushOp(B_EQ); + if (!settings->FindBool("delete_remote_when_local")) { + fido.PushAttr("BEOS:type"); + fido.PushString("text/x-partial-email"); + fido.PushOp(B_EQ); + fido.PushOp(B_AND); + } + fido.Fetch(); + + BString uid; + while (fido.GetNextRef(&entry) == B_OK) { + BNode(&entry).ReadAttrString("MAIL:unique_id",&uid); + uids_on_disk->AddItem(uid.String()); + } + } + + (*manifest) |= (*uids_on_disk); + + if (!settings->FindBool("login_and_do_nothing_else_of_any_importance")) { + DeleteHandler *h = new DeleteHandler(this); + runner->AddHandler(h); + runner->PostMessage('DELE',h); + + trash_monitor = new TrashMonitor(this,runner->Chain()->ID()); + runner->AddHandler(trash_monitor); + runner->PostMessage('INIT',trash_monitor); + } +} + + +BMailProtocol::~BMailProtocol() +{ + if (manifest != NULL) { + BMessage *meta_data = runner->Chain()->MetaData(); + meta_data->RemoveName("manifest"); + BString attr_name = "MAIL:"; + attr_name << runner->Chain()->ID() << ":manifest"; //--- In case someone puts multiple accounts in the same directory + if (meta_data->HasString("path")) { + BNode node(meta_data->FindString("path")); + if (node.InitCheck() >= B_OK) { + node.RemoveAttr(attr_name.String()); + ssize_t manifestsize = manifest->FlattenedSize(); + void *flatmanifest = malloc(manifestsize); + manifest->Flatten(flatmanifest,manifestsize); + if (status_t err = node.WriteAttr(attr_name.String(),manifest->TypeCode(),0,flatmanifest,manifestsize) < B_OK) { + BString error = "Error while saving account manifest: "; + error << strerror(err); + runner->ShowError(error.String()); + } + free(flatmanifest); + } else runner->ShowError("Error while saving account manifest: cannot use destination directory."); + } else runner->ShowError("Error while saving account manifest: no destination directory exists."); + } + delete unique_ids; + delete manifest; + delete trash_monitor; + delete uids_on_disk; +} + + +#define dump_stringlist(a) printf("BStringList %s:\n",#a); \ + for (int32 i = 0; i < (a)->CountItems(); i++)\ + puts((a)->ItemAt(i)); \ + puts("Done\n"); + +status_t +BMailProtocol::ProcessMailMessage(BPositionIO **io_message, BEntry *io_entry, + BMessage *io_headers, BPath *io_folder, const char *io_uid) +{ + status_t error; + + if (io_uid == NULL) + return B_ERROR; + + error = GetMessage(io_uid, io_message, io_headers, io_folder); + if (error < B_OK) { + if (error != B_MAIL_END_FETCH) { + MDR_DIALECT_CHOICE ( + error_alert("getting a message",error);, + error_alert("新しいメッセージヲ取得中にエラーが発生しました",error); + ); + } + return B_MAIL_END_FETCH; + } + + runner->RegisterMessageCallback(new ManifestAdder(manifest, &uids_on_disk, io_uid)); + runner->RegisterMessageCallback(new MessageDeletion(this, io_uid, io_entry, !settings->FindBool("leave_mail_on_server"))); + + return B_OK; +} + +void BMailProtocol::CheckForDeletedMessages() { + { + //---Delete things from the manifest no longer on the server + BStringList temp; + manifest->NotThere(*unique_ids, &temp); + (*manifest) -= temp; + } + + if (((settings->FindBool("delete_remote_when_local")) || !(settings->FindBool("leave_mail_on_server"))) && (manifest->CountItems() > 0)) { + BStringList to_delete; + + if (uids_on_disk == NULL) { + BStringList query_contents; + BVolumeRoster volumes; + BVolume volume; + + while (volumes.GetNextVolume(&volume) == B_OK) { + BQuery fido; + entry_ref entry; + + fido.SetVolume(&volume); + fido.PushAttr("MAIL:chain"); + fido.PushInt32(settings->FindInt32("chain")); + fido.PushOp(B_EQ); + fido.Fetch(); + + BString uid; + while (fido.GetNextRef(&entry) == B_OK) { + BNode(&entry).ReadAttrString("MAIL:unique_id",&uid); + query_contents.AddItem(uid.String()); + } + } + + query_contents.NotHere(*manifest,&to_delete); + } else { + uids_on_disk->NotHere(*manifest,&to_delete); + delete uids_on_disk; + uids_on_disk = NULL; + } + + for (int32 i = 0; i < to_delete.CountItems(); i++) + DeleteMessage(to_delete[i]); + + //*(unique_ids) -= to_delete; --- This line causes bad things to + // happen (POP3 client uses the wrong indices to retrieve + // messages). Without it, bad things don't happen. + *(manifest) -= to_delete; + } +} + +void BMailProtocol::_ReservedProtocol1() {} +void BMailProtocol::_ReservedProtocol2() {} +void BMailProtocol::_ReservedProtocol3() {} +void BMailProtocol::_ReservedProtocol4() {} +void BMailProtocol::_ReservedProtocol5() {} + + +// #pragma mark - + + +MessageDeletion::MessageDeletion(BMailProtocol *home, const char *uid, + BEntry *io_entry, bool delete_anyway) + : + us(home), + always(delete_anyway), + message_id(uid), entry(io_entry) +{ +} + + +void +MessageDeletion::Callback(status_t result) +{ + #if DEBUG + printf("Deleting %s\n", message_id); + #endif + BNode node(entry); + BNodeInfo info(&node); + char type[255]; + info.GetType(type); + if ((always && strcmp(B_MAIL_TYPE,type) == 0) || result == B_MAIL_DISCARD) + us->DeleteMessage(message_id); +} + +} diff --git a/src/kits/mail/MailSettings.cpp b/src/kits/mail/MailSettings.cpp new file mode 100644 index 0000000000..03e026d5af --- /dev/null +++ b/src/kits/mail/MailSettings.cpp @@ -0,0 +1,333 @@ +/* BMailSettings - the mail daemon's settings +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class BMailSettings; + +namespace MailInternal { + status_t WriteMessageFile(const BMessage& archive, const BPath& path, const char* name); +} + +#include + +BMailSettings::BMailSettings() +{ + Reload(); +} + +BMailSettings::~BMailSettings() +{ +} + +status_t BMailSettings::InitCheck() const +{ + return B_OK; +} + + +status_t BMailSettings::Save(bigtime_t /*timeout*/) +{ + status_t ret; + // + // Find chain-saving directory + // + + BPath path; + ret = find_directory(B_USER_SETTINGS_DIRECTORY, &path); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't find user settings directory: %s\n", + strerror(ret)); + return ret; + } + + path.Append("Mail"); + + status_t result = MailInternal::WriteMessageFile(data,path,"new_mail_daemon"); + if (result < B_OK) + return result; + + BMessenger("application/x-vnd.Be-POST").SendMessage('mrrs'); + + return B_OK; +} + +status_t BMailSettings::Reload() +{ + status_t ret; + + BPath path; + ret = find_directory(B_USER_SETTINGS_DIRECTORY, &path); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't find user settings directory: %s\n", + strerror(ret)); + return ret; + } + + path.Append("Mail/new_mail_daemon"); + + // open + BFile settings(path.Path(),B_READ_ONLY); + ret = settings.InitCheck(); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't open settings file '%s': %s\n", + path.Path(), strerror(ret)); + return ret; + } + + // read settings + BMessage tmp; + ret = tmp.Unflatten(&settings); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't read settings from '%s': %s\n", + path.Path(), strerror(ret)); + return ret; + } + + // clobber old settings + data = tmp; + return B_OK; +} + + +// Chain methods + +// +// To do +// +_EXPORT BMailChain* NewMailChain() +{ + // attempted solution: use time(NULL) and hope it's unique. Is there a better idea? + // note that two chains in two second is quite possible. how to fix this? + // maybe we could | in some bigtime_t as well. hrrm... + + // This is to fix a problem with generating the correct id for chains. + // Basically if the chains dir does not exist, the first time you create + // an account both the inbound and outbound chains will be called 0. + create_directory("/boot/home/config/settings/Mail/chains",0777); + + BPath path; + find_directory(B_USER_SETTINGS_DIRECTORY, &path); + path.Append("Mail/chains"); + BDirectory chain_dir(path.Path()); + BDirectory outbound_dir(&chain_dir,"outbound"), inbound_dir(&chain_dir,"inbound"); + + chain_dir.Lock(); //---------Try to lock the directory + + int32 id = -1; //-----When inc'ed, we start with 0---- + chain_dir.ReadAttr("last_issued_chain_id",B_INT32_TYPE,0,&id,sizeof(id)); + + BString string_id; + + do { + id++; + string_id = ""; + string_id << id; + } while ((outbound_dir.Contains(string_id.String())) || (inbound_dir.Contains(string_id.String()))); + + + chain_dir.WriteAttr("last_issued_chain_id",B_INT32_TYPE,0,&id,sizeof(id)); + + return new BMailChain(id); +} +// +// Done +// + +_EXPORT BMailChain* GetMailChain(uint32 id) +{ + return new BMailChain(id); +} + +_EXPORT status_t GetInboundMailChains(BList *list) +{ + BPath path; + status_t ret = B_OK; + + ret = find_directory(B_USER_SETTINGS_DIRECTORY, &path); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't find user settings directory: %s\n", + strerror(ret)); + return ret; + } + + path.Append("Mail/chains/inbound"); + BDirectory chain_dir(path.Path()); + entry_ref ref; + + while (chain_dir.GetNextRef(&ref)==B_OK) + { + char *end; + uint32 id = strtoul(ref.name, &end, 10); + + if (!end || *end == '\0') + list->AddItem((void*)new BMailChain(id)); + } + + return ret; +} + +_EXPORT status_t GetOutboundMailChains(BList *list) +{ + BPath path; + status_t ret = B_OK; + + ret = find_directory(B_USER_SETTINGS_DIRECTORY, &path); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't find user settings directory: %s\n", + strerror(ret)); + return ret; + } + + path.Append("Mail/chains/outbound"); + BDirectory chain_dir(path.Path()); + entry_ref ref; + + while (chain_dir.GetNextRef(&ref)==B_OK) + { + char *end; + uint32 id = strtoul(ref.name, &end, 10); + if (!end || *end == '\0') + list->AddItem((void*)new BMailChain(id)); + } + + return ret; +} + + +// Global settings +int32 BMailSettings::WindowFollowsCorner() +{ + return data.FindInt32("WindowFollowsCorner"); +} +void BMailSettings::SetWindowFollowsCorner(int32 which_corner) +{ + if (data.ReplaceInt32("WindowFollowsCorner",which_corner)) + data.AddInt32("WindowFollowsCorner",which_corner); +} + +uint32 BMailSettings::ShowStatusWindow() +{ + return data.FindInt32("ShowStatusWindow"); +} +void BMailSettings::SetShowStatusWindow(uint32 mode) +{ + if (data.ReplaceInt32("ShowStatusWindow",mode)) + data.AddInt32("ShowStatusWindow",mode); +} + +bool BMailSettings::DaemonAutoStarts() +{ + return data.FindBool("DaemonAutoStarts"); +} +void BMailSettings::SetDaemonAutoStarts(bool does_it) +{ + if (data.ReplaceBool("DaemonAutoStarts",does_it)) + data.AddBool("DaemonAutoStarts",does_it); +} + +BRect BMailSettings::ConfigWindowFrame() +{ + return data.FindRect("ConfigWindowFrame"); +} +void BMailSettings::SetConfigWindowFrame(BRect frame) +{ + if (data.ReplaceRect("ConfigWindowFrame",frame)) + data.AddRect("ConfigWindowFrame",frame); +} + +BRect BMailSettings::StatusWindowFrame() +{ + return data.FindRect("StatusWindowFrame"); +} +void BMailSettings::SetStatusWindowFrame(BRect frame) +{ + if (data.ReplaceRect("StatusWindowFrame",frame)) + data.AddRect("StatusWindowFrame",frame); +} + +int32 BMailSettings::StatusWindowWorkspaces() +{ + return data.FindInt32("StatusWindowWorkSpace"); +} +void BMailSettings::SetStatusWindowWorkspaces(int32 workspace) +{ + if (data.ReplaceInt32("StatusWindowWorkSpace",workspace)) + data.AddInt32("StatusWindowWorkSpace",workspace); + + BMessage msg('wsch'); + msg.AddInt32("StatusWindowWorkSpace",workspace); + BMessenger("application/x-vnd.Be-POST").SendMessage(&msg); +} + +int32 BMailSettings::StatusWindowLook() +{ + return data.FindInt32("StatusWindowLook"); +} +void BMailSettings::SetStatusWindowLook(int32 look) +{ + if (data.ReplaceInt32("StatusWindowLook",look)) + data.AddInt32("StatusWindowLook",look); + + BMessage msg('lkch'); + msg.AddInt32("StatusWindowLook",look); + BMessenger("application/x-vnd.Be-POST").SendMessage(&msg); +} + +bigtime_t BMailSettings::AutoCheckInterval() { + bigtime_t value = B_INFINITE_TIMEOUT; + data.FindInt64("AutoCheckInterval",&value); + return value; +} + +void BMailSettings::SetAutoCheckInterval(bigtime_t interval) { + if (data.ReplaceInt64("AutoCheckInterval",interval)) + data.AddInt64("AutoCheckInterval",interval); +} + +bool BMailSettings::CheckOnlyIfPPPUp() { + return data.FindBool("CheckOnlyIfPPPUp"); +} + +void BMailSettings::SetCheckOnlyIfPPPUp(bool yes) { + if (data.ReplaceBool("CheckOnlyIfPPPUp",yes)) + data.AddBool("CheckOnlyIfPPPUp",yes); +} + +bool BMailSettings::SendOnlyIfPPPUp() { + return data.FindBool("SendOnlyIfPPPUp"); +} + +void BMailSettings::SetSendOnlyIfPPPUp(bool yes) { + if (data.ReplaceBool("SendOnlyIfPPPUp",yes)) + data.AddBool("SendOnlyIfPPPUp",yes); +} + +uint32 BMailSettings::DefaultOutboundChainID() { + return data.FindInt32("DefaultOutboundChainID"); +} + +void BMailSettings::SetDefaultOutboundChainID(uint32 to) { + if (data.ReplaceInt32("DefaultOutboundChainID",to)) + data.AddInt32("DefaultOutboundChainID",to); +} diff --git a/src/kits/mail/NodeMessage.cpp b/src/kits/mail/NodeMessage.cpp new file mode 100644 index 0000000000..45018461fb --- /dev/null +++ b/src/kits/mail/NodeMessage.cpp @@ -0,0 +1,58 @@ +#include "NodeMessage.h" +#include +#include +#include +/* + These functions gives a nice BMessage interface to node attributes, + by letting you transfer attributes to and from BMessages. It makes + it so you can use all the convenient Find...() and Add...() functions + provided by BMessage for attributes too. You use it as follows: + + BMessage m; + BNode n(path); + if (reading) { n>>m; printf("woohoo=%s\n",m.FindString("woohoo")) } + else { m.AddString("woohoo","it's howdy doody time"); n<>(BNode& n, BMessage& m) +{ + char name[B_ATTR_NAME_LENGTH]; + attr_info info; + vector buf(4); + + n.RewindAttrs(); + while (n.GetNextAttrName(name)==B_OK) + { + n.GetAttrInfo(name,&info); + buf.resize(info.size); + info.size=n.ReadAttr(name,info.type,0,buf.begin(),info.size); + if (info.size >= 0) + m.AddData(name,info.type,buf.begin(),info.size); + } + n.RewindAttrs(); + + return n; +} diff --git a/src/kits/mail/ProtocolConfigView.cpp b/src/kits/mail/ProtocolConfigView.cpp new file mode 100644 index 0000000000..e2962fcf76 --- /dev/null +++ b/src/kits/mail/ProtocolConfigView.cpp @@ -0,0 +1,330 @@ +/* BMailProtocolConfigView - the standard config view for all protocols +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +class _EXPORT BMailProtocolConfigView; + +#include + +#include "ProtocolConfigView.h" + + +namespace { + +//--------------------Support functions and #defines--------------- +#define enable_control(name) if (FindView(name) != NULL) ((BControl *)(FindView(name)))->SetEnabled(true) +#define disable_control(name) if (FindView(name) != NULL) ((BControl *)(FindView(name)))->SetEnabled(false) + +BTextControl *AddTextField (BRect &rect, const char *name, const char *label); +BMenuField *AddMenuField (BRect &rect, const char *name, const char *label); +float FindWidestLabel(BView *view); + +static float gItemHeight; + +inline const char *TextControl(BView *parent,const char *name) { + BTextControl *control = (BTextControl *)(parent->FindView(name)); + if (control != NULL) + return control->Text(); + + return ""; +} + +BTextControl *AddTextField (BRect &rect, const char *name, const char *label) { + BTextControl *text_control = new BTextControl(rect,name,label,"",NULL,B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); +// text_control->SetDivider(be_plain_font->StringWidth(label)); + rect.OffsetBy(0,gItemHeight); + return text_control; +} + +BMenuField *AddMenuField (BRect &rect, const char *name, const char *label) { + BPopUpMenu *menu = new BPopUpMenu("Select"); + BMenuField *control = new BMenuField(rect,name,label,menu,B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); + control->SetDivider(be_plain_font->StringWidth(label) + 6); + rect.OffsetBy(0,gItemHeight); + return control; +} + +inline BCheckBox *AddCheckBox(BRect &rect, const char *name, const char *label, BMessage *msg = NULL) { + BCheckBox *control = new BCheckBox(rect,name,label,msg); + rect.OffsetBy(0,gItemHeight); + return control; +} + +inline void SetTextControl(BView *parent, const char *name, const char *text) { + BTextControl *control = (BTextControl *)(parent->FindView(name)); + if (control != NULL) + control->SetText(text); +} + +float FindWidestLabel(BView *view) +{ + float width = 0; + for (int32 i = view->CountChildren();i-- > 0;) { + if (BControl *control = dynamic_cast(view->ChildAt(i))) { + float labelWidth = control->StringWidth(control->Label()); + if (labelWidth > width) + width = labelWidth; + } + } + return width; +} + + +//----------------Real code---------------------- +BMailProtocolConfigView::BMailProtocolConfigView(uint32 options_mask) : BView (BRect(0,0,100,20), "protocol_config_view", B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW) { + BRect rect(5,5,245,25); + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + // determine font height + font_height fontHeight; + GetFontHeight(&fontHeight); + gItemHeight = (int32)(fontHeight.ascent + fontHeight.descent + fontHeight.leading) + 13; + rect.bottom = rect.top - 2 + gItemHeight; + + if (options_mask & B_MAIL_PROTOCOL_HAS_HOSTNAME) + AddChild(AddTextField(rect,"host",MDR_DIALECT_CHOICE ("Mail Host:","サーバ名 :"))); + + if (options_mask & B_MAIL_PROTOCOL_HAS_USERNAME) + AddChild(AddTextField(rect,"user",MDR_DIALECT_CHOICE ("User Name:","ユーザーID:"))); + + if (options_mask & B_MAIL_PROTOCOL_HAS_PASSWORD) { + BTextControl *control = AddTextField(rect,"pass",MDR_DIALECT_CHOICE ("Password:","パスワード:")); + control->TextView()->HideTyping(true); + AddChild(control); + } + + if (options_mask & B_MAIL_PROTOCOL_HAS_FLAVORS) + AddChild(AddMenuField(rect,"flavor","Connection Type:")); + + if (options_mask & B_MAIL_PROTOCOL_HAS_AUTH_METHODS) + AddChild(AddMenuField(rect,"auth_method",MDR_DIALECT_CHOICE ("Authentication Method:","認証方法 :"))); + + // set divider + float width = FindWidestLabel(this); + for (int32 i = CountChildren();i-- > 0;) { + if (BTextControl *text = dynamic_cast(ChildAt(i))) + text->SetDivider(width + 6); + } + + if (options_mask & B_MAIL_PROTOCOL_CAN_LEAVE_MAIL_ON_SERVER) { + AddChild(AddCheckBox(rect,"leave_mail_remote",MDR_DIALECT_CHOICE ("Leave Mail On Server","受信後にサーバ内のメールを削除しない"),new BMessage('lmos'))); + BCheckBox *box = AddCheckBox(rect,"delete_remote_when_local",MDR_DIALECT_CHOICE ("Delete Mail From Server When Deleted Locally","端末で削除されたらサーバ保存分も削除")); + box->SetEnabled(false); + AddChild(box); + } + + // resize views + float height; + GetPreferredSize(&width,&height); + ResizeTo(width,height); + for (int32 i = CountChildren();i-- > 0;) { + // this doesn't work with BTextControl, does anyone know why? -- axeld. + if (BView *view = ChildAt(i)) + view->ResizeTo(width - 10,view->Bounds().Height()); + } +} + +BMailProtocolConfigView::~BMailProtocolConfigView() {} + +void BMailProtocolConfigView::SetTo(BMessage *archive) { + BString host = archive->FindString("server"); + if (archive->HasInt32("port")) + host << ':' << archive->FindInt32("port"); + + SetTextControl(this,"host",host.String()); + SetTextControl(this,"user",archive->FindString("username")); + + char *password = get_passwd(archive,"cpasswd"); + if (password) + { + SetTextControl(this,"pass",password); + delete password; + } + else + SetTextControl(this,"pass",archive->FindString("password")); + + if (archive->HasInt32("flavor")) { + BMenuField *menu = (BMenuField *)(FindView("flavor")); + if (menu != NULL) { + if (BMenuItem *item = menu->Menu()->ItemAt(archive->FindInt32("flavor"))) + item->SetMarked(true); + } + } + + if (archive->HasInt32("auth_method")) { + BMenuField *menu = (BMenuField *)(FindView("auth_method")); + if (menu != NULL) { + if (BMenuItem *item = menu->Menu()->ItemAt(archive->FindInt32("auth_method"))) { + item->SetMarked(true); + if (item->Command() != 'none') { + enable_control("user"); + enable_control("pass"); + } + } + } + } + + BCheckBox *box; + + box = (BCheckBox *)(FindView("leave_mail_remote")); + if (box != NULL) + box->SetValue(archive->FindBool("leave_mail_on_server") ? B_CONTROL_ON : B_CONTROL_OFF); + + box = (BCheckBox *)(FindView("delete_remote_when_local")); + if (box != NULL) { + box->SetValue(archive->FindBool("delete_remote_when_local") ? B_CONTROL_ON : B_CONTROL_OFF); + + if (archive->FindBool("leave_mail_on_server")) + box->SetEnabled(true); + else + box->SetEnabled(false); + } +} + +void BMailProtocolConfigView::AddFlavor(const char *label) { + BMenuField *menu = (BMenuField *)(FindView("flavor")); + if (menu != NULL) { + menu->Menu()->AddItem(new BMenuItem(label,NULL)); + if (menu->Menu()->FindMarked() == NULL) + menu->Menu()->ItemAt(0)->SetMarked(true); + } +} + +void BMailProtocolConfigView::AddAuthMethod(const char *label,bool needUserPassword) { + BMenuField *menu = (BMenuField *)(FindView("auth_method")); + if (menu != NULL) { + BMenuItem *item = new BMenuItem(label,new BMessage(needUserPassword ? 'some' : 'none')); + + menu->Menu()->AddItem(item); + + if (menu->Menu()->FindMarked() == NULL) { + menu->Menu()->ItemAt(0)->SetMarked(true); + MessageReceived(menu->Menu()->ItemAt(0)->Message()); + } + } +} + +void BMailProtocolConfigView::AttachedToWindow() { + BMenuField *menu = (BMenuField *)(FindView("auth_method")); + if (menu != NULL) + menu->Menu()->SetTargetForItems(this); + + BCheckBox *box = (BCheckBox *)(FindView("leave_mail_remote")); + if (box != NULL) + box->SetTarget(this); +} + +void BMailProtocolConfigView::MessageReceived(BMessage *msg) { + switch (msg->what) { + case 'some': + enable_control("user"); + enable_control("pass"); + break; + case 'none': + disable_control("user"); + disable_control("pass"); + break; + + case 'lmos': + if (msg->FindInt32("be:value") == 1) { + enable_control("delete_remote_when_local"); + } else { + disable_control("delete_remote_when_local"); + } + break; + } +} + +status_t BMailProtocolConfigView::Archive(BMessage *into, bool) const { + const char *host = TextControl((BView *)this,"host"); + int32 port = -1; + BString host_name = host; + if (host_name.FindFirst(':') > -1) { + port = atol(host_name.String() + host_name.FindFirst(':') + 1); + host_name.Truncate(host_name.FindFirst(':')); + } + + if (into->ReplaceString("server",host_name.String()) != B_OK) + into->AddString("server",host_name.String()); + + // since there is no need for the port option, remove it here + into->RemoveName("port"); + if (port != -1) + into->AddInt32("port",port); + + if (into->ReplaceString("username",TextControl((BView *)this,"user")) != B_OK) + into->AddString("username",TextControl((BView *)this,"user")); + + // remove old unencrypted passwords + into->RemoveName("password"); + + set_passwd(into,"cpasswd",TextControl((BView *)this,"pass")); + + BMenuField *field; + int32 index = -1; + + if ((field = (BMenuField *)(FindView("flavor"))) != NULL) { + BMenuItem *item = field->Menu()->FindMarked(); + if (item != NULL) + index = field->Menu()->IndexOf(item); + } + + if (into->ReplaceInt32("flavor",index) != B_OK) + into->AddInt32("flavor",index); + + index = -1; + + if ((field = (BMenuField *)(FindView("auth_method"))) != NULL) { + BMenuItem *item = field->Menu()->FindMarked(); + if (item != NULL) + index = field->Menu()->IndexOf(item); + } + + if (into->ReplaceInt32("auth_method",index) != B_OK) + into->AddInt32("auth_method",index); + + if (FindView("leave_mail_remote") != NULL) { + if (into->ReplaceBool("leave_mail_on_server",((BControl *)(FindView("leave_mail_remote")))->Value() == B_CONTROL_ON) != B_OK) + into->AddBool("leave_mail_on_server",((BControl *)(FindView("leave_mail_remote")))->Value() == B_CONTROL_ON); + + if (into->ReplaceBool("delete_remote_when_local",((BControl *)(FindView("delete_remote_when_local")))->Value() == B_CONTROL_ON) != B_OK) + into->AddBool("delete_remote_when_local",((BControl *)(FindView("delete_remote_when_local")))->Value() == B_CONTROL_ON); + } else { + if (into->ReplaceBool("leave_mail_on_server",false) != B_OK) + into->AddBool("leave_mail_on_server",false); + + if (into->ReplaceBool("delete_remote_when_local",false) != B_OK) + into->AddBool("delete_remote_when_local",false); + } + + return B_OK; +} + +void BMailProtocolConfigView::GetPreferredSize(float *width, float *height) { + float minWidth; + if (BView *view = FindView("delete_remote_when_local")) { + float ignore; + view->GetPreferredSize(&minWidth,&ignore); + } + if (minWidth < 250) + minWidth = 250; + *width = minWidth + 10; + *height = (CountChildren() * gItemHeight) + 5; +} + +} // namespace diff --git a/src/kits/mail/RemoteStorageProtocol.cpp b/src/kits/mail/RemoteStorageProtocol.cpp new file mode 100644 index 0000000000..e49907224f --- /dev/null +++ b/src/kits/mail/RemoteStorageProtocol.cpp @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +class _EXPORT BMailRemoteStorageProtocol; + +#include +#include + +namespace { + +void GetSubFolders(BDirectory *of, BStringList *folders, const char *prepend = ""); + +class UpdateHandler : public BHandler { + public: + UpdateHandler(BMailRemoteStorageProtocol *prot, const char *dest) : _prot(prot), _dest(dest) { + node_ref ref; + _dest.GetNodeRef(&ref); + dest_node = ref.node; + } + virtual ~UpdateHandler() { + stop_watching(this); + } + void MessageReceived(BMessage *msg) { + switch (msg->what) { + case 'INIT': { + if (_prot->InitCheck() < B_OK) + return; + + ((BMailChainRunner *)(Looper()))->ReportProgress(0,0,"Synchronizing Mailboxes"); + + BStringList subdirs; + GetSubFolders(&_dest,&subdirs); + BStringList to_delete; + BStringList to_add; + subdirs.NotThere(_prot->mailboxes,&to_add); + if (subdirs.CountItems() != 0) // --- If it's a virgin mailfolder, the user probably just configured his machineand probably *doesn't* want all his mail folders deleted :) + subdirs.NotHere(_prot->mailboxes,&to_delete); + for (int32 i = 0; i < to_add.CountItems(); i++) { + if (_prot->CreateMailbox(to_add[i]) != B_OK) + continue; + _prot->mailboxes += to_add[i]; + _prot->SyncMailbox(to_add[i]); + } + + _prot->CheckForDeletedMessages(); //--- Refresh the manifest list, delete messages in locally deleted folders + + for (int32 i = 0; i < to_delete.CountItems(); i++) { + if (to_delete[i][0] == 0) + continue; + if (_prot->DeleteMailbox(to_delete[i]) == B_OK) + _prot->mailboxes -= to_delete[i]; + } + + entry_ref ref; + BEntry entry; + _dest.GetEntry(&entry); + entry.GetRef(&ref); + BPath path(&ref), work_path(path); + BNode node(&ref); + node_ref watcher; + node.GetNodeRef(&watcher); + watch_node(&watcher,B_WATCH_DIRECTORY,this); + + for (int32 i = 0; i < _prot->mailboxes.CountItems(); i++) { + + work_path = path; + work_path.Append(_prot->mailboxes[i]); + node.SetTo(work_path.Path()); + node.GetNodeRef(&watcher); + nodes[watcher.node] = strdup(_prot->mailboxes[i]); + if (_prot->mailboxes[i][0] == 0) + continue; //--- We've covered this in the parent monitor + watch_node(&watcher,B_WATCH_DIRECTORY,this); + _prot->SyncMailbox(_prot->mailboxes[i]); + } + ((BMailChainRunner *)(Looper()))->ResetProgress(); + } break; + + case B_NODE_MONITOR: { + int32 opcode; + if (msg->FindInt32("opcode",&opcode) < B_OK) + break; + + int64 directory, node(msg->FindInt64("node")); + dev_t device(msg->FindInt32("device")); + + + bool is_dir; + { + node_ref item_ref; + item_ref.node = node; + item_ref.device = device; + BDirectory dir(&item_ref); + is_dir = (dir.InitCheck() == B_OK); + } + + if (opcode == B_ENTRY_MOVED) { + ino_t from, to; + msg->FindInt64("from directory",&from); + msg->FindInt64("to directory",&to); + const char *from_mb(nodes[from]), *to_mb(nodes[to]); + if (to == dest_node) + to_mb = ""; + if (from == dest_node) + from_mb = ""; + if (from_mb == NULL) { + msg->AddInt64("directory",to); + opcode = B_ENTRY_CREATED; + } else if (to_mb == NULL) { + msg->AddInt64("directory",from); + if (is_dir) + opcode = B_ENTRY_REMOVED; + } else { + if (!is_dir) { + { + node_ref item_ref; + item_ref.node = to; + item_ref.device = device; + BDirectory dir(&item_ref); + BNode node(&dir,msg->FindString("name")); + //--- Why in HELL can't you make a BNode from a node_ref? + + if (node.InitCheck() != B_OK) + break; //-- We're late, it's already gone elsewhere. Ignore for now. + + BString id; + node.ReadAttrString("MAIL:unique_id",&id); + id.Truncate(id.FindLast('/')); + if (id == to_mb) + break; //-- Already where it belongs, no need to do anything + } + + snooze(uint64(5e5)); + _prot->SyncMailbox(to_mb); + + //node.WriteAttrString("MAIL:unique_id",&id); + + _prot->CheckForDeletedMessages(); + } else { + BString mb; + if (to_mb[0] == 0) + mb = msg->FindString("name"); + else { + mb = to_mb; + mb << '/' << msg->FindString("name"); + } + if (strcmp(mb.String(),nodes[node]) == 0) + break; + if (_prot->CreateMailbox(mb.String()) < B_OK) + break; + _prot->mailboxes += mb.String(); + _prot->SyncMailbox(mb.String()); + _prot->CheckForDeletedMessages(); + _prot->DeleteMailbox(nodes[node]); + _prot->mailboxes -= nodes[node]; + free((void *)nodes[node]); + nodes[node] = strdup(mb.String()); + } + break; + } + } + + msg->FindInt64("directory",&directory); + switch (opcode) { + case B_ENTRY_CREATED: + if (!is_dir) { + const char *dir = nodes[directory]; + snooze(500000); + + if (dir == NULL) + dir = ""; + + { + node_ref item_ref; + item_ref.node = directory; + item_ref.device = device; + BDirectory dir(&item_ref); + BNode node(&dir,msg->FindString("name")); + //--- Why in HELL can't you make a BNode from a node_ref? + + if (node.InitCheck() != B_OK) + break; //-- We're late, it's already gone elsewhere. Ignore for now. + } + + _prot->SyncMailbox(nodes[directory]); + } else { + BString mb; + if (directory == dest_node) + mb = msg->FindString("name"); + else { + mb = nodes[directory]; + mb << '/' << msg->FindString("name"); + } + if (_prot->CreateMailbox(mb.String()) < B_OK) + break; + nodes[node] = strdup(mb.String()); + _prot->mailboxes += mb.String(); + _prot->SyncMailbox(mb.String()); + node_ref ref; + ref.device = device; + ref.node = node; + watch_node(&ref,B_WATCH_DIRECTORY,this); + } + break; + case B_ENTRY_REMOVED: + _prot->CheckForDeletedMessages(); + if ((is_dir) && (nodes[node] != NULL)) { + _prot->DeleteMailbox(nodes[node]); + _prot->mailboxes -= nodes[node]; + free((void *)nodes[node]); + nodes[node] = NULL; + node_ref ref; + ref.device = device; + ref.node = node; + watch_node(&ref,B_STOP_WATCHING,this); + } + break; + } + + } break; + } + } + + + + private: + BMailRemoteStorageProtocol *_prot; + BDirectory _dest; + + map nodes; + ino_t dest_node; +}; + +void GetSubFolders(BDirectory *of, BStringList *folders, const char *prepend) { + of->Rewind(); + BEntry ent; + BString crud; + BDirectory sub; + char buf[255]; + while (of->GetNextEntry(&ent) == B_OK) { + if (ent.IsDirectory()) { + sub.SetTo(&ent); + ent.GetName(buf); + crud = prepend; + crud << buf << '/'; + GetSubFolders(&sub,folders,crud.String()); + crud = prepend; + crud << buf; + (*folders) += crud.String(); + } + } +} + +BMailRemoteStorageProtocol::BMailRemoteStorageProtocol(BMessage *settings, BMailChainRunner *runner) : BMailProtocol(settings,runner) { + handler = new UpdateHandler(this,runner->Chain()->MetaData()->FindString("path")); + runner->AddHandler(handler); + runner->PostMessage('INIT',handler); +} + +BMailRemoteStorageProtocol::~BMailRemoteStorageProtocol() { + delete handler; +} +} + +//----BMailProtocol stuff +status_t BMailRemoteStorageProtocol::GetMessage( + const char* uid, + BPositionIO** out_file, BMessage* out_headers, + BPath* out_folder_location) { + BString folder(uid), id; + { + BString raw(uid); + folder.Truncate(raw.FindLast('/')); + raw.CopyInto(id,raw.FindLast('/') + 1,raw.Length()); + } + + *out_folder_location = folder.String(); + return GetMessage(folder.String(),id.String(),out_file,out_headers); + } + +status_t BMailRemoteStorageProtocol::DeleteMessage(const char* uid) { + BString folder(uid), id; + { + BString raw(uid); + int32 j = raw.FindLast('/'); + folder.Truncate(j); + raw.CopyInto(id,j + 1,raw.Length()); + } + + status_t err; + if ((err = DeleteMessage(folder.String(),id.String())) < B_OK) + return err; + + (*unique_ids) -= uid; + return B_OK; +} + +void BMailRemoteStorageProtocol::SyncMailbox(const char *mailbox) { + BPath path(runner->Chain()->MetaData()->FindString("path")); + path.Append(mailbox); + + BDirectory folder(path.Path()); + + BEntry entry; + BFile snoodle; + BString string; + uint32 chain; + bool append; + + while (folder.GetNextEntry(&entry) == B_OK) { + if (!entry.IsFile()) + continue; + while (snoodle.SetTo(&entry,B_READ_WRITE) == B_BUSY) snooze(100); + append = false; + + while (snoodle.Lock() != B_OK) snooze(100); + snoodle.Unlock(); + + if (snoodle.ReadAttr("MAIL:chain",B_INT32_TYPE,0,&chain,sizeof(chain)) < B_OK) + append = true; + if (chain != runner->Chain()->ID()) + append = true; + if (snoodle.ReadAttrString("MAIL:unique_id",&string) < B_OK) + append = true; + + BString folder(string), id(""); + int32 j = string.FindLast('/'); + if ((!append) && (j >= 0)) { + folder.Truncate(j); + string.CopyInto(id,j + 1,string.Length()); + if (folder == mailbox) + continue; + } else { + append = true; + } + + if (append) + AddMessage(mailbox,&snoodle,&id); //---We should check for partial messages here + else + CopyMessage(folder.String(),mailbox,&id); + + string = mailbox; + string << '/' << id; + /*snoodle.RemoveAttr("MAIL:unique_id"); + snoodle.RemoveAttr("MAIL:chain");*/ + chain = runner->Chain()->ID(); + snoodle.WriteAttr("MAIL:chain",B_INT32_TYPE,0,&chain,sizeof(chain)); + snoodle.WriteAttrString("MAIL:unique_id",&string); + (*manifest) += string.String(); + (*unique_ids) += string.String(); + string = runner->Chain()->Name(); + snoodle.WriteAttrString("MAIL:account",&string); + } +} + +/*status_t BMailRemoteStorageProtocol::MoveMessage(const char *mailbox, const char *to_mailbox, BString *message) { + BString new_id(*message); + status_t err; + if ((err = CopyMessage(mailbox,to_mailbox,&new_id)) < B_OK) + return err; + if ((err = DeleteMessage(mailbox,message->String())) < B_OK) + return err; + *message = new_id; + return B_OK; +}*/ diff --git a/src/kits/mail/StatusWindow.cpp b/src/kits/mail/StatusWindow.cpp new file mode 100644 index 0000000000..b23a7fc506 --- /dev/null +++ b/src/kits/mail/StatusWindow.cpp @@ -0,0 +1,539 @@ +/* BMailStatusWindow - the status window while fetching/sending mails +** +** Copyright (c) 2001-2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class _EXPORT BMailStatusWindow; +class _EXPORT BMailStatusView; + +#include "status.h" +#include "MailSettings.h" + +#include + +/*------------------------------------------------ + +BMailStatusWindow + +------------------------------------------------*/ + +static BLocker sLock; + + +BMailStatusWindow::BMailStatusWindow(BRect rect, const char *name, uint32 s) + : BWindow(rect, name, B_MODAL_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, + B_NOT_CLOSABLE | B_NO_WORKSPACE_ACTIVATION | B_NOT_V_RESIZABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE), + fShowMode(s), + fWindowMoved(0L) +{ + BRect frame(Bounds()); + frame.InsetBy(90.0 + 5.0, 5.0); + + BButton *button = new BButton(frame, "check_mail", + MDR_DIALECT_CHOICE ("Check Mail Now","メールチェック"), + new BMessage('mbth'), B_FOLLOW_LEFT_RIGHT, + B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE); + button->ResizeToPreferred(); + frame = button->Frame(); + + button->ResizeTo(button->Bounds().Width(),25); + button->SetTarget(be_app_messenger); + + frame.OffsetBy(0.0, frame.Height()); + frame.InsetBy(-90.0, 0.0); + + fMessageView = new BStringView(frame, "message_view", "", + B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE); + fMessageView->SetAlignment(B_ALIGN_CENTER); + fMessageView->SetText(MDR_DIALECT_CHOICE ("No new messages.","未読メッセージはありません")); + float framewidth = frame.Width(); + fMessageView->ResizeToPreferred(); + fMessageView->ResizeTo(framewidth,fMessageView->Bounds().Height()); + frame = fMessageView->Frame(); + + frame.InsetBy(-5.0, -5.0); + frame.top = 0.0; + + fDefaultView = new BBox(frame, "default_view", B_FOLLOW_LEFT_RIGHT, + B_WILL_DRAW|B_FRAME_EVENTS|B_NAVIGABLE_JUMP, B_PLAIN_BORDER); + fDefaultView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + fDefaultView->AddChild(button); + fDefaultView->AddChild(fMessageView); + + fMinWidth = fDefaultView->Bounds().Width(); + fMinHeight = fDefaultView->Bounds().Height(); + ResizeTo(fMinWidth, fMinHeight); + SetSizeLimits(fMinWidth, 2.0 * fMinWidth, fMinHeight, fMinHeight); + + BMailSettings general; + if (general.InitCheck() == B_OK) { + // set on-screen location + + frame = general.StatusWindowFrame(); + BScreen screen(this); + if (screen.Frame().Contains(frame)) { + MoveTo(frame.LeftTop()); + if (frame.Width() >= fMinWidth && frame.Height() >= fMinHeight) { + float x_off_set = frame.Width() - fMinWidth; + float y_off_set = 0; //---The height is constant + + ResizeBy(x_off_set, y_off_set); + fDefaultView->ResizeBy(x_off_set, y_off_set); + button->ResizeBy(x_off_set, y_off_set); + fMessageView->ResizeBy(x_off_set, y_off_set); + } + } + // set workspace for window + + uint32 workspace = general.StatusWindowWorkspaces(); + int32 workspacesCount = count_workspaces(); + uint32 workspacesMask = (workspacesCount > 31 ? 0 : 1L << workspacesCount) - 1; + if ((workspacesMask & workspace) && (workspace != Workspaces())) + SetWorkspaces(workspace); + + // set look + + SetBorderStyle(general.StatusWindowLook()); + } + AddChild(fDefaultView); + + fFrame = Frame(); + + if (fShowMode != B_MAIL_SHOW_STATUS_WINDOW_ALWAYS) + Hide(); + Show(); +} + + +BMailStatusWindow::~BMailStatusWindow() +{ + // remove all status_views, so we don't accidentally delete them + while (BMailStatusView *status_view = (BMailStatusView *)fStatusViews.RemoveItem(0L)) + RemoveView(status_view); + + BMailSettings general; + if (general.InitCheck() == B_OK) { + // save the current status window properties + general.SetStatusWindowFrame(Frame()); + general.SetStatusWindowWorkspaces((int32)Workspaces()); + general.Save(); + } +} + + +void +BMailStatusWindow::FrameMoved(BPoint /*origin*/) +{ + if (fLastWorkspace == current_workspace()) + fFrame = Frame(); +} + + +void +BMailStatusWindow::WorkspaceActivated(int32 workspace, bool active) +{ + if (!active) + return; + + MoveTo(fFrame.LeftTop()); + fLastWorkspace = workspace; + + // make the window visible if the screen's frame doesn't contain it + BScreen screen; + if (screen.Frame().bottom < fFrame.top) + MoveTo(fFrame.left - 1, screen.Frame().bottom - fFrame.Height() - 4); + if (screen.Frame().right < fFrame.left) + MoveTo(fFrame.left - 1, screen.Frame().bottom - fFrame.Height() - 4); +} + + +void +BMailStatusWindow::MessageReceived(BMessage *msg) +{ + switch (msg->what) { + case 'lkch': + { + int32 look; + if (msg->FindInt32("StatusWindowLook", &look) == B_OK) + SetBorderStyle(look); + break; + } + case 'wsch': + { + uint32 workspaces; + if (msg->FindInt32("StatusWindowWorkSpace", (int32 *)&workspaces) != B_OK) + break; + if (Workspaces() != B_ALL_WORKSPACES && workspaces != B_ALL_WORKSPACES) + break; + if (workspaces != Workspaces()) + SetWorkspaces(workspaces); + break; + } + case 'DATA': + msg->what = B_REFS_RECEIVED; + be_roster->Launch(B_MAIL_TYPE, msg); + break; + + default: + BWindow::MessageReceived(msg); + } +} + + +void +BMailStatusWindow::SetDefaultMessage(const BString &message) +{ + if (Lock()) { + fMessageView->SetText(message.String()); + Unlock(); + } +} + + +BMailStatusView * +BMailStatusWindow::NewStatusView(const char *description, bool upstream) +{ + if (!Lock()) + return NULL; + + BRect rect = Bounds(); + rect.top = fStatusViews.CountItems() * (fMinHeight + 1); + rect.bottom = rect.top + fMinHeight; + BMailStatusView *status = new BMailStatusView(rect, description, upstream); + status->window = this; + + Unlock(); + return status; +} + + +void +BMailStatusWindow::ActuallyAddStatusView(BMailStatusView *status) +{ + if (!Lock()) + return; + + sLock.Lock(); + + BRect rect = Bounds(); + rect.top = fStatusViews.CountItems() * (fMinHeight + 1); + rect.bottom = rect.top + fMinHeight; + + status->MoveTo(rect.LeftTop()); + status->ResizeTo(rect.Width(), rect.Height()); + + fStatusViews.AddItem((void *)status); + + status->Hide(); + AddChild(status); + + if (CountVisibleItems() == 1) + fDefaultView->Hide(); + + status->Show(); + SetSizeLimits(10.0, 2000.0, 10.0, 2000.0); + + // if the window doesn't fit on screen anymore, move it + BScreen screen; + if (screen.Frame().bottom < Frame().top + rect.bottom) { + MoveBy(0, -fMinHeight - 1); + fWindowMoved++; + } + + ResizeTo(rect.Width(), rect.bottom); + + if (fShowMode != B_MAIL_SHOW_STATUS_WINDOW_ALWAYS + && fShowMode != B_MAIL_SHOW_STATUS_WINDOW_NEVER + && CountVisibleItems() == 1) + { + SetFlags(Flags() | B_AVOID_FOCUS); + Show(); + SetFlags(Flags() ^ B_AVOID_FOCUS); + } + sLock.Unlock(); + Unlock(); +} + + +void +BMailStatusWindow::RemoveView(BMailStatusView *view) +{ + if (!view || !Lock()) + return; + + sLock.Lock(); + // ToDo: although there already is the outer lock, this seems + // to help... (maybe we should investigate this further...) + + int32 i = fStatusViews.IndexOf(view); + if (i < 0) { + Unlock(); + return; + } + + fStatusViews.RemoveItem((void *)view); + if (RemoveChild(view)) { + while ((view = (BMailStatusView *)fStatusViews.ItemAt(i++)) != NULL) + view->MoveBy(0, -fMinHeight - 1); + + // the view will be deleted in the ChainRunner + view = NULL; + } + + if (fWindowMoved > 0) { + fWindowMoved--; + MoveBy(0, fMinHeight + 1); + } + + if (CountVisibleItems() == 0) { + if (fShowMode != B_MAIL_SHOW_STATUS_WINDOW_NEVER + && fShowMode != B_MAIL_SHOW_STATUS_WINDOW_ALWAYS) { + while (!IsHidden()) + Hide(); + } + + fDefaultView->Show(); + + SetSizeLimits(fMinWidth, 2.0 * fMinWidth, fMinHeight, fMinHeight); + ResizeTo(fDefaultView->Frame().Width(), fDefaultView->Frame().Height()); + + be_app->PostMessage('stwg'); + // notify that the status window is gone + } + else + ResizeTo(Bounds().Width(), fStatusViews.CountItems() * fMinHeight); + + sLock.Unlock(); + Unlock(); +} + + +int32 +BMailStatusWindow::CountVisibleItems() +{ + if (fShowMode != B_MAIL_SHOW_STATUS_WINDOW_WHEN_SENDING) + return fStatusViews.CountItems(); + + int32 count = 0; + for (int32 i = fStatusViews.CountItems(); i-- > 0;) { + BMailStatusView *view = (BMailStatusView *)fStatusViews.ItemAt(i); + if (view->is_upstream) + count++; + } + return count; +} + + +bool +BMailStatusWindow::HasItems(void) +{ + return CountVisibleItems() > 0; +} + + +void +BMailStatusWindow::SetShowCriterion(uint32 when) +{ + if (!Lock()) + return; + + fShowMode = when; + if (fShowMode == B_MAIL_SHOW_STATUS_WINDOW_ALWAYS + || (fShowMode != B_MAIL_SHOW_STATUS_WINDOW_NEVER && HasItems())) + { + while (IsHidden()) + Show(); + } else { + while (!IsHidden()) + Hide(); + } + Unlock(); +} + + +void +BMailStatusWindow::SetBorderStyle(int32 look) +{ + switch (look) { + case B_MAIL_STATUS_LOOK_TITLED: + SetLook(B_TITLED_WINDOW_LOOK); + break; + case B_MAIL_STATUS_LOOK_FLOATING: + SetLook(B_FLOATING_WINDOW_LOOK); + break; + case B_MAIL_STATUS_LOOK_THIN_BORDER: + SetLook(B_BORDERED_WINDOW_LOOK); + break; + case B_MAIL_STATUS_LOOK_NO_BORDER: + SetLook(B_NO_BORDER_WINDOW_LOOK); + break; + + case B_MAIL_STATUS_LOOK_NORMAL_BORDER: + default: + SetLook(B_MODAL_WINDOW_LOOK); + } +} + + +// #pragma mark - +//------------------------------------------------ +// +// BMailStatusView +// +//------------------------------------------------ + + +BMailStatusView::BMailStatusView(BRect rect, const char *description,bool upstream) + : BBox(rect, description, B_FOLLOW_LEFT_RIGHT, + B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, + B_PLAIN_BORDER) +{ + status = new BStatusBar(BRect(5, 5, Bounds().right - 5, Bounds().bottom - 5), + "status_bar", description, ""); + status->SetResizingMode(B_FOLLOW_ALL_SIDES); + status->SetBarHeight(12); + + if (!upstream) { + const rgb_color downstreamColor = {48,176,48,255}; // upstream was: {255,100,50,255} + status->SetBarColor(downstreamColor); + } + AddChild(status); + + items_now = 0; + total_items = 0; + pre_text[0] = 0; + is_upstream = upstream; +} + + +BMailStatusView::~BMailStatusView() +{ +} + + +void +BMailStatusView::AddProgress(int32 how_much) +{ + AddSelfToWindow(); + + if (LockLooper()) { + if (status->CurrentValue() == 0) + strcpy(pre_text,status->TrailingText()); + char final[80]; + if (by_bytes) { + sprintf(final,"%.1f / %.1f kb (%d / %d messages)",float(float(status->CurrentValue() + how_much) / 1024),float(float(status->MaxValue()) / 1024),(int)items_now+1,(int)total_items); + status->Update(how_much,NULL,final); + } else { + sprintf(final,"%d / %d messages",(int)items_now,(int)total_items); + status->Update(how_much,NULL,final); + } + UnlockLooper(); + } +} + + +void +BMailStatusView::SetMessage(const char *msg) +{ + AddSelfToWindow(); + + if (LockLooper()) { + status->SetTrailingText(msg); + UnlockLooper(); + } +} + + +void +BMailStatusView::Reset(bool hide) +{ + if (LockLooper()) { + char old[255]; + if ((pre_text[0] == 0) && !hide) + strcpy(pre_text, status->TrailingText()); + if (hide) + pre_text[0] = 0; + + strcpy(old,status->Label()); + status->Reset(old); + status->SetTrailingText(pre_text); + status->Draw(status->Bounds()); + pre_text[0] = 0; + total_items = 0; + items_now = 0; + UnlockLooper(); + } + if (hide && Window()) + window->RemoveView(this); +} + + +void +BMailStatusView::SetMaximum(int32 max_bytes) +{ + AddSelfToWindow(); + + if (LockLooper()) { + if (max_bytes < 0) { + status->SetMaxValue(total_items); + by_bytes = false; + } else { + status->SetMaxValue(max_bytes); + by_bytes = true; + } + UnlockLooper(); + } +} + + +void +BMailStatusView::SetTotalItems(int32 items) +{ + AddSelfToWindow(); + total_items = items; +} + + +int32 +BMailStatusView::CountTotalItems() +{ + return total_items; +} + + +void +BMailStatusView::AddItem(void) +{ + AddSelfToWindow(); + items_now++; + + if (!by_bytes) + AddProgress(1); +} + + +void +BMailStatusView::AddSelfToWindow() +{ + if (Window() != NULL) + return; + + window->ActuallyAddStatusView(this); +} + diff --git a/src/kits/mail/StringList.cpp b/src/kits/mail/StringList.cpp new file mode 100644 index 0000000000..428df42c3f --- /dev/null +++ b/src/kits/mail/StringList.cpp @@ -0,0 +1,355 @@ +/* BStringList - a string list implementation +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +#include +#include +#include + +class _EXPORT BStringList; + +#include "StringList.h" + +static uint8 string_hash(const char *string); + +static uint8 string_hash(const char *string) { + uint8 hash = 0; + for (int i = 0; string[i] != 0; i++) + hash ^= string[i]; + + return hash; +} + +struct string_bucket { + const char *string; + ~string_bucket() { + if (string != NULL) + free ((void *)(string)); + } + + struct string_bucket *next; +}; + +BStringList::BStringList() + : BFlattenable(), _items(0), _indexed(new BList) +{ + for (int32 i = 0; i < 256; i++) + _buckets[i] = NULL; +} + +BStringList::BStringList(const BStringList& from) + : BFlattenable(), _items(0), _indexed(new BList) +{ + for (int32 i = 0; i < 256; i++) + _buckets[i] = NULL; + + AddList(&from); +} + +BStringList &BStringList::operator=(const BStringList &from) { + MakeEmpty(); + + AddList(&from); + return *this; +} + +bool BStringList::IsFixedSize() const { + return false; +} + +type_code BStringList::TypeCode() const { + return 'STRL'; +} + +ssize_t BStringList::FlattenedSize() const { + ssize_t size = 0; + struct string_bucket *bkt; + for (int32 i = 0; i < 256; i++) { + bkt = (struct string_bucket *)(_buckets[i]); + while (bkt != NULL) { + size += (strlen(bkt->string) + 1); + bkt = bkt->next; + } + } + return size; +} + +status_t BStringList::Flatten(void *buffer, ssize_t size) const { + if (size < FlattenedSize()) + return B_NO_MEMORY; + + for (int32 i = 0; i < CountItems(); i++) { + memcpy(buffer, ItemAt(i), strlen(ItemAt(i)) + 1); + buffer = (void *)((const char *)(buffer) + (strlen(ItemAt(i)) + 1)); + } + + return B_OK; +} + +bool BStringList::AllowsTypeCode(type_code code) const { + return (code == 'STRL'); +} + +status_t BStringList::Unflatten(type_code c, const void *buf, ssize_t size) { + if (c != 'STRL') + return B_ERROR; + + const char *string = (const char *)(buf); + + for (off_t offset = 0; offset < size; offset ++) { + if (((int8 *)(buf))[offset] == 0) { + AddItem(string); + string = (const char *)buf + offset + 1; + } + } + + return B_OK; +} + +void BStringList::AddItem(const char *item) { + struct string_bucket *new_bkt; + + new_bkt = (struct string_bucket *)_buckets[string_hash(item)]; + if (new_bkt == NULL) { + new_bkt = new struct string_bucket; + new_bkt->string = strdup(item); + _indexed->AddItem((void *)(new_bkt->string)); + new_bkt->next = NULL; + _buckets[string_hash(item)] = new_bkt; + + _items++; + return; + } + + while (new_bkt->next != NULL) new_bkt = new_bkt->next; + + new_bkt->next = new struct string_bucket; + new_bkt = new_bkt->next; + new_bkt->string = strdup(item); + _indexed->AddItem((void *)(new_bkt->string)); + + new_bkt->next = NULL; + _items++; +} + +void BStringList::AddList(const BStringList *newItems) { + for (int32 i = 0; i < newItems->CountItems(); i++) + AddItem((const char *)(newItems->ItemAt(i))); + + /*struct string_bucket *bkt, *new_bkt; + for (int32 i = 0; i < 256; i++) { + bkt = (struct string_bucket *)(newItems->_buckets[i]); + while (bkt != NULL) { + new_bkt = (struct string_bucket *)_buckets[i]; + if (new_bkt == NULL) { + new_bkt = new struct string_bucket; + new_bkt->string = strdup(bkt->string); + _indexed->Add + new_bkt->next = NULL; + _buckets[i] = new_bkt; + _items++; + continue; + } + + while (new_bkt->next != NULL) new_bkt = new_bkt->next; + + new_bkt->next = new struct string_bucket; + new_bkt = new_bkt->next; + new_bkt->string = strdup(bkt->string); + new_bkt->next = NULL; + _items++; + + bkt = bkt->next; + } + }*/ +} + +bool BStringList::RemoveItem(const char *item) { + struct string_bucket *bkt = (struct string_bucket *)_buckets[string_hash(item)]; + + if (bkt == NULL) + return false; + + if (strcmp(bkt->string,item) == 0) { + _indexed->RemoveItem(IndexOf(item)); + _buckets[string_hash(item)] = bkt->next; + delete bkt; + _items--; + return true; + } + + struct string_bucket *tmp_bkt; + + while (bkt->next != NULL) { + if (strcmp(bkt->next->string,item) == 0) { + _indexed->RemoveItem(IndexOf(item)); + + tmp_bkt = bkt->next; + bkt->next = bkt->next->next; + delete tmp_bkt; + _items--; + + return true; + } + + bkt = bkt->next; + } + + return false; +} + +void BStringList::MakeEmpty() { + struct string_bucket *bkt, *next_bkt; + + for (int i = 0; i < 256; i++) { + bkt = (struct string_bucket *)_buckets[i]; + _buckets[i] = NULL; + + while (bkt != NULL) { + next_bkt = bkt->next; + delete bkt; + + bkt = next_bkt; + } + } + + _indexed->MakeEmpty(); + + _items = 0; +} + +const char *BStringList::ItemAt(int32 index) const { + return (const char *)(_indexed->ItemAt(index)); +} + +int32 BStringList::IndexOf(const char *item) const { + for (int32 i = 0; i < _indexed->CountItems(); i++) { + if (strcmp(item,(const char *)_indexed->ItemAt(i)) == 0) + return i; + } + + return -1; +} + +bool BStringList::HasItem(const char *item) const { + struct string_bucket *bkt = (struct string_bucket *)_buckets[string_hash(item)]; + + while (bkt != NULL) { + if (strcmp(bkt->string,item) == 0) + return true; + + bkt = bkt->next; + } + + return false; +} + +int32 BStringList::CountItems() const { + return _items; +} + +bool BStringList::IsEmpty() const { + return (_items == 0); +} + +void BStringList::NotHere(BStringList &other_list, BStringList *results) { + #if DEBUG + assert(_items == _indexed->CountItems()); + assert(other_list._items == other_list._indexed->CountItems()); + #endif + + for (int32 i = 0; i < other_list.CountItems(); i++) { + if (!HasItem(other_list[i])) + results->AddItem(other_list[i]); + } +} + +void BStringList::NotThere(BStringList &other_list, BStringList *results) { + other_list.NotHere(*this,results); +} + +BStringList &BStringList::operator += (const char *item) { + AddItem(item); + return *this; +} + +BStringList &BStringList::operator += (BStringList &list) { + AddList(&list); + return *this; +} + +BStringList &BStringList::operator -= (const char *item) { + RemoveItem(item); + return *this; +} + +BStringList &BStringList::operator -= (BStringList &list) { + for (int32 i = 0; i < list.CountItems(); i++) + RemoveItem(list[i]); + + return *this; +} + +BStringList BStringList::operator | (BStringList &list2) { + BStringList list(*this); + for (int32 i = 0; i < list2.CountItems(); i++) { + if (!list.HasItem(list2.ItemAt(i))) + list += list2.ItemAt(i); + } + + return list; +} + +BStringList &BStringList::operator |= (BStringList &list2) { + for (int32 i = 0; i < list2.CountItems(); i++) { + if (!HasItem(list2.ItemAt(i))) + AddItem(list2.ItemAt(i)); + } + + return *this; +} + +BStringList BStringList::operator ^ (BStringList &list2) { + BStringList list; + for (int32 i = 0; i < CountItems(); i++) { + if (!list2.HasItem(ItemAt(i))) + list += ItemAt(i); + } + for (int32 i = 0; i < list2.CountItems(); i++) { + if (!HasItem(list2.ItemAt(i))) + list += list2.ItemAt(i); + } + return list; +} + +BStringList &BStringList::operator ^= (BStringList &list) { + return (*this = *this ^ list); +} + +bool BStringList::operator == (BStringList &list) { + if (list.CountItems() != CountItems()) + return false; + + for (int32 i = 0; i < CountItems(); i++) { + if (strcmp(list.ItemAt(i),ItemAt(i)) != 0) + return false; + } + + return true; +} + +const char *BStringList::operator [] (int32 index) { + return ItemAt(index); +} + +BStringList::~BStringList() { + MakeEmpty(); + + delete _indexed; +} + + diff --git a/src/kits/mail/b_mail_message.cpp b/src/kits/mail/b_mail_message.cpp new file mode 100644 index 0000000000..89a579330e --- /dev/null +++ b/src/kits/mail/b_mail_message.cpp @@ -0,0 +1,138 @@ +/* BMailMessage - compatibility wrapper to our mail message class +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +//------This entire document is a horrible, horrible hack. I apologize. +#include + +class _EXPORT BMailMessage; + +#include + +#include +#include + +#include + +struct CharsetConversionEntry +{ + const char *charset; + uint32 flavor; +}; + +extern const CharsetConversionEntry mail_charsets[]; + + +BMailMessage::BMailMessage(void) + : fFields((BList *)(new BEmailMessage())) +{ +} + +BMailMessage::~BMailMessage(void) +{ + delete ((BEmailMessage *)(fFields)); +} + +status_t BMailMessage::AddContent(const char *text, int32 length, + uint32 encoding, bool /*clobber*/) +{ + BTextMailComponent *comp = new BTextMailComponent; + BMemoryIO io(text,length); + comp->SetDecodedData(&io); + + comp->SetEncoding(quoted_printable,encoding); + + //if (clobber) + ((BEmailMessage *)(fFields))->AddComponent(comp); + + return B_OK; +} + +status_t BMailMessage::AddContent(const char *text, int32 length, + const char *encoding, bool /*clobber*/) +{ + BTextMailComponent *comp = new BTextMailComponent(); + BMemoryIO io(text,length); + comp->SetDecodedData(&io); + + uint32 encode = B_ISO1_CONVERSION; + //-----I'm assuming that encoding is one of the RFC charsets + //-----there are no docs. Am I right? + if (encoding != NULL) { + for (int32 i = 0; mail_charsets[i].charset != NULL; i++) { + if (strcasecmp(encoding,mail_charsets[i].charset) == 0) { + encode = mail_charsets[i].flavor; + break; + } + } + } + + comp->SetEncoding(quoted_printable,encode); + + //if (clobber) + ((BEmailMessage *)(fFields))->AddComponent(comp); + + return B_OK; +} + +status_t BMailMessage::AddEnclosure(entry_ref *ref, bool /*clobber*/) +{ + ((BEmailMessage *)(fFields))->Attach(ref); + return B_OK; +} + +status_t BMailMessage::AddEnclosure(const char *path, bool /*clobber*/) +{ + BEntry entry(path); + status_t status; + if ((status = entry.InitCheck()) < B_OK) + return status; + + entry_ref ref; + if ((status = entry.GetRef(&ref)) < B_OK) + return status; + + ((BEmailMessage *)(fFields))->Attach(&ref); + return B_OK; +} + +status_t BMailMessage::AddEnclosure(const char *MIME_type, void *data, int32 len, + bool /*clobber*/) +{ + BSimpleMailAttachment *attach = new BSimpleMailAttachment; + attach->SetDecodedData(data,len); + attach->SetHeaderField("Content-Type",MIME_type); + + ((BEmailMessage *)(fFields))->AddComponent(attach); + return B_OK; +} + +status_t BMailMessage::AddHeaderField(uint32 /*encoding*/, const char *field_name, const char *str, + bool /*clobber*/) +{ + //printf("First AddHeaderField. Args are %s%s\n",field_name,str); + + BString string = field_name; + string.Truncate(string.Length() - 2); //----BMailMessage includes the ": " + ((BEmailMessage *)(fFields))->SetHeaderField(string.String(),str); + return B_OK; +} + +status_t BMailMessage::AddHeaderField(const char *field_name, const char *str, + bool /*clobber*/) +{ + //printf("Second AddHeaderField. Args are %s%s\n",field_name,str); + BString string = field_name; + string.Truncate(string.Length() - 2); //----BMailMessage includes the ": " + ((BEmailMessage *)(fFields))->SetHeaderField(string.String(),str); + return B_OK; +} + +status_t BMailMessage::Send(bool send_now, + bool /*remove_when_I_have_completed_sending_this_message_to_your_preferred_SMTP_server*/) +{ + return ((BEmailMessage *)(fFields))->Send(send_now); +} + diff --git a/src/kits/mail/c_mail_api.cpp b/src/kits/mail/c_mail_api.cpp new file mode 100644 index 0000000000..2589472a4e --- /dev/null +++ b/src/kits/mail/c_mail_api.cpp @@ -0,0 +1,141 @@ +/* C-mail API - compatibility function (stubs) for the old mail kit +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + + +_EXPORT status_t check_for_mail(int32 * incoming_count) +{ + status_t err = BMailDaemon::CheckMail(true); + if (err < B_OK) + return err; + + if (incoming_count != NULL) + *incoming_count = BMailDaemon::CountNewMessages(true); + + return B_OK; +} + +_EXPORT status_t send_queued_mail(void) +{ + return BMailDaemon::SendQueuedMail(); +} + +_EXPORT int32 count_pop_accounts(void) +{ + BPath path; + status_t status = find_directory(B_USER_SETTINGS_DIRECTORY,&path); + if (status < B_OK) + return 0; + + path.Append("Mail/chains/inbound"); + BDirectory dir(path.Path()); + return dir.CountEntries(); +} + +_EXPORT status_t get_mail_notification(mail_notification *notification) +{ + notification->alert = true; + notification->beep = false; + return B_OK; +} + +_EXPORT status_t set_mail_notification(mail_notification *, bool) +{ + return B_NO_REPLY; +} + +_EXPORT status_t get_pop_account(mail_pop_account* account, int32 index) +{ + status_t err = B_OK; + const char *password, *passwd; + BMessage settings; + + BList chains; + GetInboundMailChains(&chains); + + BMailChain *chain = (BMailChain *)(chains.ItemAt(index)); + if (chain == NULL) { + err = B_BAD_INDEX; + goto clean_up; //------Eek! A goto! + } + + chain->GetFilter(0,&settings); + strcpy(account->pop_name,settings.FindString("username")); + strcpy(account->pop_host,settings.FindString("server")); + strcpy(account->real_name,chain->MetaData()->FindString("real_name")); + strcpy(account->reply_to,chain->MetaData()->FindString("reply_to")); + + password = settings.FindString("password"); + passwd = get_passwd(&settings,"cpasswd"); + if (passwd) + password = passwd; + strcpy(account->pop_password,password); + + //-------Note that we don't do the scheduling flags + + clean_up: + for (int32 i = 0; i < chains.CountItems(); i++) + delete (BMailChain *)chains.ItemAt(i); + + return err; +} + +_EXPORT status_t set_pop_account(mail_pop_account *, int32, bool) +{ + return B_NO_REPLY; +} + +_EXPORT status_t get_smtp_host(char* buffer) +{ + BMailChain chain(BMailSettings().DefaultOutboundChainID()); + status_t err = chain.InitCheck(); + if (err < B_OK) + return err; + + BMessage settings; + err = chain.GetFilter(chain.CountFilters() - 1,&settings); + if (err < B_OK) + return err; + + if (settings.HasString("server")) + strcpy(buffer,settings.FindString("server")); + else + return B_NAME_NOT_FOUND; + + return B_OK; +} + +_EXPORT status_t set_smtp_host(char * /* host */, bool /* save */) +{ + return B_NO_REPLY; +} + +_EXPORT status_t forward_mail(entry_ref *ref, const char *recipients, bool now) +{ + BFile file(ref, O_RDONLY); + status_t status = file.InitCheck(); + if (status < B_OK) + return status; + + BEmailMessage mail(&file); + mail.SetTo(recipients); + + return mail.Send(now); +} + diff --git a/src/kits/mail/cpp_abi_base64.c b/src/kits/mail/cpp_abi_base64.c new file mode 100644 index 0000000000..b7e31e68af --- /dev/null +++ b/src/kits/mail/cpp_abi_base64.c @@ -0,0 +1,25 @@ +#include + +#if __MWERKS__ + #define encode_base64__local_abi encode_base64__FPcPcx + #define decode_base64__local_abi decode_base64__FPcPcxb +#elif __GNUC__ <= 2 + #define encode_base64__local_abi encode_base64__FPcT0x + #define decode_base64__local_abi decode_base64__FPcT0xb +#else + #error "We don't seem to have a C++ ABI hack for your compiler. Please add one." +#endif + + +ssize_t encode_base64__local_abi(char *out, char *in, off_t length); +ssize_t decode_base64__local_abi(char *out, char *in, off_t length, char); + +_EXPORT ssize_t encode_base64__local_abi(char *out, char *in, off_t length) { + return encode_base64(out,in,length,0 /* headerMode */); +} + +_EXPORT ssize_t decode_base64__local_abi(char *out, char *in, off_t length, char nothing) { + nothing = '\0'; + return decode_base64(out,in,length); +} + diff --git a/src/kits/mail/crypt.cpp b/src/kits/mail/crypt.cpp new file mode 100644 index 0000000000..27a2126e5a --- /dev/null +++ b/src/kits/mail/crypt.cpp @@ -0,0 +1,58 @@ +/* crypt - simple encryption algorithm used for passwords +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +#include +#include + + +static const char key[PASSWORD_LENGTH + 1] = "Dr. Zoidberg Enterprises, BeMail"; + + +_EXPORT char *get_passwd(BMessage *msg,const char *name) +{ + char *encryptedPassword; + ssize_t length; + if (msg->FindData(name,B_RAW_TYPE,(const void **)&encryptedPassword,&length) < B_OK || !encryptedPassword || length == 0) + return NULL; + + char *buffer = new char[length]; + passwd_crypt(encryptedPassword,buffer,length); + + return buffer; +} + + +_EXPORT bool set_passwd(BMessage *msg,const char *name,const char *password) +{ + if (!password) + return false; + + ssize_t length = strlen(password) + 1; + char *buffer = new char[length]; + passwd_crypt((char *)password,buffer,length); + + msg->RemoveName(name); + status_t status = msg->AddData(name,B_RAW_TYPE,buffer,length,false); + + delete [] buffer; + return (status >= B_OK); +} + + +_EXPORT void passwd_crypt(char *in,char *out,int length) +{ + int i; + + memcpy(out,in,length); + if (length > PASSWORD_LENGTH) + length = PASSWORD_LENGTH; + + for (i = 0;i < length;i++) + out[i] ^= key[i]; +} + diff --git a/src/kits/mail/des.c b/src/kits/mail/des.c new file mode 100644 index 0000000000..8e9e6d8178 --- /dev/null +++ b/src/kits/mail/des.c @@ -0,0 +1,434 @@ +/* DES - encryption algorithm, removed double and triple DES +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + +/* D3DES (V5.09) - + * + * A portable, public domain, version of the Data Encryption Standard. + * + * Written with Symantec's THINK (Lightspeed) C by Richard Outerbridge. + * Thanks to: Dan Hoey for his excellent Initial and Inverse permutation + * code; Jim Gillogly & Phil Karn for the DES key schedule code; Dennis + * Ferguson, Eric Young and Dana How for comparing notes; and Ray Lau, + * for humouring me on. + * + * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge. + * (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992. + */ + +#include +#include + +#define ZOIDBERG_KEY "Zoidberg" + +static void scrunch(unsigned char *, unsigned long *); +static void unscrun(unsigned long *, unsigned char *); +static void desfunc(unsigned long *, unsigned long *); +static void cookey(unsigned long *); + +static unsigned long KnL[32] = { 0L }; +//static unsigned long KnR[32] = { 0L }; +//static unsigned long Kn3[32] = { 0L }; +//static unsigned char Df_Key[24] = { +// 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef, +// 0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10, +// 0x89,0xab,0xcd,0xef,0x01,0x23,0x45,0x67 }; + +static unsigned short bytebit[8] = { + 0200, 0100, 040, 020, 010, 04, 02, 01 }; + +static unsigned long bigbyte[24] = { + 0x800000L, 0x400000L, 0x200000L, 0x100000L, + 0x80000L, 0x40000L, 0x20000L, 0x10000L, + 0x8000L, 0x4000L, 0x2000L, 0x1000L, + 0x800L, 0x400L, 0x200L, 0x100L, + 0x80L, 0x40L, 0x20L, 0x10L, + 0x8L, 0x4L, 0x2L, 0x1L }; + +/* Use the key schedule specified in the Standard (ANSI X3.92-1981). */ + +static unsigned char pc1[56] = { + 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 }; + +static unsigned char totrot[16] = { + 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 }; + +static unsigned char pc2[48] = { + 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, + 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, + 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, + 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 }; + + +void des_decrypt(char *in,int length,char *out) +{ + des_setkey((unsigned char *)(ZOIDBERG_KEY),DES_DECRYPT); + length = (length + 7) / 8; + while (length-- > 0) + { + des_crypt((unsigned char *)(in),(unsigned char *)(out)); + in += 8; out += 8; + } +} + + +void des_encrypt(char *in,char *out) +{ + int length = (strlen(in) + 7) / 8; + + des_setkey((unsigned char *)(ZOIDBERG_KEY),DES_ENCRYPT); + while (length-- > 0) + { + des_crypt((unsigned char *)(in),(unsigned char *)(out)); + in += 8; out += 8; + } +} + + +void des_setkey(unsigned char *key,short edf) /* Thanks to James Gillogly & Phil Karn! */ +{ + register int i, j, l, m, n; + unsigned char pc1m[56], pcr[56]; + unsigned long kn[32]; + + for ( j = 0; j < 56; j++ ) + { + l = pc1[j]; + m = l & 07; + pc1m[j] = (key[l >> 3] & bytebit[m]) ? 1 : 0; + } + for( i = 0; i < 16; i++ ) + { + if (edf == DES_DECRYPT) m = (15 - i) << 1; + else m = i << 1; + n = m + 1; + kn[m] = kn[n] = 0L; + for (j = 0;j < 28;j++) + { + l = j + totrot[i]; + if (l < 28) pcr[j] = pc1m[l]; + else pcr[j] = pc1m[l - 28]; + } + for (j = 28;j < 56;j++) + { + l = j + totrot[i]; + if (l < 56) pcr[j] = pc1m[l]; + else pcr[j] = pc1m[l - 28]; + } + for (j = 0;j < 24;j++) + { + if( pcr[pc2[j]] ) kn[m] |= bigbyte[j]; + if( pcr[pc2[j+24]] ) kn[n] |= bigbyte[j]; + } + } + cookey(kn); +} + + +static void cookey(register unsigned long *raw1) +{ + register unsigned long *cook, *raw0; + unsigned long dough[32]; + register int i; + + cook = dough; + for( i = 0; i < 16; i++, raw1++ ) + { + raw0 = raw1++; + *cook = (*raw0 & 0x00fc0000L) << 6; + *cook |= (*raw0 & 0x00000fc0L) << 10; + *cook |= (*raw1 & 0x00fc0000L) >> 10; + *cook++ |= (*raw1 & 0x00000fc0L) >> 6; + *cook = (*raw0 & 0x0003f000L) << 12; + *cook |= (*raw0 & 0x0000003fL) << 16; + *cook |= (*raw1 & 0x0003f000L) >> 4; + *cook++ |= (*raw1 & 0x0000003fL); + } + des_usekey(dough); +} + + +void des_cpkey(register unsigned long *into) +{ + register unsigned long *from, *endp; + + from = KnL, endp = &KnL[32]; + while( from < endp ) *into++ = *from++; +} + + +void des_usekey(register unsigned long *from) +{ + register unsigned long *to, *endp; + + to = KnL, endp = &KnL[32]; + while( to < endp ) *to++ = *from++; +} + + +void des_crypt(unsigned char *inblock,unsigned char *outblock) +{ + unsigned long work[2]; + + scrunch(inblock, work); + desfunc(work, KnL); + unscrun(work, outblock); +} + + +static void scrunch(register unsigned char *outof,register unsigned long *into) +{ + *into = (*outof++ & 0xffL) << 24; + *into |= (*outof++ & 0xffL) << 16; + *into |= (*outof++ & 0xffL) << 8; + *into++ |= (*outof++ & 0xffL); + *into = (*outof++ & 0xffL) << 24; + *into |= (*outof++ & 0xffL) << 16; + *into |= (*outof++ & 0xffL) << 8; + *into |= (*outof & 0xffL); +} + + +static void unscrun(register unsigned long *outof,register unsigned char *into) +{ + *into++ = (*outof >> 24) & 0xffL; + *into++ = (*outof >> 16) & 0xffL; + *into++ = (*outof >> 8) & 0xffL; + *into++ = *outof++ & 0xffL; + *into++ = (*outof >> 24) & 0xffL; + *into++ = (*outof >> 16) & 0xffL; + *into++ = (*outof >> 8) & 0xffL; + *into = *outof & 0xffL; +} + +static unsigned long SP1[64] = { + 0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L, + 0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L, + 0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L, + 0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L, + 0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L, + 0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L, + 0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L, + 0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L, + 0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L, + 0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L, + 0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L, + 0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L, + 0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L, + 0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L, + 0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L, + 0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L }; + +static unsigned long SP2[64] = { + 0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L, + 0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L, + 0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L, + 0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L, + 0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L, + 0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L, + 0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L, + 0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L, + 0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L, + 0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L, + 0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L, + 0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L, + 0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L, + 0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L, + 0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L, + 0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L }; + +static unsigned long SP3[64] = { + 0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L, + 0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L, + 0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L, + 0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L, + 0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L, + 0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L, + 0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L, + 0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L, + 0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L, + 0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L, + 0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L, + 0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L, + 0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L, + 0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L, + 0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L, + 0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L }; + +static unsigned long SP4[64] = { + 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, + 0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L, + 0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L, + 0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L, + 0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L, + 0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L, + 0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L, + 0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L, + 0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L, + 0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L, + 0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L, + 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, + 0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L, + 0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L, + 0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L, + 0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L }; + +static unsigned long SP5[64] = { + 0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L, + 0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L, + 0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L, + 0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L, + 0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L, + 0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L, + 0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L, + 0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L, + 0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L, + 0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L, + 0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L, + 0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L, + 0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L, + 0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L, + 0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L, + 0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L }; + +static unsigned long SP6[64] = { + 0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L, + 0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L, + 0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L, + 0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L, + 0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L, + 0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L, + 0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L, + 0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L, + 0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L, + 0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L, + 0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L, + 0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L, + 0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L, + 0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L, + 0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L, + 0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L }; + +static unsigned long SP7[64] = { + 0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L, + 0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L, + 0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L, + 0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L, + 0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L, + 0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L, + 0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L, + 0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L, + 0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L, + 0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L, + 0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L, + 0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L, + 0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L, + 0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L, + 0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L, + 0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L }; + +static unsigned long SP8[64] = { + 0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L, + 0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L, + 0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L, + 0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L, + 0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L, + 0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L, + 0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L, + 0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L, + 0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L, + 0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L, + 0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L, + 0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L, + 0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L, + 0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L, + 0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L, + 0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L }; + + +static void desfunc(register unsigned long *block,unsigned long *keys) +{ + register unsigned long fval, work, right, leftt; + register int round; + + leftt = block[0]; + right = block[1]; + work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL; + right ^= work; + leftt ^= (work << 4); + work = ((leftt >> 16) ^ right) & 0x0000ffffL; + right ^= work; + leftt ^= (work << 16); + work = ((right >> 2) ^ leftt) & 0x33333333L; + leftt ^= work; + right ^= (work << 2); + work = ((right >> 8) ^ leftt) & 0x00ff00ffL; + leftt ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL; + work = (leftt ^ right) & 0xaaaaaaaaL; + leftt ^= work; + right ^= work; + leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL; + + for( round = 0; round < 8; round++ ) + { + work = (right << 28) | (right >> 4); + work ^= *keys++; + fval = SP7[ work & 0x3fL]; + fval |= SP5[(work >> 8) & 0x3fL]; + fval |= SP3[(work >> 16) & 0x3fL]; + fval |= SP1[(work >> 24) & 0x3fL]; + work = right ^ *keys++; + fval |= SP8[ work & 0x3fL]; + fval |= SP6[(work >> 8) & 0x3fL]; + fval |= SP4[(work >> 16) & 0x3fL]; + fval |= SP2[(work >> 24) & 0x3fL]; + leftt ^= fval; + work = (leftt << 28) | (leftt >> 4); + work ^= *keys++; + fval = SP7[ work & 0x3fL]; + fval |= SP5[(work >> 8) & 0x3fL]; + fval |= SP3[(work >> 16) & 0x3fL]; + fval |= SP1[(work >> 24) & 0x3fL]; + work = leftt ^ *keys++; + fval |= SP8[ work & 0x3fL]; + fval |= SP6[(work >> 8) & 0x3fL]; + fval |= SP4[(work >> 16) & 0x3fL]; + fval |= SP2[(work >> 24) & 0x3fL]; + right ^= fval; + } + + right = (right << 31) | (right >> 1); + work = (leftt ^ right) & 0xaaaaaaaaL; + leftt ^= work; + right ^= work; + leftt = (leftt << 31) | (leftt >> 1); + work = ((leftt >> 8) ^ right) & 0x00ff00ffL; + right ^= work; + leftt ^= (work << 8); + work = ((leftt >> 2) ^ right) & 0x33333333L; + right ^= work; + leftt ^= (work << 2); + work = ((right >> 16) ^ leftt) & 0x0000ffffL; + leftt ^= work; + right ^= (work << 16); + work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL; + leftt ^= work; + right ^= (work << 4); + *block++ = right; + *block = leftt; +} + + +/* Validation sets: + * + * Single-length key, single-length plaintext - + * Key : 0123 4567 89ab cdef + * Plain : 0123 4567 89ab cde7 + * Cipher : c957 4425 6a5e d31d + */ diff --git a/src/kits/mail/mail_encoding.c b/src/kits/mail/mail_encoding.c new file mode 100644 index 0000000000..2213dccada --- /dev/null +++ b/src/kits/mail/mail_encoding.c @@ -0,0 +1,342 @@ +#include +#include + +#include + +#define DEC(Char) (((Char) - ' ') & 077) + +typedef unsigned char uchar; + +char base64_alphabet[64] = { //----Fast lookup table + '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', + '+', + '/' + }; + +const char hex_alphabet[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + + +_EXPORT ssize_t +encode(mail_encoding encoding, char *out, const char *in, off_t length, int headerMode) +{ + switch (encoding) { + case base64: + return encode_base64(out,in,length,headerMode); + case quoted_printable: + return encode_qp(out,in,length,headerMode); + case seven_bit: + case eight_bit: + case no_encoding: + memcpy(out,in,length); + return length; + case uuencode: + default: + return -1; + } + + return -1; +} + + +_EXPORT ssize_t +decode(mail_encoding encoding, char *out, const char *in, off_t length, int underscore_is_space) +{ + switch (encoding) { + case base64: + return decode_base64(out, in, length); + case uuencode: + return uu_decode(out, in, length); + case seven_bit: + case eight_bit: + case no_encoding: + memcpy(out, in, length); + return length; + case quoted_printable: + return decode_qp(out, in, length, underscore_is_space); + default: + break; + } + + return -1; +} + + +_EXPORT ssize_t +max_encoded_length(mail_encoding encoding, off_t cur_length) +{ + switch (encoding) { + case base64: + { + double result; + result = cur_length; + result *= 1.33333333333333; + result += (result / BASE64_LINELENGTH)*2 + 20; + return (ssize_t)(result); + } + case quoted_printable: + return cur_length*3; + case seven_bit: + case eight_bit: + case no_encoding: + return cur_length; + case uuencode: + default: + return -1; + } + + return -1; +} + + +_EXPORT mail_encoding +encoding_for_cte(const char *cte) +{ + if (cte == NULL) + return no_encoding; + + if (strcasecmp(cte,"uuencode") == 0) + return uuencode; + if (strcasecmp(cte,"base64") == 0) + return base64; + if (strcasecmp(cte,"quoted-printable") == 0) + return quoted_printable; + if (strcasecmp(cte,"7bit") == 0) + return seven_bit; + if (strcasecmp(cte,"8bit") == 0) + return eight_bit; + + return no_encoding; +} + + +_EXPORT ssize_t +encode_base64(char *out, const char *in, off_t length, int headerMode) +{ + unsigned long concat; + int i = 0; + int k = 0; + int curr_linelength = 4; //--4 is a safety extension, designed to cause retirement *before* it actually gets too long + + while (i < length) { + concat = ((in[i] & 0xff) << 16); + + if ((i+1) < length) + concat |= ((in[i+1] & 0xff) << 8); + if ((i+2) < length) + concat |= (in[i+2] & 0xff); + + i += 3; + + out[k++] = base64_alphabet[(concat >> 18) & 63]; + out[k++] = base64_alphabet[(concat >> 12) & 63]; + out[k++] = base64_alphabet[(concat >> 6) & 63]; + out[k++] = base64_alphabet[concat & 63]; + + if (i >= length) { + int v; + for (v = 0; v <= (i - length); v++) + out[k-v] = '='; + } + + curr_linelength += 4; + + // No line breaks in header mode, since the text is part of a Subject: + // line or some other single header line. The header code will do word + // wrapping separately from this encoding stuff. + if (!headerMode && curr_linelength > BASE64_LINELENGTH) { + out[k++] = '\r'; + out[k++] = '\n'; + + curr_linelength = 4; + } + } + + return k; +} + + +_EXPORT ssize_t +decode_base64(char *out, const char *in, off_t length) +{ + unsigned long concat, value; + int lastOutLine = 0; + int i, j; + int outIndex = 0; + + for (i = 0; i < length; i += 4) { + concat = 0; + + for (j = 0; j < 4 && (i + j) < length; j++) { + value = in[i + j]; + + if (value == '\n' || value == '\r') { + // jump over line breaks + lastOutLine = outIndex; + i++; + j--; + continue; + } + + if ((value >= 'A') && (value <= 'Z')) + value -= 'A'; + else if ((value >= 'a') && (value <= 'z')) + value = value - 'a' + 26; + else if ((value >= '0') && (value <= '9')) + value = value - '0' + 52; + else if (value == '+') + value = 62; + else if (value == '/') + value = 63; + else if (value == '=') + break; + else { + // there is an invalid character in this line - we will + // ignore the whole line and go to the next + outIndex = lastOutLine; + while (i < length && in[i] != '\n' && in[i] != '\r') + i++; + concat = 0; + } + + value = value << ((3-j)*6); + + concat |= value; + } + + if (j > 1) + out[outIndex++] = (concat & 0x00ff0000) >> 16; + if (j > 2) + out[outIndex++] = (concat & 0x0000ff00) >> 8; + if (j > 3) + out[outIndex++] = (concat & 0x000000ff); + } + + return outIndex; +} + + +_EXPORT ssize_t +decode_qp(char *out, const char *in, off_t length, int underscore_is_space) +{ + // decode Quoted Printable + char *dataout = out; + const char *datain = in, *dataend = in+length; + + while ( datain < dataend ) + { + if (*datain == '=' && dataend-datain>2) + { + int a,b; + + a = toupper(datain[1]); + a -= a>='0' && a<='9'? '0' : (a>='A' && a<='F'? 'A'-10 : a+1); + + b = toupper(datain[2]); + b -= b>='0' && b<='9'? '0' : (b>='A' && b<='F'? 'A'-10 : b+1); + + if (a>=0 && b>=0) + { + *dataout++ = (a<<4) + b; + datain += 3; + continue; + } else if (datain[1]=='\r' && datain[2]=='\n') { + // strip = + datain += 3; + continue; + } + } + else if ((*datain == '_') && (underscore_is_space)) + { + *dataout++ = ' '; + ++datain; + continue; + } + + *dataout++ = *datain++; + } + + *dataout = '\0'; + return dataout-out; +} + + +_EXPORT ssize_t +encode_qp(char *out, const char *in, off_t length, int headerMode) +{ + int g = 0, i = 0; + + for (; i < length; i++) { + if ((((unsigned char *)(in))[i] > 127) || + (in[i] == '?') || + (in[i] == '=') || + (in[i] == '_') || + // Also encode the letter F in "From " at the start of the line, + // which Unix systems use to mark the start of messages in their + // mbox files. + (in[i] == 'F' && + (i + 5 <= length) && + (i == 0 || in[i-1] == '\n') && + in[i+1] == 'r' && + in[i+2] == 'o' && + in[i+3] == 'm' && + in[i+4] == ' ')) { + out[g++] = '='; + out[g++] = hex_alphabet[(in[i] >> 4) & 0x0f]; + out[g++] = hex_alphabet[in[i] & 0x0f]; + } + else if (headerMode && (in[i] == ' ' || in[i] == '\t')) + out[g++] = '_'; + else if (headerMode && (in[i] >= 0 && in[i] < 32)) { + // Control codes in headers need to be sanitized, otherwise certain + // Japanese ISPs mangle the headers badly. But they don't mangle + // the body. + out[g++] = '='; + out[g++] = hex_alphabet[(in[i] >> 4) & 0x0f]; + out[g++] = hex_alphabet[in[i] & 0x0f]; + } else + out[g++] = in[i]; + } + + return g; +} + + +_EXPORT ssize_t +uu_decode(char *out, const char *in, off_t length) +{ + long n; + uchar *p,*inBuffer = (uchar *)in; + uchar *outBuffer = (uchar *)out; + + inBuffer = (uchar *)strstr((char *)inBuffer, "begin"); + goto enterLoop; + + while (((inBuffer - (uchar *)in) <= length) && strncmp((char *)inBuffer, "end", 3)) { + p = inBuffer; + n = DEC(inBuffer[0]); + + for (++inBuffer; n > 0; inBuffer += 4, n -= 3) { + if (n >= 3) { + *outBuffer++ = DEC(inBuffer[0]) << 2 | DEC (inBuffer[1]) >> 4; + *outBuffer++ = DEC(inBuffer[1]) << 4 | DEC (inBuffer[2]) >> 2; + *outBuffer++ = DEC(inBuffer[2]) << 6 | DEC (inBuffer[3]); + } else { + if (n >= 1) *outBuffer++ = DEC(inBuffer[0]) << 2 + | DEC (inBuffer[1]) >> 4; + if (n >= 2) *outBuffer++ = DEC(inBuffer[1]) << 4 + | DEC (inBuffer[2]) >> 2; + } + } + inBuffer = p; + + enterLoop: + while ((inBuffer[0] != '\n') && (inBuffer[0] != '\r') + && (inBuffer[0] != 0)) inBuffer++; + while ((inBuffer[0] == '\n') || (inBuffer[0] == '\r')) inBuffer++; + } + + return (ssize_t)(outBuffer - ((uchar *)in)); +} + diff --git a/src/kits/mail/mail_util.cpp b/src/kits/mail/mail_util.cpp new file mode 100644 index 0000000000..c3fec1ad55 --- /dev/null +++ b/src/kits/mail/mail_util.cpp @@ -0,0 +1,1439 @@ +/* mail util - header parsing +** +** Copyright 2001-2003 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define CRLF "\r\n" + +struct CharsetConversionEntry +{ + const char *charset; + uint32 flavor; +}; + +extern const CharsetConversionEntry mail_charsets [] = +{ + // In order of authority, so when searching for the name for a particular + // numbered conversion, start at the beginning of the array. + {"iso-8859-1", B_ISO1_CONVERSION}, + {"iso-8859-2", B_ISO2_CONVERSION}, + {"iso-8859-3", B_ISO3_CONVERSION}, + {"iso-8859-4", B_ISO4_CONVERSION}, + {"iso-8859-5", B_ISO5_CONVERSION}, + {"iso-8859-6", B_ISO6_CONVERSION}, + {"iso-8859-7", B_ISO7_CONVERSION}, + {"iso-8859-8", B_ISO8_CONVERSION}, + {"iso-8859-9", B_ISO9_CONVERSION}, + {"iso-8859-10", B_ISO10_CONVERSION}, + {"iso-8859-13", B_ISO13_CONVERSION}, + {"iso-8859-14", B_ISO14_CONVERSION}, + {"iso-8859-15", B_ISO15_CONVERSION}, + {"shift-jis", B_SJIS_CONVERSION}, + {"iso-2022-jp", B_JIS_CONVERSION}, + {"euc-jp", B_EUC_CONVERSION}, + {"euc-kr", B_EUC_KR_CONVERSION}, // Shift encoding 7 bit and KSC-5601 if bit 8 is on. + {"ksc5601", B_EUC_KR_CONVERSION}, // Not sure if 7 or 8 bit. + {"ks_c_5601-1987", B_EUC_KR_CONVERSION}, // Not sure if 7 or 8 bit. + {"koi8-r", B_KOI8R_CONVERSION}, + {"windows-1251",B_MS_WINDOWS_1251_CONVERSION}, + {"windows-1252",B_MS_WINDOWS_CONVERSION}, + {"dos-437", B_MS_DOS_CONVERSION}, + {"dos-866", B_MS_DOS_866_CONVERSION}, + {"x-mac-roman", B_MAC_ROMAN_CONVERSION}, + /* {"utf-16", B_UNICODE_CONVERSION}, Might not work due to NULs in text, needs testing. */ + {"us-ascii", B_MAIL_US_ASCII_CONVERSION}, + {"utf-8", B_MAIL_UTF8_CONVERSION /* Special code for no conversion */}, + {NULL, (uint32) -1} /* End of list marker, NULL string pointer is the key. */ +}; + + +// The next couple of functions are our wrapper around convert_to_utf8 and +// convert_from_utf8 so that they can also convert from UTF-8 to UTF-8 by +// specifying the B_MAIL_UTF8_CONVERSION constant as the conversion operation. It +// also lets us add new conversions, like B_MAIL_US_ASCII_CONVERSION. + +_EXPORT status_t mail_convert_to_utf8 ( + uint32 srcEncoding, + const char *src, + int32 *srcLen, + char *dst, + int32 *dstLen, + int32 *state, + char substitute) +{ + int32 copyAmount; + char *originalDst = dst; + status_t returnCode = -1; + + if (srcEncoding == B_MAIL_UTF8_CONVERSION) { + copyAmount = *srcLen; + if (*dstLen < copyAmount) + copyAmount = *dstLen; + memcpy (dst, src, copyAmount); + *srcLen = copyAmount; + *dstLen = copyAmount; + returnCode = B_OK; + } else if (srcEncoding == B_MAIL_US_ASCII_CONVERSION) { + int32 i; + unsigned char letter; + copyAmount = *srcLen; + if (*dstLen < copyAmount) + copyAmount = *dstLen; + for (i = 0; i < copyAmount; i++) { + letter = *src++; + if (letter > 0x80U) + // Invalid, could also use substitute, but better to strip high bit. + *dst++ = letter - 0x80U; + else if (letter == 0x80U) + // Can't convert to 0x00 since that's NUL, which would cause problems. + *dst++ = substitute; + else + *dst++ = letter; + } + *srcLen = copyAmount; + *dstLen = copyAmount; + returnCode = B_OK; + } else + returnCode = convert_to_utf8 (srcEncoding, src, srcLen, + dst, dstLen, state, substitute); + + if (returnCode == B_OK) { + // Replace spurious NUL bytes, which should normally not be in the + // output of the decoding (not normal UTF-8 characters, and no NULs are + // in our usual input strings). They happen for some odd ISO-2022-JP + // byte pair combinations which are improperly handled by the BeOS + // routines. Like "\e$ByD\e(B" where \e is the ESC character $1B, the + // first ESC $ B switches to a Japanese character set, then the next + // two bytes "yD" specify a character, then ESC ( B switches back to + // the ASCII character set. The UTF-8 conversion yields a NUL byte. + int32 i; + for (i = 0; i < *dstLen; i++) + if (originalDst[i] == 0) + originalDst[i] = substitute; + } + return returnCode; +} + + +_EXPORT status_t mail_convert_from_utf8 ( + uint32 dstEncoding, + const char *src, + int32 *srcLen, + char *dst, + int32 *dstLen, + int32 *state, + char substitute) +{ + int32 copyAmount; + status_t errorCode; + int32 originalDstLen = *dstLen; + int32 tempDstLen; + int32 tempSrcLen; + + if (dstEncoding == B_MAIL_UTF8_CONVERSION) + { + copyAmount = *srcLen; + if (*dstLen < copyAmount) + copyAmount = *dstLen; + memcpy (dst, src, copyAmount); + *srcLen = copyAmount; + *dstLen = copyAmount; + return B_OK; + } + + if (dstEncoding == B_MAIL_US_ASCII_CONVERSION) + { + int32 characterLength; + int32 dstRemaining = *dstLen; + unsigned char letter; + int32 srcRemaining = *srcLen; + + // state contains the number of source bytes to skip, left over from a + // partial UTF-8 character split over the end of the buffer from last + // time. + if (srcRemaining <= *state) { + *state -= srcRemaining; + *dstLen = 0; + return B_OK; + } + srcRemaining -= *state; + src += *state; + *state = 0; + + while (true) { + if (srcRemaining <= 0 || dstRemaining <= 0) + break; + letter = *src; + if (letter < 0x80) + characterLength = 1; // Regular ASCII equivalent code. + else if (letter < 0xC0) + characterLength = 1; // Invalid in-between data byte 10xxxxxx. + else if (letter < 0xE0) + characterLength = 2; + else if (letter < 0xF0) + characterLength = 3; + else if (letter < 0xF8) + characterLength = 4; + else if (letter < 0xFC) + characterLength = 5; + else if (letter < 0xFE) + characterLength = 6; + else + characterLength = 1; // 0xFE and 0xFF are invalid in UTF-8. + if (letter < 0x80) + *dst++ = *src; + else + *dst++ = substitute; + dstRemaining--; + if (srcRemaining < characterLength) { + // Character split past the end of the buffer. + *state = characterLength - srcRemaining; + srcRemaining = 0; + } else { + src += characterLength; + srcRemaining -= characterLength; + } + } + // Update with the amounts used. + *srcLen = *srcLen - srcRemaining; + *dstLen = *dstLen - dstRemaining; + return B_OK; + } + + errorCode = convert_from_utf8 (dstEncoding, src, srcLen, dst, dstLen, state, substitute); + if (errorCode != B_OK) + return errorCode; + + if (dstEncoding != B_JIS_CONVERSION) + return B_OK; + + // B_JIS_CONVERSION (ISO-2022-JP) works by shifting between different + // character subsets. For E-mail headers (and other uses), it needs to be + // switched back to ASCII at the end (otherwise the last character gets + // lost or other weird things happen in the headers). Note that we can't + // just append the escape code since the convert_from_utf8 "state" will be + // wrong. So we append an ASCII letter and throw it away, leaving just the + // escape code. Well, it actually switches to the Roman character set, not + // ASCII, but that should be OK. + + tempDstLen = originalDstLen - *dstLen; + if (tempDstLen < 3) // Not enough space remaining in the output. + return B_OK; // Sort of an error, but we did convert the rest OK. + tempSrcLen = 1; + errorCode = convert_from_utf8 (dstEncoding, "a", &tempSrcLen, + dst + *dstLen, &tempDstLen, state, substitute); + if (errorCode != B_OK) + return errorCode; + *dstLen += tempDstLen - 1 /* don't include the ASCII letter */; + return B_OK; +} + + + +static int handle_non_rfc2047_encoding(char **buffer,size_t *bufferLength,size_t *sourceLength) +{ + char *string = *buffer; + int32 length = *sourceLength; + int32 i; + + // check for 8-bit characters + for (i = 0;i < length;i++) + if (string[i] & 0x80) + break; + if (i == length) + return false; + + // check for groups of 8-bit characters - this code is not very smart; + // it just can detect some sort of single-byte encoded stuff, the rest + // is regarded as UTF-8 + + int32 singletons = 0,doubles = 0; + + for (i = 0;i < length;i++) + { + if (string[i] & 0x80) + { + if ((string[i + 1] & 0x80) == 0) + singletons++; + else doubles++; + i++; + } + } + + if (singletons != 0) // can't be valid UTF-8 anymore, so we assume ISO-Latin-1 + { + int32 state = 0; + // just to be sure + int32 destLength = length * 4 + 1; + int32 destBufferLength = destLength; + char *dest = (char *)malloc(destLength); + if (dest == NULL) + return 0; + + if (convert_to_utf8(B_ISO1_CONVERSION,string,&length,dest,&destLength,&state) == B_OK) + { + free(*buffer); + *buffer = dest; + *bufferLength = destBufferLength; + *sourceLength = destLength; + return true; + } + free(dest); + return false; + } + + // we assume a valid UTF-8 string here, but yes, we don't check it + return true; +} + + +_EXPORT ssize_t rfc2047_to_utf8(char **bufp, size_t *bufLen, size_t strLen) +{ + char *string = *bufp; + char *head, *tail; + char *charset, *encoding, *end; + ssize_t ret = B_OK; + + if (bufp == NULL || *bufp == NULL) + return -1; + + //---------Handle *&&^%*&^ non-RFC compliant, 8bit mail + if (handle_non_rfc2047_encoding(bufp,bufLen,&strLen)) + return strLen; + + // set up string length + if (strLen == 0) + strLen = strlen(*bufp); + char lastChar = (*bufp)[strLen]; + (*bufp)[strLen] = '\0'; + + //---------Whew! Now for RFC compliant mail + bool encodedWordFoundPreviously = false; + for (head = tail = string; + ((charset = strstr(tail, "=?")) != NULL) + && (((encoding = strchr(charset + 2, '?')) != NULL) + && encoding[1] && (encoding[2] == '?') && encoding[3]) + && (end = strstr(encoding + 3, "?=")) != NULL; + // found "=?...charset...?e?...text...?= (e == encoding) + // ^charset ^encoding ^end + tail = end) + { + // Copy non-encoded text (from tail up to charset) to the output. + // Ignore spaces between two encoded "words". RFC2047 says the words + // should be concatenated without the space (designed for Asian + // sentences which have no spaces yet need to be broken into "words" to + // keep within the line length limits). + bool nonSpaceFound = false; + for (int i = 0; i < charset-tail; i++) { + if (!isspace (tail[i])) { + nonSpaceFound = true; + break; + } + } + if (!encodedWordFoundPreviously || nonSpaceFound) { + if (string != tail && tail != charset) + memmove(string, tail, charset-tail); + string += charset-tail; + } + tail = charset; + encodedWordFoundPreviously = true; + + // move things to point at what they should: + // =?...charset...?e?...text...?= (e == encoding) + // ^charset ^encoding ^end + charset += 2; + encoding += 1; + end += 2; + + // find the charset this text is in now + size_t cLen = encoding - 1 - charset; + bool base64encoded = toupper(*encoding) == 'B'; + + int i; + for (i = 0; mail_charsets[i].charset != NULL; i++) + { + if (strncasecmp(charset, mail_charsets[i].charset, cLen) == 0 && + strlen(mail_charsets[i].charset) == cLen) + break; + } + + if (mail_charsets[i].charset == NULL) + { + // unidentified charset + // what to do? doing nothing skips the encoded text; + // but we should keep it: we copy it to the output. + if (string != tail && tail != end) + memmove(string, tail, end-tail); + string += end-tail; + continue; + } + // else we've successfully identified the charset + + char *src = encoding+2; + int32 srcLen = end - 2 - src; + // encoded text: src..src+srcLen + + // decode text, get decoded length (reducing xforms) + srcLen = !base64encoded ? decode_qp(src, src, srcLen, 1) + : decode_base64(src, src, srcLen); + + // allocate space for the converted text + int32 dstLen = end-string + *bufLen-strLen; + char *dst = (char*)malloc(dstLen); + int32 cvLen = srcLen; + int32 convState = 0; + + // + // do the conversion + // + ret = mail_convert_to_utf8(mail_charsets[i].flavor, src, &cvLen, dst, &dstLen, &convState); + if (ret != B_OK) + { + // what to do? doing nothing skips the encoded text + // but we should keep it: we copy it to the output. + + free(dst); + + if (string != tail && tail != end) + memmove(string, tail, end-tail); + string += end-tail; + continue; + } + /* convert_to_ is either returning something wrong or my + test data is screwed up. Whatever it is, Not Enough + Space is not the only cause of the below, so we just + assume it succeeds if it converts anything at all. + else if (cvLen < srcLen) + { + // not enough room to convert the data; + // grow *buf and retry + + free(dst); + + char *temp = (char*)realloc(*bufp, 2*(*bufLen + 1)); + if (temp == NULL) + { + ret = B_NO_MEMORY; + break; + } + + *bufp = temp; + *bufLen = 2*(*bufLen + 1); + + string = *bufp + (string-head); + tail = *bufp + (tail-head); + charset = *bufp + (charset-head); + encoding = *bufp + (encoding-head); + end = *bufp + (end-head); + src = *bufp + (src-head); + head = *bufp; + continue; + } + */ + else + { + if (dstLen > end-string) + { + // copy the string forward... + memmove(string+dstLen, end, strLen - (end-head) + 1); + strLen += string+dstLen - end; + end = string + dstLen; + } + + memcpy(string, dst, dstLen); + string += dstLen; + free(dst); + continue; + } + } + + // copy everything that's left + size_t tailLen = strLen - (tail - head); + memmove(string, tail, tailLen+1); + string += tailLen; + + // replace the last char + (*bufp)[strLen] = lastChar; + + return ret < B_OK ? ret : string-head; +} + + +_EXPORT ssize_t utf8_to_rfc2047 (char **bufp, ssize_t length, uint32 charset, char encoding) { + struct word { + BString originalWord; + BString convertedWord; + bool needsEncoding; + + // Convert the word from UTF-8 to the desired character set. The + // converted version also includes the escape codes to return to ASCII + // mode, if relevant. Also note if it uses unprintable characters, + // which means it will need that special encoding treatment later. + void ConvertWordToCharset (uint32 charset) { + int32 state = 0; + int32 originalLength = originalWord.Length(); + int32 convertedLength = originalLength * 5 + 1; + char *convertedBuffer = convertedWord.LockBuffer (convertedLength); + mail_convert_from_utf8 (charset, originalWord.String(), + &originalLength, convertedBuffer, &convertedLength, &state); + for (int i = 0; i < convertedLength; i++) { + if ((convertedBuffer[i] & (1 << 7)) || + (convertedBuffer[i] >= 0 && convertedBuffer[i] < 32)) { + needsEncoding = true; + break; + } + } + convertedWord.UnlockBuffer (convertedLength); + }; + }; + struct word *currentWord; + BList words; + + // Break the header into words. White space characters (including tabs and + // newlines) separate the words. Each word includes any space before it as + // part of the word. Actually, quotes and other special characters + // (",()<>@) are treated as separate words of their own so that they don't + // get encoded (because MIME headers get the quotes parsed before character + // set unconversion is done). The reader is supposed to ignore all white + // space between encoded words, which can be inserted so that older mail + // parsers don't have overly long line length problems. + + const char *source = *bufp; + const char *bufEnd = *bufp + length; + const char *specialChars = "\"()<>@,"; + + while (source < bufEnd) { + currentWord = new struct word; + currentWord->needsEncoding = false; + + int wordEnd = 0; + + // Include leading spaces as part of the word. + while (source + wordEnd < bufEnd && isspace (source[wordEnd])) + wordEnd++; + + if (source + wordEnd < bufEnd && + strchr (specialChars, source[wordEnd]) != NULL) { + // Got a quote mark or other special character, which is treated as + // a word in itself since it shouldn't be encoded, which would hide + // it from the mail system. + wordEnd++; + } else { + // Find the end of the word. Leave wordEnd pointing just after the + // last character in the word. + while (source + wordEnd < bufEnd) { + if (isspace(source[wordEnd]) || + strchr (specialChars, source[wordEnd]) != NULL) + break; + if (wordEnd > 51 /* Makes Base64 ISO-2022-JP "word" a multiple of 4 bytes */ && + 0xC0 == (0xC0 & (unsigned int) source[wordEnd])) { + // No English words are that long (46 is the longest), + // break up what is likely Asian text (which has no spaces) + // at the start of the next non-ASCII UTF-8 character (high + // two bits are both ones). Note that two encoded words in + // a row get joined together, even if there is a space + // between them in the final output text, according to the + // standard. Next word will also be conveniently get + // encoded due to the 0xC0 test. + currentWord->needsEncoding = true; + break; + } + wordEnd++; + } + } + currentWord->originalWord.SetTo (source, wordEnd); + currentWord->ConvertWordToCharset (charset); + words.AddItem(currentWord); + source += wordEnd; + } + + // Combine adjacent words which contain unprintable text so that the + // overhead of switching back and forth between regular text and specially + // encoded text is reduced. However, the combined word must be shorter + // than the maximum of 75 bytes, including character set specification and + // all those delimiters (worst case 22 bytes of overhead). + + struct word *run; + + for (int32 i = 0; (currentWord = (struct word *) words.ItemAt (i)) != NULL; i++) { + if (!currentWord->needsEncoding) + continue; // No need to combine unencoded words. + for (int32 g = i+1; (run = (struct word *) words.ItemAt (g)) != NULL; g++) { + if (!run->needsEncoding) + break; // Don't want to combine encoded and unencoded words. + if ((currentWord->convertedWord.Length() + run->convertedWord.Length() <= 53)) { + currentWord->originalWord.Append (run->originalWord); + currentWord->ConvertWordToCharset (charset); + words.RemoveItem(g); + delete run; + g--; + } else // Can't merge this word, result would be too long. + break; + } + } + + // Combine the encoded and unencoded words into one line, doing the + // quoted-printable or base64 encoding. Insert an extra space between + // words which are both encoded to make word wrapping easier, since there + // is normally none, and you're allowed to insert space (the receiver + // throws it away if it is between encoded words). + + BString rfc2047; + bool previousWordNeededEncoding = false; + + const char *charset_dec = "none-bug"; + for (int32 i = 0; mail_charsets[i].charset != NULL; i++) { + if (mail_charsets[i].flavor == charset) { + charset_dec = mail_charsets[i].charset; + break; + } + } + + while ((currentWord = (struct word *)words.RemoveItem(0L)) != NULL) { + if ((encoding != quoted_printable && encoding != base64) || + !currentWord->needsEncoding) { + rfc2047.Append (currentWord->convertedWord); + } else { + // This word needs encoding. Try to insert a space between it and + // the previous word. + if (previousWordNeededEncoding) + rfc2047 << ' '; // Can insert as many spaces as you want between encoded words. + else { + // Previous word is not encoded, spaces are significant. Try + // to move a space from the start of this word to be outside of + // the encoded text, so that there is a bit of space between + // this word and the previous one to enhance word wrapping + // chances later on. + if (currentWord->originalWord.Length() > 1 && + isspace (currentWord->originalWord[0])) { + rfc2047 << currentWord->originalWord[0]; + currentWord->originalWord.Remove (0 /* offset */, 1 /* length */); + currentWord->ConvertWordToCharset (charset); + } + } + + char *encoded = NULL; + ssize_t encoded_len = 0; + int32 convertedLength = currentWord->convertedWord.Length (); + const char *convertedBuffer = currentWord->convertedWord.String (); + + switch (encoding) { + case quoted_printable: + encoded = (char *) malloc (convertedLength * 3); + encoded_len = encode_qp (encoded, convertedBuffer, convertedLength, true /* headerMode */); + break; + case base64: + encoded = (char *) malloc (convertedLength * 2); + encoded_len = encode_base64 (encoded, convertedBuffer, convertedLength, true /* headerMode */); + break; + default: // Unknown encoding type, shouldn't happen. + encoded = (char *) convertedBuffer; + encoded_len = convertedLength; + break; + } + + rfc2047 << "=?" << charset_dec << '?' << encoding << '?'; + rfc2047.Append (encoded, encoded_len); + rfc2047 << "?="; + + if (encoding == quoted_printable || encoding == base64) + free(encoded); + } + previousWordNeededEncoding = currentWord->needsEncoding; + delete currentWord; + } + + free(*bufp); + + ssize_t finalLength = rfc2047.Length (); + *bufp = (char *) (malloc (finalLength + 1)); + memcpy (*bufp, rfc2047.String(), finalLength); + (*bufp)[finalLength] = 0; + + return finalLength; +} + + +//==================================================================== + +void FoldLineAtWhiteSpaceAndAddCRLF (BString &string) +{ + int inputLength = string.Length(); + int lineStartIndex; + const int maxLineLength = 78; // Doesn't include CRLF. + BString output; + int splitIndex; + int tempIndex; + + lineStartIndex = 0; + while (true) { + // If we don't need to wrap the text, just output the remainder, if any. + + if (lineStartIndex + maxLineLength >= inputLength) { + if (lineStartIndex < inputLength) { + output.Insert (string, lineStartIndex /* source offset */, + inputLength - lineStartIndex /* count */, + output.Length() /* insert at */); + output.Append (CRLF); + } + break; + } + + // Look ahead for a convenient spot to split it, between a comma and + // space, which you often see between e-mail addresses like this: + // "Joe Who" joe@dot.com, "Someone Else" else@blot.com + + tempIndex = lineStartIndex + maxLineLength; + if (tempIndex > inputLength) + tempIndex = inputLength; + splitIndex = string.FindLast (", ", tempIndex); + if (splitIndex >= lineStartIndex) + splitIndex++; // Point to the space character. + + // If none of those exist, try splitting at any white space. + + if (splitIndex <= lineStartIndex) + splitIndex = string.FindLast (" ", tempIndex); + if (splitIndex <= lineStartIndex) + splitIndex = string.FindLast ("\t", tempIndex); + + // If none of those exist, allow for a longer word - split at the next + // available white space. + + if (splitIndex <= lineStartIndex) + splitIndex = string.FindFirst (" ", lineStartIndex + 1); + if (splitIndex <= lineStartIndex) + splitIndex = string.FindFirst ("\t", lineStartIndex + 1); + + // Give up, the whole rest of the line can't be split, just dump it + // out. + + if (splitIndex <= lineStartIndex) { + if (lineStartIndex < inputLength) { + output.Insert (string, lineStartIndex /* source offset */, + inputLength - lineStartIndex /* count */, + output.Length() /* insert at */); + output.Append (CRLF); + } + break; + } + + // Do the split. The current line up to but not including the space + // gets output, followed by a CRLF. The space remains to become the + // start of the next line (and that tells the message reader that it is + // a continuation line). + + output.Insert (string, lineStartIndex /* source offset */, + splitIndex - lineStartIndex /* count */, + output.Length() /* insert at */); + output.Append (CRLF); + lineStartIndex = splitIndex; + } + string.SetTo (output); +} + + +//==================================================================== + +_EXPORT ssize_t readfoldedline(FILE *file, char **buffer, size_t *buflen) +{ + ssize_t len = buflen && *buflen ? *buflen : 0; + char * buf = buffer && *buffer ? *buffer : NULL; + ssize_t cnt = 0; // Number of characters currently in the buffer. + int c; + + while (true) + { + // Make sure there is space in the buffer for two more characters (one + // for the next character, and one for the end of string NUL byte). + if (buf == NULL || cnt + 2 >= len) + { + char *temp = (char *)realloc(buf, len + 64); + if (temp == NULL) { + // Out of memory, however existing buffer remains allocated. + cnt = ENOMEM; + break; + } + len += 64; + buf = temp; + } + + // Read the next character, or end of file, or IO error. + if ((c = fgetc(file)) == EOF) { + if (ferror (file)) { + cnt = errno; + if (cnt >= 0) + cnt = -1; // Error codes must be negative. + } else { + // Really is end of file. Also make it end of line if there is + // some text already read in. If the first thing read was EOF, + // just return an empty string. + if (cnt > 0) { + buf[cnt++] = '\n'; + if (buf[cnt-2] == '\r') { + buf[cnt-2] = '\n'; + --cnt; + } + } + } + break; + } + + buf[cnt++] = c; + + if (c == '\n') { + // Convert CRLF end of line to just a LF. Do it before folding, in + // case we don't need to fold. + if (cnt >= 2 && buf[cnt-2] == '\r') { + buf[cnt-2] = '\n'; + --cnt; + } + // If the current line is empty then return it (so that empty lines + // don't disappear if the next line starts with a space). + if (cnt <= 1) + break; + // Fold if first character on the next line is whitespace. + c = fgetc(file); // Note it's OK to read EOF and ungetc it too. + if (c == ' ' || c == '\t') + buf[cnt-1] = c; // Replace \n with the white space character. + else { + // Not folding, we finished reading a line; break out of the loop + ungetc(c,file); + break; + } + } + } + + + if (buf != NULL && cnt >= 0) + buf[cnt] = '\0'; + + if (buffer) + *buffer = buf; + else if (buf) + free(buf); + + if (buflen) + *buflen = len; + + return cnt; +} + + +//==================================================================== + +_EXPORT ssize_t readfoldedline(BPositionIO &in, char **buffer, size_t *buflen) +{ + ssize_t len = buflen && *buflen ? *buflen : 0; + char * buf = buffer && *buffer ? *buffer : NULL; + ssize_t cnt = 0; // Number of characters currently in the buffer. + char c; + status_t errorCode; + + while (true) + { + // Make sure there is space in the buffer for two more characters (one + // for the next character, and one for the end of string NUL byte). + if (buf == NULL || cnt + 2 >= len) + { + char *temp = (char *)realloc(buf, len + 64); + if (temp == NULL) { + // Out of memory, however existing buffer remains allocated. + cnt = ENOMEM; + break; + } + len += 64; + buf = temp; + } + + errorCode = in.Read (&c,1); // A really slow way of reading - unbuffered. + if (errorCode != 1) { + if (errorCode < 0) { + cnt = errorCode; // IO error encountered, just return the code. + } else { + // Really is end of file. Also make it end of line if there is + // some text already read in. If the first thing read was EOF, + // just return an empty string. + if (cnt > 0) { + buf[cnt++] = '\n'; + if (buf[cnt-2] == '\r') { + buf[cnt-2] = '\n'; + --cnt; + } + } + } + break; + } + + buf[cnt++] = c; + + if (c == '\n') { + // Convert CRLF end of line to just a LF. Do it before folding, in + // case we don't need to fold. + if (cnt >= 2 && buf[cnt-2] == '\r') { + buf[cnt-2] = '\n'; + --cnt; + } + // If the current line is empty then return it (so that empty lines + // don't disappear if the next line starts with a space). + if (cnt <= 1) + break; + // if first character on the next line is whitespace, fold lines + errorCode = in.Read(&c,1); + if (errorCode == 1) { + if (c == ' ' || c == '\t') + buf[cnt-1] = c; // Replace \n with the white space character. + else { + // Not folding, we finished reading a whole line. + in.Seek(-1,SEEK_CUR); // Undo the look-ahead character read. + break; + } + } else if (errorCode < 0) { + cnt = errorCode; + break; + } else // No next line; at the end of the file. Return the line. + break; + } + } + + if (buf != NULL && cnt >= 0) + buf[cnt] = '\0'; + + if (buffer) + *buffer = buf; + else if (buf) + free(buf); + + if (buflen) + *buflen = len; + + return cnt; +} + + +_EXPORT ssize_t +nextfoldedline(const char** header, char **buffer, size_t *buflen) +{ + ssize_t len = buflen && *buflen ? *buflen : 0; + char * buf = buffer && *buffer ? *buffer : NULL; + ssize_t cnt = 0; // Number of characters currently in the buffer. + char c; + + while (true) + { + // Make sure there is space in the buffer for two more characters (one + // for the next character, and one for the end of string NUL byte). + if (buf == NULL || cnt + 2 >= len) + { + char *temp = (char *)realloc(buf, len + 64); + if (temp == NULL) { + // Out of memory, however existing buffer remains allocated. + cnt = ENOMEM; + break; + } + len += 64; + buf = temp; + } + + // Read the next character, or end of file. + if ((c = *(*header)++) == 0) { + // End of file. Also make it end of line if there is some text + // already read in. If the first thing read was EOF, just return + // an empty string. + if (cnt > 0) { + buf[cnt++] = '\n'; + if (buf[cnt-2] == '\r') { + buf[cnt-2] = '\n'; + --cnt; + } + } + break; + } + + buf[cnt++] = c; + + if (c == '\n') { + // Convert CRLF end of line to just a LF. Do it before folding, in + // case we don't need to fold. + if (cnt >= 2 && buf[cnt-2] == '\r') { + buf[cnt-2] = '\n'; + --cnt; + } + // If the current line is empty then return it (so that empty lines + // don't disappear if the next line starts with a space). + if (cnt <= 1) + break; + // if first character on the next line is whitespace, fold lines + c = *(*header)++; + if (c == ' ' || c == '\t') + buf[cnt-1] = c; // Replace \n with the white space character. + else { + // Not folding, we finished reading a line; break out of the loop + (*header)--; // Undo read of the non-whitespace. + break; + } + } + } + + + if (buf != NULL && cnt >= 0) + buf[cnt] = '\0'; + + if (buffer) + *buffer = buf; + else if (buf) + free(buf); + + if (buflen) + *buflen = len; + + return cnt; +} + + +_EXPORT void +trim_white_space(BString &string) +{ + int32 i; + int32 length = string.Length(); + char *buffer = string.LockBuffer(length + 1); + + while (length > 0 && isspace(buffer[length - 1])) + length--; + buffer[length] = '\0'; + + for (i = 0; buffer[i] && isspace(buffer[i]); i++) {} + if (i != 0) { + length -= i; + memmove(buffer,buffer + i,length + 1); + } + string.UnlockBuffer(length); +} + + +/** Tries to return a human-readable name from the specified + * header parameter (should be from "To:" or "From:"). + * Tries to return the name rather than the eMail address. + */ + +_EXPORT void +extract_address_name(BString &header) +{ + BString name; + const char *start = header.String(); + const char *stop = start + strlen (start); + + // Find a string S in the header (email foo) that matches: + // Old style name in brackets: foo@bar.com (S) + // New style quotes: "S" + // New style no quotes if nothing else found: S + // If nothing else found then use the whole thing: S + + for (int i = 0; i <= 3; i++) { + // Set p1 to the first letter in the name and p2 to just past the last + // letter in the name. p2 stays NULL if a name wasn't found in this + // pass. + const char *p1 = NULL, *p2 = NULL; + + switch (i) { + case 0: // foo@bar.com (S) + if ((p1 = strchr(start,'(')) != NULL) { + p1++; // Advance to first letter in the name. + size_t nest = 1; // Handle nested brackets. + for (p2 = p1; p2 < stop; ++p2) + { + if (*p2 == ')') + --nest; + else if (*p2 == '(') + ++nest; + if (nest <= 0) + break; + } + if (nest != 0) + p2 = NULL; // False alarm, no terminating bracket. + } + break; + case 1: // "S" + if ((p1 = strchr(start, '\"')) != NULL) + p2 = strchr(++p1, '\"'); + break; + case 2: // S + p1 = start; + if (name.Length() == 0) + p2 = strchr(start, '<'); + break; + case 3: // S + p1 = start; + if (name.Length() == 0) + p2 = stop; + break; + } + + // Remove leading and trailing space-like characters and save the + // result if it is longer than any other likely names found. + if (p2 != NULL) { + while (p1 < p2 && (isspace (*p1))) + ++p1; + + while (p1 < p2 && (isspace (p2[-1]))) + --p2; + + int newLength = p2 - p1; + if (name.Length() < newLength) + name.SetTo(p1, newLength); + } + } + + int32 lessIndex = name.FindFirst('<'); + int32 greaterIndex = name.FindLast('>'); + + if (lessIndex == 0) { + // Have an address of the form

and nothing else, so remove + // the greater and less than signs, if any. + if (greaterIndex > 0) + name.Remove(greaterIndex, 1); + name.Remove(lessIndex, 1); + } else if (lessIndex > 0 && lessIndex < greaterIndex) { + // Yahoo stupidly inserts the e-mail address into the name string, so + // this bit of code fixes: "Joe " + name.Remove(lessIndex, greaterIndex - lessIndex + 1); + } + + trim_white_space(name); + header = name; +} + + + +// Given a subject in a BString, remove the extraneous RE: re: and other stuff +// to get down to the core subject string, which should be identical for all +// messages posted about a topic. The input string is modified in place to +// become the output core subject string. + +static int32 gLocker = 0; +static size_t gNsub = 1; +static re_pattern_buffer gRe; +static re_pattern_buffer *gRebuf = NULL; +static char gTranslation[256]; + +_EXPORT void SubjectToThread (BString &string) +{ +// a regex that matches a non-ASCII UTF8 character: +#define U8C \ + "[\302-\337][\200-\277]" \ + "|\340[\302-\337][\200-\277]" \ + "|[\341-\357][\200-\277][\200-\277]" \ + "|\360[\220-\277][\200-\277][\200-\277]" \ + "|[\361-\367][\200-\277][\200-\277][\200-\277]" \ + "|\370[\210-\277][\200-\277][\200-\277][\200-\277]" \ + "|[\371-\373][\200-\277][\200-\277][\200-\277][\200-\277]" \ + "|\374[\204-\277][\200-\277][\200-\277][\200-\277][\200-\277]" \ + "|\375[\200-\277][\200-\277][\200-\277][\200-\277][\200-\277]" + +#define PATTERN \ + "^ +" \ + "|^(\\[[^]]*\\])(\\<| +| *(\\<(\\w|" U8C "){2,3} *(\\[[^\\]]*\\])? *:)+ *)" \ + "|^( +| *(\\<(\\w|" U8C "){2,3} *(\\[[^\\]]*\\])? *:)+ *)" \ + "| *\\(fwd\\) *$" + + if (gRebuf == NULL && atomic_add(&gLocker,1) == 0) + { + // the idea is to compile the regexp once to speed up testing + + for (int i=0; i<256; ++i) gTranslation[i]=i; + for (int i='a'; i<='z'; ++i) gTranslation[i]=toupper(i); + + gRe.translate = gTranslation; + gRe.regs_allocated = REGS_FIXED; + re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; + + const char *pattern = PATTERN; + // count subexpressions in PATTERN + for (unsigned int i=0; pattern[i] != 0; ++i) + { + if (pattern[i] == '\\') + ++i; + else if (pattern[i] == '(') + ++gNsub; + } + + const char *err = re_compile_pattern(pattern,strlen(pattern),&gRe); + if (err == NULL) + gRebuf = &gRe; + else + fprintf(stderr, "Failed to compile the regex: %s\n", err); + } + else + { + int32 tries = 200; + while (gRebuf == NULL && tries-- > 0) + snooze(10000); + } + + if (gRebuf) + { + struct re_registers regs; + // can't be static if this function is to be thread-safe + + regs.num_regs = gNsub; + regs.start = (regoff_t*)malloc(gNsub*sizeof(regoff_t)); + regs.end = (regoff_t*)malloc(gNsub*sizeof(regoff_t)); + + for (int start=0; + (start=re_search(gRebuf, string.String(), string.Length(), + 0, string.Length(), ®s)) >= 0; + ) + { + // + // we found something + // + + // don't delete [bemaildaemon]... + if (start == regs.start[1]) + start = regs.start[2]; + + string.Remove(start,regs.end[0]-start); + if (start) string.Insert(' ',1,start); + } + + free(regs.start); + free(regs.end); + } + + // Finally remove leading and trailing space. Some software, like + // tm-edit 1.8, appends a space to the subject, which would break + // threading if we left it in. + trim_white_space(string); +} + + + +// Converts a date to a time. Handles numeric time zones too, unlike +// parsedate. Returns -1 if it fails. + +_EXPORT time_t ParseDateWithTimeZone (const char *DateString) +{ + time_t currentTime; + time_t dateAsTime; + char tempDateString [80]; + char tempZoneString [6]; + time_t zoneDeltaTime; + int zoneIndex; + char *zonePntr; + + // See if we can remove the time zone portion. parsedate understands time + // zone 3 letter names, but doesn't understand the numeric +9999 time zone + // format. To do: see if a newer parsedate exists. + + strncpy (tempDateString, DateString, sizeof (tempDateString)); + tempDateString[sizeof (tempDateString) - 1] = 0; + + // Remove trailing spaces. + zonePntr = tempDateString + strlen (tempDateString) - 1; + while (zonePntr >= tempDateString && isspace (*zonePntr)) + *zonePntr-- = 0; + if (zonePntr < tempDateString) + return -1; // Empty string. + + // Remove the trailing time zone in round brackets, like in + // Fri, 22 Feb 2002 15:22:42 EST (-0500) + // Thu, 25 Apr 1996 11:44:19 -0400 (EDT) + if (tempDateString[strlen(tempDateString)-1] == ')') + { + zonePntr = strrchr (tempDateString, '('); + if (zonePntr != NULL) + { + *zonePntr-- = 0; // Zap the '(', then remove trailing spaces. + while (zonePntr >= tempDateString && isspace (*zonePntr)) + *zonePntr-- = 0; + if (zonePntr < tempDateString) + return -1; // Empty string. + } + } + + // Look for a numeric time zone like Tue, 30 Dec 2003 05:01:40 +0000 + for (zoneIndex = strlen (tempDateString); zoneIndex >= 0; zoneIndex--) + { + zonePntr = tempDateString + zoneIndex; + if (zonePntr[0] == '+' || zonePntr[0] == '-') + { + if (zonePntr[1] >= '0' && zonePntr[1] <= '9' && + zonePntr[2] >= '0' && zonePntr[2] <= '9' && + zonePntr[3] >= '0' && zonePntr[3] <= '9' && + zonePntr[4] >= '0' && zonePntr[4] <= '9') + break; + } + } + if (zoneIndex >= 0) + { + // Remove the zone from the date string and any following time zone + // letter codes. Also put in GMT so that the date gets parsed as GMT. + memcpy (tempZoneString, zonePntr, 5); + tempZoneString [5] = 0; + strcpy (zonePntr, "GMT"); + } + else // No numeric time zone found. + strcpy (tempZoneString, "+0000"); + + time (¤tTime); + dateAsTime = parsedate (tempDateString, currentTime); + if (dateAsTime == (time_t) -1) + return -1; // Failure. + + zoneDeltaTime = 60 * atol (tempZoneString + 3); // Get the last two digits - minutes. + tempZoneString[3] = 0; + zoneDeltaTime += atol (tempZoneString + 1) * 60 * 60; // Get the first two digits - hours. + if (tempZoneString[0] == '+') + zoneDeltaTime = 0 - zoneDeltaTime; + dateAsTime += zoneDeltaTime; + + return dateAsTime; +} + + +/** Parses a mail header and fills the headers BMessage + */ + +_EXPORT status_t +parse_header(BMessage &headers, BPositionIO &input) +{ + char *buffer = NULL; + size_t bufferSize = 0; + int32 length; + + while ((length = readfoldedline(input, &buffer, &bufferSize)) >= 2) { + --length; + // Don't include the \n at the end of the buffer. + + // convert to UTF-8 and null-terminate the buffer + length = rfc2047_to_utf8(&buffer, &bufferSize, length); + buffer[length] = '\0'; + + const char *delimiter = strstr(buffer, ":"); + if (delimiter == NULL) + continue; + + BString header(buffer, delimiter - buffer); + header.CapitalizeEachWord(); + // unified case for later fetch + + delimiter++; // Skip the colon. + while (isspace (*delimiter)) + delimiter++; // Skip over leading white space and tabs. To do: (comments in brackets). + + // ToDo: implement joining of multiple header tags (i.e. multiple "Cc:"s) + headers.AddString(header.String(), delimiter); + } + free(buffer); + + return B_OK; +} + + +_EXPORT void +extract_address(BString &address) +{ + const char *string = address.String(); + int32 first; + + // first, remove all quoted text + + if ((first = address.FindFirst('"')) >= 0) { + int32 last = first + 1; + while (string[last] && string[last] != '"') + last++; + + if (string[last] == '"') + address.Remove(first, last + 1 - first); + } + + // try to extract the address now + + if ((first = address.FindFirst('<')) >= 0) { + // the world likes us and we can just get the address the easy way... + int32 last = address.FindFirst('>'); + if (last >= 0) { + address.Truncate(last); + address.Remove(0, first + 1); + + return; + } + } + + // then, see if there is anything in parenthesis to throw away + + if ((first = address.FindFirst('(')) >= 0) { + int32 last = first + 1; + while (string[last] && string[last] != ')') + last++; + + if (string[last] == ')') + address.Remove(first, last + 1 - first); + } + + // now, there shouldn't be much else left + + trim_white_space(address); +} + + +_EXPORT void +get_address_list(BList &list, const char *string, void (*cleanupFunc)(BString &)) +{ + if (string == NULL || !string[0]) + return; + + const char *start = string; + + while (true) { + if (string[0] == '"') { + const char *quoteEnd = ++string; + + while (quoteEnd[0] && quoteEnd[0] != '"') + quoteEnd++; + + if (!quoteEnd[0]) // string exceeds line! + quoteEnd = string; + + string = quoteEnd + 1; + } + + if (string[0] == ',' || string[0] == '\0') { + BString address(start, string - start); + trim_white_space(address); + + if (cleanupFunc) + cleanupFunc(address); + + list.AddItem(strdup(address.String())); + + start = string + 1; + } + + if (!string[0]) + break; + + string++; + } +} + diff --git a/src/kits/mail/makefile b/src/kits/mail/makefile new file mode 100755 index 0000000000..95623b5907 --- /dev/null +++ b/src/kits/mail/makefile @@ -0,0 +1,176 @@ +## BeOS Generic Makefile v2.2 ## + +## Fill in this file to specify the project being created, and the referenced +## makefile-engine will do all of the hard work for you. This handles both +## Intel and PowerPC builds of the BeOS. + +## Application Specific Settings --------------------------------------------- + +# specify the name of the binary +NAME= libmail.so + +# specify the type of binary +# APP: Application +# SHARED: Shared library or add-on +# STATIC: Static library archive +# DRIVER: Kernel Driver +TYPE= SHARED + +# add support for new Pe and Eddie features +# to fill in generic makefile + +#%{ +# @src->@ + +# specify the source files to use +# full paths or paths relative to the makefile can be included +# all files, regardless of directory, will have their object +# files created in the common object directory. +# Note that this means this makefile will not work correctly +# if two source files with the same name (source.c or source.cpp) +# are included from different directories. Also note that spaces +# in folder names do not work well with this makefile. +SRCS= MailAddon.cpp numailkit.cpp MailProtocol.cpp \ + MailChain.cpp MailSettings.cpp StringList.cpp status.cp \ + ChainRunner.cpp NodeMessage.cpp MailDaemon.cpp c_mail_api.cpp \ + des.c crypt.cpp ProtocolConfigView.cpp mail_util.cpp MailComponent.cpp \ + MailContainer.cpp MailAttachment.cpp MailMessage.cpp b_mail_message.cpp \ + cpp_abi_base64.c FileConfigView.cpp mail_encoding.c RemoteStorageProtocol.cpp \ + ErrorLogWindow.cpp + +# specify the resource files to use +# full path or a relative path to the resource file can be used. +RSRCS= + +# @<-src@ +#%} + +# end support for Pe and Eddie + +# specify additional libraries to link against +# there are two acceptable forms of library specifications +# - if your library follows the naming pattern of: +# libXXX.so or libXXX.a you can simply specify XXX +# library: libbe.so entry: be +# +# - if your library does not follow the standard library +# naming scheme you need to specify the path to the library +# and it's name +# library: my_lib.a entry: my_lib.a or path/my_lib.a +LIBS= be textencoding tracker stdc++.r4 + +# specify additional paths to directories following the standard +# libXXX.so or libXXX.a naming scheme. You can specify full paths +# or paths relative to the makefile. The paths included may not +# be recursive, so include all of the paths where libraries can +# be found. Directories where source files are found are +# automatically included. +LIBPATHS= + +# additional paths to look for system headers +# thes use the form: #include
+# source file directories are NOT auto-included here +SYSTEM_INCLUDE_PATHS = ../include ../include/numail ../include/support ../include/public + +# additional paths to look for local headers +# thes use the form: #include "header" +# source file directories are automatically included +LOCAL_INCLUDE_PATHS = + +# specify the level of optimization that you desire +# NONE, SOME, FULL +OPTIMIZE= SOME + +# specify any preprocessor symbols to be defined. The symbols will not +# have their values set automatically; you must supply the value (if any) +# to use. For example, setting DEFINES to "DEBUG=1" will cause the +# compiler option "-DDEBUG=1" to be used. Setting DEFINES to "DEBUG" +# would pass "-DDEBUG" on the compiler's command line. +DEFINES= _BUILDING_mail=1 USE_NASTY_SYNC_THREAD_HACK=1 + +# specify special warning levels +# if unspecified default warnings will be used +# NONE = supress all warnings +# ALL = enable all warnings +WARNINGS = ALL + +# specify whether image symbols will be created +# so that stack crawls in the debugger are meaningful +# if TRUE symbols will be created +SYMBOLS = TRUE + +# specify debug settings +# if TRUE will allow application to be run from a source-level +# debugger. Note that this will disable all optimzation. +DEBUGGER = + +# specify additional compiler flags for all files +COMPILER_FLAGS = + +# specify additional linker flags +LINKER_FLAGS = + +# specify the version of this particular item +# (for example, -app 3 4 0 d 0 -short 340 -long "340 "`echo -n -e '\302\251'`"1999 GNU GPL") +# This may also be specified in a resource. +APP_VERSION = + +# (for TYPE == DRIVER only) Specify desired location of driver in the /dev +# hierarchy. Used by the driverinstall rule. E.g., DRIVER_PATH = video/usb will +# instruct the driverinstall rule to place a symlink to your driver's binary in +# ~/add-ons/kernel/drivers/dev/video/usb, so that your driver will appear at +# /dev/video/usb when loaded. Default is "misc". +DRIVER_PATH = + +# ppc libroot lacks regex +MACHINE=$(shell uname -m) +ifneq ($(MACHINE),BePC) + SRCS += regex.c + DEFINES += STDC_HEADERS=1 + COMPILER_FLAGS += -w iserr +else + COMPILER_FLAGS += -Werror +endif + +ifeq ($(CHECK_MEM), true) + COMPILER_FLAGS += -fcheck-memory-usage -D_NO_INLINE_ASM=1 -D_KERNEL_MODE=1 + DEBUG_BUILD=true +endif + +# Custom overrides that can be set from the command line. +ifeq ($(DEBUG_BUILD), true) + SYMBOLS := TRUE + DEBUGGER := TRUE + OPTIMIZE := NONE + COMPILER_FLAGS += -DDEBUG=1 + + ifeq ($(shell uname -r), 5.1) + COMPILER_FLAGS += -fno-debug-opt + endif + +endif + +TARGET_DIR=. +INSTALL_DIR=/boot/beos/system/lib + +# Detect BONE +ifeq ($(shell ls 2>/dev/null -1 /boot/develop/headers/be/bone/bone_api.h), /boot/develop/headers/be/bone/bone_api.h) + SYSTEM_INCLUDE_PATHS += /boot/develop/headers/be/bone + LIBS += socket + DEFINES += BONE + + # And now detect Zeta + ifeq ($(shell ls 2>/dev/null -1 /boot/beos/system/lib/libzeta.so), /boot/beos/system/lib/libzeta.so) + LIBS += zeta + DEFINES += _ZETA_USING_DEPRECATED_API_ + endif +else + LIBS += net +endif + +## include the makefile-engine +include $(BUILDHOME)/etc/makefile-engine.MailD + +clean :: rmapp + +# rm -rf $(TARGET_DIR)/$(NAME) diff --git a/src/kits/mail/numailkit.cpp b/src/kits/mail/numailkit.cpp new file mode 100644 index 0000000000..8c21526fcb --- /dev/null +++ b/src/kits/mail/numailkit.cpp @@ -0,0 +1,183 @@ +/* Numail Kit - general header for using the kit +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define timeout 5e5 + +namespace MailInternal { + +status_t WriteMessageFile(const BMessage& archive, const BPath& path, const char* name); + +} + + +status_t MailInternal::WriteMessageFile(const BMessage& archive, const BPath& path, const char* name) +{ + status_t ret = B_OK; + BString leaf = name; + leaf << ".tmp"; + + BEntry settings_entry; + BFile tmpfile; + bigtime_t now = system_time(); + + create_directory(path.Path(), 0777); + { + BDirectory account_dir(path.Path()); + ret = account_dir.InitCheck(); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't open '%s': %s\n", + path.Path(), strerror(ret)); + return ret; + } + + // get an entry for the tempfile + // Get it here so that failure doesn't create any problems + ret = settings_entry.SetTo(&account_dir,leaf.String()); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't create an entry for '%s/%s': %s\n", + path.Path(), leaf.String(), strerror(ret)); + return ret; + } + } + + // + // Save to a temporary file + // + + // Our goal is to write to a tempfile and then use 'rename' to + // link that file into place once it contains valid contents. + // Given the filesystem's guarantee of atomic "rename" oper- + // ations this will guarantee that any non-temp files in the + // config directory are valid configuration files. + // + // Ideally, we would be able to do the following: + // BFile tmpfile(&account_dir, "tmpfile", B_WRITE_ONLY|B_CREATE_FILE); + // // ... + // tmpfile.Relink(&account_dir,"realfile"); + // But this doesn't work because there is no way in the API + // to link based on file descriptor. (There should be, for + // exactly this reason, and I see no reason that it can not + // be added to the API, but as it is not present now so we'll + // have to deal.) It has to be file-descriptor based because + // (a) all a BFile knows is its node/FD and (b) the file may + // be unlinked at any time, at which point renaming the entry + // to clobber the "realfile" will result in an invalid con- + // figuration file being created. + // + // We can't count on not clobbering the tempfile to gain + // exclusivity because, if the system crashes between when + // we create the tempfile an when we rename it, we will have + // a zombie tempfile that will prevent us from doing any more + // saves. + // + // What we can do is: + // + // Create or open the tempfile + // // At this point no one will *clobber* the file, but + // // others may open it + // Lock the tempfile + // // At this point, no one else may open it and we have + // // exclusive access to it. Because of the above, we + // // know that our entry is still valid + // + // Truncate the tempfile + // Write settings + // Sync + // Rename to the realfile + // // this does not affect the lock, but now open- + // // ing the realfile will fail with B_BUSY + // Unlock + // + // If this code is the only code that changes these files, + // then we are guaranteed that all realfiles will be valid + // settings files. I think that's the best we can do unless + // we get the Relink() api. An implementation of the above + // follows. + // + + // Create or open + ret = B_TIMED_OUT; + while (system_time() - now < timeout) //-ATT-no timeout arg. Setting by #define + { + ret = tmpfile.SetTo(&settings_entry, B_WRITE_ONLY | B_CREATE_FILE); + if (ret != B_BUSY) break; + + // wait 1/100th second + snooze((bigtime_t)1e4); + } + if (ret != B_OK) + { + fprintf(stderr, "Couldn't open '%s/%s' within the timeout period (%fs): %s\n", + path.Path(), leaf.String(), (float)timeout/1e6, strerror(ret)); + return ret==B_BUSY? B_TIMED_OUT:ret; + } + + // lock + ret = B_TIMED_OUT; + while (system_time() - now < timeout) + { + ret = tmpfile.Lock(); //-ATT-changed account_file to tmpfile. Is that allowed? + if (ret != B_BUSY) break; + + // wait 1/100th second + snooze((bigtime_t)1e4); + } + if (ret != B_OK) + { + fprintf(stderr, "Couldn't lock '%s/%s' in within the timeout period (%fs): %s\n", + path.Path(), leaf.String(), (float)timeout/1e6, strerror(ret)); + // Can't remove it here, since it might be someone else's. + // Leaving a zombie shouldn't cause any problems tho so + // that's OK. + return ret==B_BUSY? B_TIMED_OUT:ret; + } + + // truncate + tmpfile.SetSize(0); + + // write + ret = archive.Flatten(&tmpfile); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't flatten settings to '%s/%s': %s\n", + path.Path(), leaf.String(), strerror(ret)); + return ret; + } + + // ensure it's actually writen + ret = tmpfile.Sync(); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't sync settings to '%s/%s': %s\n", + path.Path(), leaf.String(), strerror(ret)); + return ret; + } + + // clobber old settings + ret = settings_entry.Rename(name,true); + if (ret != B_OK) + { + fprintf(stderr, "Couldn't clobber old settings '%s/%s': %s\n", + path.Path(), name, strerror(ret)); + return ret; + } + + return B_OK; +} + diff --git a/src/kits/mail/regex.c b/src/kits/mail/regex.c new file mode 100644 index 0000000000..3b219962da --- /dev/null +++ b/src/kits/mail/regex.c @@ -0,0 +1,5806 @@ +/* Extended regular expression matching and search library, + version 0.12. + (Implements POSIX draft P1003.2/D11.2, except for some of the + internationalization features.) + Copyright (C) 1993, 94, 95, 96, 97, 98 Free Software Foundation, Inc. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* AIX requires this to be the first thing in the file. */ +#if defined _AIX && !defined REGEX_MALLOC + #pragma alloca +#endif + +#undef _GNU_SOURCE +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifndef PARAMS +# if defined __GNUC__ || (defined __STDC__ && __STDC__) +# define PARAMS(args) args +# else +# define PARAMS(args) () +# endif /* GCC. */ +#endif /* Not PARAMS. */ + +#if defined STDC_HEADERS && !defined emacs +# include +#else +/* We need this for `regex.h', and perhaps for the Emacs include files. */ +# include +#endif + +/* For platform which support the ISO C amendement 1 functionality we + support user defined character classes. */ +#if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) +/* Solaris 2.5 has a bug: must be included before . */ +# include +# include + +/* We have to keep the namespace clean. */ +# define regfree(preg) __regfree (preg) +# define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) +# define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) +# define regerror(errcode, preg, errbuf, errbuf_size) \ + __regerror(errcode, preg, errbuf, errbuf_size) +# define re_set_registers(bu, re, nu, st, en) \ + __re_set_registers (bu, re, nu, st, en) +# define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ + __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) +# define re_match(bufp, string, size, pos, regs) \ + __re_match (bufp, string, size, pos, regs) +# define re_search(bufp, string, size, startpos, range, regs) \ + __re_search (bufp, string, size, startpos, range, regs) +# define re_compile_pattern(pattern, length, bufp) \ + __re_compile_pattern (pattern, length, bufp) +# define re_set_syntax(syntax) __re_set_syntax (syntax) +# define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ + __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) +# define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) + +#define btowc __btowc +#endif + +/* This is for other GNU distributions with internationalized messages. */ +#if 0 +# include +#else +# define gettext(msgid) (msgid) +#endif + +#ifndef gettext_noop +/* This define is so xgettext can find the internationalizable + strings. */ +# define gettext_noop(String) String +#endif + +/* The `emacs' switch turns on certain matching commands + that make sense only in Emacs. */ +#ifdef emacs + +# include "lisp.h" +# include "buffer.h" +# include "syntax.h" + +#else /* not emacs */ + +/* If we are not linking with Emacs proper, + we can't use the relocating allocator + even if config.h says that we can. */ +# undef REL_ALLOC + +# if defined STDC_HEADERS || defined _LIBC +# include +# else +char *malloc (); +char *realloc (); +# endif + +/* When used in Emacs's lib-src, we need to get bzero and bcopy somehow. + If nothing else has been done, use the method below. */ +# ifdef INHIBIT_STRING_HEADER +# if !(defined HAVE_BZERO && defined HAVE_BCOPY) +# if !defined bzero && !defined bcopy +# undef INHIBIT_STRING_HEADER +# endif +# endif +# endif + +/* This is the normal way of making sure we have a bcopy and a bzero. + This is used in most programs--a few other programs avoid this + by defining INHIBIT_STRING_HEADER. */ +# ifndef INHIBIT_STRING_HEADER +# if defined HAVE_STRING_H || defined STDC_HEADERS || defined _LIBC +# include +# ifndef bzero +# ifndef _LIBC +# define bzero(s, n) (memset (s, '\0', n), (s)) +# else +# define bzero(s, n) __bzero (s, n) +# endif +# endif +# else +# include +# ifndef memcmp +# define memcmp(s1, s2, n) bcmp (s1, s2, n) +# endif +# ifndef memcpy +# define memcpy(d, s, n) (bcopy (s, d, n), (d)) +# endif +# endif +# endif + +/* Define the syntax stuff for \<, \>, etc. */ + +/* This must be nonzero for the wordchar and notwordchar pattern + commands in re_match_2. */ +# ifndef Sword +# define Sword 1 +# endif + +# ifdef SWITCH_ENUM_BUG +# define SWITCH_ENUM_CAST(x) ((int)(x)) +# else +# define SWITCH_ENUM_CAST(x) (x) +# endif + +/* How many characters in the character set. */ +# define CHAR_SET_SIZE 256 + +# ifdef SYNTAX_TABLE + +extern char *re_syntax_table; + +# else /* not SYNTAX_TABLE */ + +static char re_syntax_table[CHAR_SET_SIZE]; + +static void +init_syntax_once () +{ + register int c; + static int done = 0; + + if (done) + return; + + bzero (re_syntax_table, sizeof re_syntax_table); + + for (c = 'a'; c <= 'z'; c++) + re_syntax_table[c] = Sword; + + for (c = 'A'; c <= 'Z'; c++) + re_syntax_table[c] = Sword; + + for (c = '0'; c <= '9'; c++) + re_syntax_table[c] = Sword; + + re_syntax_table['_'] = Sword; + + done = 1; +} + +# endif /* not SYNTAX_TABLE */ + +# define SYNTAX(c) re_syntax_table[c] + +#endif /* not emacs */ + +/* Get the interface, including the syntax bits. */ +#include "regex.h" + +/* isalpha etc. are used for the character classes. */ +#include + +/* Jim Meyering writes: + + "... Some ctype macros are valid only for character codes that + isascii says are ASCII (SGI's IRIX-4.0.5 is one such system --when + using /bin/cc or gcc but without giving an ansi option). So, all + ctype uses should be through macros like ISPRINT... If + STDC_HEADERS is defined, then autoconf has verified that the ctype + macros don't need to be guarded with references to isascii. ... + Defining isascii to 1 should let any compiler worth its salt + eliminate the && through constant folding." + Solaris defines some of these symbols so we must undefine them first. */ + +#undef ISASCII +#if defined STDC_HEADERS || (!defined isascii && !defined HAVE_ISASCII) +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII (c) && isblank (c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif +#ifdef isgraph +# define ISGRAPH(c) (ISASCII (c) && isgraph (c)) +#else +# define ISGRAPH(c) (ISASCII (c) && isprint (c) && !isspace (c)) +#endif + +#undef ISPRINT +#define ISPRINT(c) (ISASCII (c) && isprint (c)) +#define ISDIGIT(c) (ISASCII (c) && isdigit (c)) +#define ISALNUM(c) (ISASCII (c) && isalnum (c)) +#define ISALPHA(c) (ISASCII (c) && isalpha (c)) +#define ISCNTRL(c) (ISASCII (c) && iscntrl (c)) +#define ISLOWER(c) (ISASCII (c) && islower (c)) +#define ISPUNCT(c) (ISASCII (c) && ispunct (c)) +#define ISSPACE(c) (ISASCII (c) && isspace (c)) +#define ISUPPER(c) (ISASCII (c) && isupper (c)) +#define ISXDIGIT(c) (ISASCII (c) && isxdigit (c)) + +#ifndef NULL +# define NULL (void *)0 +#endif + +/* We remove any previous definition of `SIGN_EXTEND_CHAR', + since ours (we hope) works properly with all combinations of + machines, compilers, `char' and `unsigned char' argument types. + (Per Bothner suggested the basic approach.) */ +#undef SIGN_EXTEND_CHAR +#if __STDC__ +# define SIGN_EXTEND_CHAR(c) ((signed char) (c)) +#else /* not __STDC__ */ +/* As in Harbison and Steele. */ +# define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128) +#endif + +/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we + use `alloca' instead of `malloc'. This is because using malloc in + re_search* or re_match* could cause memory leaks when C-g is used in + Emacs; also, malloc is slower and causes storage fragmentation. On + the other hand, malloc is more portable, and easier to debug. + + Because we sometimes use alloca, some routines have to be macros, + not functions -- `alloca'-allocated space disappears at the end of the + function it is called in. */ + +#ifdef REGEX_MALLOC + +# define REGEX_ALLOCATE malloc +# define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize) +# define REGEX_FREE free + +#else /* not REGEX_MALLOC */ + +/* Emacs already defines alloca, sometimes. */ +# ifndef alloca + +/* Make alloca work the best possible way. */ +# ifdef __GNUC__ +# define alloca __builtin_alloca +# else /* not __GNUC__ */ +# if HAVE_ALLOCA_H +# include +# endif /* HAVE_ALLOCA_H */ +# endif /* not __GNUC__ */ + +# endif /* not alloca */ + +# define REGEX_ALLOCATE alloca + +/* Assumes a `char *destination' variable. */ +# define REGEX_REALLOCATE(source, osize, nsize) \ + (destination = (char *) alloca (nsize), \ + memcpy (destination, source, osize)) + +/* No need to do anything to free, after alloca. */ +# define REGEX_FREE(arg) ((void)0) /* Do nothing! But inhibit gcc warning. */ + +#endif /* not REGEX_MALLOC */ + +/* Define how to allocate the failure stack. */ + +#if defined REL_ALLOC && defined REGEX_MALLOC + +# define REGEX_ALLOCATE_STACK(size) \ + r_alloc (&failure_stack_ptr, (size)) +# define REGEX_REALLOCATE_STACK(source, osize, nsize) \ + r_re_alloc (&failure_stack_ptr, (nsize)) +# define REGEX_FREE_STACK(ptr) \ + r_alloc_free (&failure_stack_ptr) + +#else /* not using relocating allocator */ + +# ifdef REGEX_MALLOC + +# define REGEX_ALLOCATE_STACK malloc +# define REGEX_REALLOCATE_STACK(source, osize, nsize) realloc (source, nsize) +# define REGEX_FREE_STACK free + +# else /* not REGEX_MALLOC */ + +# define REGEX_ALLOCATE_STACK alloca + +# define REGEX_REALLOCATE_STACK(source, osize, nsize) \ + REGEX_REALLOCATE (source, osize, nsize) +/* No need to explicitly free anything. */ +# define REGEX_FREE_STACK(arg) + +# endif /* not REGEX_MALLOC */ +#endif /* not using relocating allocator */ + + +/* True if `size1' is non-NULL and PTR is pointing anywhere inside + `string1' or just past its end. This works if PTR is NULL, which is + a good thing. */ +#define FIRST_STRING_P(ptr) \ + (size1 && string1 <= (ptr) && (ptr) <= string1 + size1) + +/* (Re)Allocate N items of type T using malloc, or fail. */ +#define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t))) +#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t))) +#define RETALLOC_IF(addr, n, t) \ + if (addr) RETALLOC((addr), (n), t); else (addr) = TALLOC ((n), t) +#define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t))) + +#define BYTEWIDTH 8 /* In bits. */ + +#define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) + +#undef MAX +#undef MIN +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +typedef char boolean; +#define false 0 +#define true 1 + +static int re_match_2_internal PARAMS ((struct re_pattern_buffer *bufp, + const char *string1, int size1, + const char *string2, int size2, + int pos, + struct re_registers *regs, + int stop)); + +/* These are the command codes that appear in compiled regular + expressions. Some opcodes are followed by argument bytes. A + command code can specify any interpretation whatsoever for its + arguments. Zero bytes may appear in the compiled regular expression. */ + +typedef enum +{ + no_op = 0, + + /* Succeed right away--no more backtracking. */ + succeed, + + /* Followed by one byte giving n, then by n literal bytes. */ + exactn, + + /* Matches any (more or less) character. */ + anychar, + + /* Matches any one char belonging to specified set. First + following byte is number of bitmap bytes. Then come bytes + for a bitmap saying which chars are in. Bits in each byte + are ordered low-bit-first. A character is in the set if its + bit is 1. A character too large to have a bit in the map is + automatically not in the set. */ + charset, + + /* Same parameters as charset, but match any character that is + not one of those specified. */ + charset_not, + + /* Start remembering the text that is matched, for storing in a + register. Followed by one byte with the register number, in + the range 0 to one less than the pattern buffer's re_nsub + field. Then followed by one byte with the number of groups + inner to this one. (This last has to be part of the + start_memory only because we need it in the on_failure_jump + of re_match_2.) */ + start_memory, + + /* Stop remembering the text that is matched and store it in a + memory register. Followed by one byte with the register + number, in the range 0 to one less than `re_nsub' in the + pattern buffer, and one byte with the number of inner groups, + just like `start_memory'. (We need the number of inner + groups here because we don't have any easy way of finding the + corresponding start_memory when we're at a stop_memory.) */ + stop_memory, + + /* Match a duplicate of something remembered. Followed by one + byte containing the register number. */ + duplicate, + + /* Fail unless at beginning of line. */ + begline, + + /* Fail unless at end of line. */ + endline, + + /* Succeeds if at beginning of buffer (if emacs) or at beginning + of string to be matched (if not). */ + begbuf, + + /* Analogously, for end of buffer/string. */ + endbuf, + + /* Followed by two byte relative address to which to jump. */ + jump, + + /* Same as jump, but marks the end of an alternative. */ + jump_past_alt, + + /* Followed by two-byte relative address of place to resume at + in case of failure. */ + on_failure_jump, + + /* Like on_failure_jump, but pushes a placeholder instead of the + current string position when executed. */ + on_failure_keep_string_jump, + + /* Throw away latest failure point and then jump to following + two-byte relative address. */ + pop_failure_jump, + + /* Change to pop_failure_jump if know won't have to backtrack to + match; otherwise change to jump. This is used to jump + back to the beginning of a repeat. If what follows this jump + clearly won't match what the repeat does, such that we can be + sure that there is no use backtracking out of repetitions + already matched, then we change it to a pop_failure_jump. + Followed by two-byte address. */ + maybe_pop_jump, + + /* Jump to following two-byte address, and push a dummy failure + point. This failure point will be thrown away if an attempt + is made to use it for a failure. A `+' construct makes this + before the first repeat. Also used as an intermediary kind + of jump when compiling an alternative. */ + dummy_failure_jump, + + /* Push a dummy failure point and continue. Used at the end of + alternatives. */ + push_dummy_failure, + + /* Followed by two-byte relative address and two-byte number n. + After matching N times, jump to the address upon failure. */ + succeed_n, + + /* Followed by two-byte relative address, and two-byte number n. + Jump to the address N times, then fail. */ + jump_n, + + /* Set the following two-byte relative address to the + subsequent two-byte number. The address *includes* the two + bytes of number. */ + set_number_at, + + wordchar, /* Matches any word-constituent character. */ + notwordchar, /* Matches any char that is not a word-constituent. */ + + wordbeg, /* Succeeds if at word beginning. */ + wordend, /* Succeeds if at word end. */ + + wordbound, /* Succeeds if at a word boundary. */ + notwordbound /* Succeeds if not at a word boundary. */ + +#ifdef emacs + ,before_dot, /* Succeeds if before point. */ + at_dot, /* Succeeds if at point. */ + after_dot, /* Succeeds if after point. */ + + /* Matches any character whose syntax is specified. Followed by + a byte which contains a syntax code, e.g., Sword. */ + syntaxspec, + + /* Matches any character whose syntax is not that specified. */ + notsyntaxspec +#endif /* emacs */ +} re_opcode_t; + +/* Common operations on the compiled pattern. */ + +/* Store NUMBER in two contiguous bytes starting at DESTINATION. */ + +#define STORE_NUMBER(destination, number) \ + do { \ + (destination)[0] = (number) & 0377; \ + (destination)[1] = (number) >> 8; \ + } while (0) + +/* Same as STORE_NUMBER, except increment DESTINATION to + the byte after where the number is stored. Therefore, DESTINATION + must be an lvalue. */ + +#define STORE_NUMBER_AND_INCR(destination, number) \ + do { \ + STORE_NUMBER (destination, number); \ + (destination) += 2; \ + } while (0) + +/* Put into DESTINATION a number stored in two contiguous bytes starting + at SOURCE. */ + +#define EXTRACT_NUMBER(destination, source) \ + do { \ + (destination) = *(source) & 0377; \ + (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8; \ + } while (0) + +#ifdef DEBUG +static void extract_number _RE_ARGS ((int *dest, unsigned char *source)); +static void +extract_number (dest, source) + int *dest; + unsigned char *source; +{ + int temp = SIGN_EXTEND_CHAR (*(source + 1)); + *dest = *source & 0377; + *dest += temp << 8; +} + +# ifndef EXTRACT_MACROS /* To debug the macros. */ +# undef EXTRACT_NUMBER +# define EXTRACT_NUMBER(dest, src) extract_number (&dest, src) +# endif /* not EXTRACT_MACROS */ + +#endif /* DEBUG */ + +/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number. + SOURCE must be an lvalue. */ + +#define EXTRACT_NUMBER_AND_INCR(destination, source) \ + do { \ + EXTRACT_NUMBER (destination, source); \ + (source) += 2; \ + } while (0) + +#ifdef DEBUG +static void extract_number_and_incr _RE_ARGS ((int *destination, + unsigned char **source)); +static void +extract_number_and_incr (destination, source) + int *destination; + unsigned char **source; +{ + extract_number (destination, *source); + *source += 2; +} + +# ifndef EXTRACT_MACROS +# undef EXTRACT_NUMBER_AND_INCR +# define EXTRACT_NUMBER_AND_INCR(dest, src) \ + extract_number_and_incr (&dest, &src) +# endif /* not EXTRACT_MACROS */ + +#endif /* DEBUG */ + +/* If DEBUG is defined, Regex prints many voluminous messages about what + it is doing (if the variable `debug' is nonzero). If linked with the + main program in `iregex.c', you can enter patterns and strings + interactively. And if linked with the main program in `main.c' and + the other test files, you can run the already-written tests. */ + +#ifdef DEBUG + +/* We use standard I/O for debugging. */ +# include + +/* It is useful to test things that ``must'' be true when debugging. */ +# include + +static int debug = 0; + +# define DEBUG_STATEMENT(e) e +# define DEBUG_PRINT1(x) if (debug) printf (x) +# define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2) +# define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3) +# define DEBUG_PRINT4(x1, x2, x3, x4) if (debug) printf (x1, x2, x3, x4) +# define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) \ + if (debug) print_partial_compiled_pattern (s, e) +# define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) \ + if (debug) print_double_string (w, s1, sz1, s2, sz2) + + +/* Print the fastmap in human-readable form. */ + +void +print_fastmap (fastmap) + char *fastmap; +{ + unsigned was_a_range = 0; + unsigned i = 0; + + while (i < (1 << BYTEWIDTH)) + { + if (fastmap[i++]) + { + was_a_range = 0; + putchar (i - 1); + while (i < (1 << BYTEWIDTH) && fastmap[i]) + { + was_a_range = 1; + i++; + } + if (was_a_range) + { + printf ("-"); + putchar (i - 1); + } + } + } + putchar ('\n'); +} + + +/* Print a compiled pattern string in human-readable form, starting at + the START pointer into it and ending just before the pointer END. */ + +void +print_partial_compiled_pattern (start, end) + unsigned char *start; + unsigned char *end; +{ + int mcnt, mcnt2; + unsigned char *p1; + unsigned char *p = start; + unsigned char *pend = end; + + if (start == NULL) + { + printf ("(null)\n"); + return; + } + + /* Loop over pattern commands. */ + while (p < pend) + { + printf ("%d:\t", p - start); + + switch ((re_opcode_t) *p++) + { + case no_op: + printf ("/no_op"); + break; + + case exactn: + mcnt = *p++; + printf ("/exactn/%d", mcnt); + do + { + putchar ('/'); + putchar (*p++); + } + while (--mcnt); + break; + + case start_memory: + mcnt = *p++; + printf ("/start_memory/%d/%d", mcnt, *p++); + break; + + case stop_memory: + mcnt = *p++; + printf ("/stop_memory/%d/%d", mcnt, *p++); + break; + + case duplicate: + printf ("/duplicate/%d", *p++); + break; + + case anychar: + printf ("/anychar"); + break; + + case charset: + case charset_not: + { + register int c, last = -100; + register int in_range = 0; + + printf ("/charset [%s", + (re_opcode_t) *(p - 1) == charset_not ? "^" : ""); + + assert (p + *p < pend); + + for (c = 0; c < 256; c++) + if (c / 8 < *p + && (p[1 + (c/8)] & (1 << (c % 8)))) + { + /* Are we starting a range? */ + if (last + 1 == c && ! in_range) + { + putchar ('-'); + in_range = 1; + } + /* Have we broken a range? */ + else if (last + 1 != c && in_range) + { + putchar (last); + in_range = 0; + } + + if (! in_range) + putchar (c); + + last = c; + } + + if (in_range) + putchar (last); + + putchar (']'); + + p += 1 + *p; + } + break; + + case begline: + printf ("/begline"); + break; + + case endline: + printf ("/endline"); + break; + + case on_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/on_failure_jump to %d", p + mcnt - start); + break; + + case on_failure_keep_string_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/on_failure_keep_string_jump to %d", p + mcnt - start); + break; + + case dummy_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/dummy_failure_jump to %d", p + mcnt - start); + break; + + case push_dummy_failure: + printf ("/push_dummy_failure"); + break; + + case maybe_pop_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/maybe_pop_jump to %d", p + mcnt - start); + break; + + case pop_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/pop_failure_jump to %d", p + mcnt - start); + break; + + case jump_past_alt: + extract_number_and_incr (&mcnt, &p); + printf ("/jump_past_alt to %d", p + mcnt - start); + break; + + case jump: + extract_number_and_incr (&mcnt, &p); + printf ("/jump to %d", p + mcnt - start); + break; + + case succeed_n: + extract_number_and_incr (&mcnt, &p); + p1 = p + mcnt; + extract_number_and_incr (&mcnt2, &p); + printf ("/succeed_n to %d, %d times", p1 - start, mcnt2); + break; + + case jump_n: + extract_number_and_incr (&mcnt, &p); + p1 = p + mcnt; + extract_number_and_incr (&mcnt2, &p); + printf ("/jump_n to %d, %d times", p1 - start, mcnt2); + break; + + case set_number_at: + extract_number_and_incr (&mcnt, &p); + p1 = p + mcnt; + extract_number_and_incr (&mcnt2, &p); + printf ("/set_number_at location %d to %d", p1 - start, mcnt2); + break; + + case wordbound: + printf ("/wordbound"); + break; + + case notwordbound: + printf ("/notwordbound"); + break; + + case wordbeg: + printf ("/wordbeg"); + break; + + case wordend: + printf ("/wordend"); + +# ifdef emacs + case before_dot: + printf ("/before_dot"); + break; + + case at_dot: + printf ("/at_dot"); + break; + + case after_dot: + printf ("/after_dot"); + break; + + case syntaxspec: + printf ("/syntaxspec"); + mcnt = *p++; + printf ("/%d", mcnt); + break; + + case notsyntaxspec: + printf ("/notsyntaxspec"); + mcnt = *p++; + printf ("/%d", mcnt); + break; +# endif /* emacs */ + + case wordchar: + printf ("/wordchar"); + break; + + case notwordchar: + printf ("/notwordchar"); + break; + + case begbuf: + printf ("/begbuf"); + break; + + case endbuf: + printf ("/endbuf"); + break; + + default: + printf ("?%d", *(p-1)); + } + + putchar ('\n'); + } + + printf ("%d:\tend of pattern.\n", p - start); +} + + +void +print_compiled_pattern (bufp) + struct re_pattern_buffer *bufp; +{ + unsigned char *buffer = bufp->buffer; + + print_partial_compiled_pattern (buffer, buffer + bufp->used); + printf ("%ld bytes used/%ld bytes allocated.\n", + bufp->used, bufp->allocated); + + if (bufp->fastmap_accurate && bufp->fastmap) + { + printf ("fastmap: "); + print_fastmap (bufp->fastmap); + } + + printf ("re_nsub: %d\t", bufp->re_nsub); + printf ("regs_alloc: %d\t", bufp->regs_allocated); + printf ("can_be_null: %d\t", bufp->can_be_null); + printf ("newline_anchor: %d\n", bufp->newline_anchor); + printf ("no_sub: %d\t", bufp->no_sub); + printf ("not_bol: %d\t", bufp->not_bol); + printf ("not_eol: %d\t", bufp->not_eol); + printf ("syntax: %lx\n", bufp->syntax); + /* Perhaps we should print the translate table? */ +} + + +void +print_double_string (where, string1, size1, string2, size2) + const char *where; + const char *string1; + const char *string2; + int size1; + int size2; +{ + int this_char; + + if (where == NULL) + printf ("(null)"); + else + { + if (FIRST_STRING_P (where)) + { + for (this_char = where - string1; this_char < size1; this_char++) + putchar (string1[this_char]); + + where = string2; + } + + for (this_char = where - string2; this_char < size2; this_char++) + putchar (string2[this_char]); + } +} + +void +printchar (c) + int c; +{ + putc (c, stderr); +} + +#else /* not DEBUG */ + +# undef assert +# define assert(e) + +# define DEBUG_STATEMENT(e) +# define DEBUG_PRINT1(x) +# define DEBUG_PRINT2(x1, x2) +# define DEBUG_PRINT3(x1, x2, x3) +# define DEBUG_PRINT4(x1, x2, x3, x4) +# define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) +# define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) + +#endif /* not DEBUG */ + +/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can + also be assigned to arbitrarily: each pattern buffer stores its own + syntax, so it can be changed between regex compilations. */ +/* This has no initializer because initialized variables in Emacs + become read-only after dumping. */ +_EXPORT reg_syntax_t re_syntax_options; + + +/* Specify the precise syntax of regexps for compilation. This provides + for compatibility for various utilities which historically have + different, incompatible syntaxes. + + The argument SYNTAX is a bit mask comprised of the various bits + defined in regex.h. We return the old syntax. */ + +_EXPORT reg_syntax_t +re_set_syntax (syntax) + reg_syntax_t syntax; +{ + reg_syntax_t ret = re_syntax_options; + + re_syntax_options = syntax; +#ifdef DEBUG + if (syntax & RE_DEBUG) + debug = 1; + else if (debug) /* was on but now is not */ + debug = 0; +#endif /* DEBUG */ + return ret; +} +#ifdef _LIBC +weak_alias (__re_set_syntax, re_set_syntax) +#endif + +/* This table gives an error message for each of the error codes listed + in regex.h. Obviously the order here has to be same as there. + POSIX doesn't require that we do anything for REG_NOERROR, + but why not be nice? */ + +static const char *re_error_msgid[] = + { + gettext_noop ("Success"), /* REG_NOERROR */ + gettext_noop ("No match"), /* REG_NOMATCH */ + gettext_noop ("Invalid regular expression"), /* REG_BADPAT */ + gettext_noop ("Invalid collation character"), /* REG_ECOLLATE */ + gettext_noop ("Invalid character class name"), /* REG_ECTYPE */ + gettext_noop ("Trailing backslash"), /* REG_EESCAPE */ + gettext_noop ("Invalid back reference"), /* REG_ESUBREG */ + gettext_noop ("Unmatched [ or [^"), /* REG_EBRACK */ + gettext_noop ("Unmatched ( or \\("), /* REG_EPAREN */ + gettext_noop ("Unmatched \\{"), /* REG_EBRACE */ + gettext_noop ("Invalid content of \\{\\}"), /* REG_BADBR */ + gettext_noop ("Invalid range end"), /* REG_ERANGE */ + gettext_noop ("Memory exhausted"), /* REG_ESPACE */ + gettext_noop ("Invalid preceding regular expression"), /* REG_BADRPT */ + gettext_noop ("Premature end of regular expression"), /* REG_EEND */ + gettext_noop ("Regular expression too big"), /* REG_ESIZE */ + gettext_noop ("Unmatched ) or \\)"), /* REG_ERPAREN */ + }; + +/* Avoiding alloca during matching, to placate r_alloc. */ + +/* Define MATCH_MAY_ALLOCATE unless we need to make sure that the + searching and matching functions should not call alloca. On some + systems, alloca is implemented in terms of malloc, and if we're + using the relocating allocator routines, then malloc could cause a + relocation, which might (if the strings being searched are in the + ralloc heap) shift the data out from underneath the regexp + routines. + + Here's another reason to avoid allocation: Emacs + processes input from X in a signal handler; processing X input may + call malloc; if input arrives while a matching routine is calling + malloc, then we're scrod. But Emacs can't just block input while + calling matching routines; then we don't notice interrupts when + they come in. So, Emacs blocks input around all regexp calls + except the matching calls, which it leaves unprotected, in the + faith that they will not malloc. */ + +/* Normally, this is fine. */ +#define MATCH_MAY_ALLOCATE + +/* When using GNU C, we are not REALLY using the C alloca, no matter + what config.h may say. So don't take precautions for it. */ +#ifdef __GNUC__ +# undef C_ALLOCA +#endif + +/* The match routines may not allocate if (1) they would do it with malloc + and (2) it's not safe for them to use malloc. + Note that if REL_ALLOC is defined, matching would not use malloc for the + failure stack, but we would still use it for the register vectors; + so REL_ALLOC should not affect this. */ +#if (defined C_ALLOCA || defined REGEX_MALLOC) && defined emacs +# undef MATCH_MAY_ALLOCATE +#endif + + +/* Failure stack declarations and macros; both re_compile_fastmap and + re_match_2 use a failure stack. These have to be macros because of + REGEX_ALLOCATE_STACK. */ + + +/* Number of failure points for which to initially allocate space + when matching. If this number is exceeded, we allocate more + space, so it is not a hard limit. */ +#ifndef INIT_FAILURE_ALLOC +# define INIT_FAILURE_ALLOC 5 +#endif + +/* Roughly the maximum number of failure points on the stack. Would be + exactly that if always used MAX_FAILURE_ITEMS items each time we failed. + This is a variable only so users of regex can assign to it; we never + change it ourselves. */ + +#ifdef INT_IS_16BIT + +# if defined MATCH_MAY_ALLOCATE +/* 4400 was enough to cause a crash on Alpha OSF/1, + whose default stack limit is 2mb. */ +long int re_max_failures = 4000; +# else +long int re_max_failures = 2000; +# endif + +union fail_stack_elt +{ + unsigned char *pointer; + long int integer; +}; + +typedef union fail_stack_elt fail_stack_elt_t; + +typedef struct +{ + fail_stack_elt_t *stack; + unsigned long int size; + unsigned long int avail; /* Offset of next open position. */ +} fail_stack_type; + +#else /* not INT_IS_16BIT */ + +# if defined MATCH_MAY_ALLOCATE +/* 4400 was enough to cause a crash on Alpha OSF/1, + whose default stack limit is 2mb. */ +int re_max_failures = 20000; +# else +int re_max_failures = 2000; +# endif + +union fail_stack_elt +{ + unsigned char *pointer; + int integer; +}; + +typedef union fail_stack_elt fail_stack_elt_t; + +typedef struct +{ + fail_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} fail_stack_type; + +#endif /* INT_IS_16BIT */ + +#define FAIL_STACK_EMPTY() (fail_stack.avail == 0) +#define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0) +#define FAIL_STACK_FULL() (fail_stack.avail == fail_stack.size) + + +/* Define macros to initialize and free the failure stack. + Do `return -2' if the alloc fails. */ + +#ifdef MATCH_MAY_ALLOCATE +# define INIT_FAIL_STACK() \ + do { \ + fail_stack.stack = (fail_stack_elt_t *) \ + REGEX_ALLOCATE_STACK (INIT_FAILURE_ALLOC * sizeof (fail_stack_elt_t)); \ + \ + if (fail_stack.stack == NULL) \ + return -2; \ + \ + fail_stack.size = INIT_FAILURE_ALLOC; \ + fail_stack.avail = 0; \ + } while (0) + +# define RESET_FAIL_STACK() REGEX_FREE_STACK (fail_stack.stack) +#else +# define INIT_FAIL_STACK() \ + do { \ + fail_stack.avail = 0; \ + } while (0) + +# define RESET_FAIL_STACK() +#endif + + +/* Double the size of FAIL_STACK, up to approximately `re_max_failures' items. + + Return 1 if succeeds, and 0 if either ran out of memory + allocating space for it or it was already too large. + + REGEX_REALLOCATE_STACK requires `destination' be declared. */ + +#define DOUBLE_FAIL_STACK(fail_stack) \ + ((fail_stack).size > (unsigned) (re_max_failures * MAX_FAILURE_ITEMS) \ + ? 0 \ + : ((fail_stack).stack = (fail_stack_elt_t *) \ + REGEX_REALLOCATE_STACK ((fail_stack).stack, \ + (fail_stack).size * sizeof (fail_stack_elt_t), \ + ((fail_stack).size << 1) * sizeof (fail_stack_elt_t)), \ + \ + (fail_stack).stack == NULL \ + ? 0 \ + : ((fail_stack).size <<= 1, \ + 1))) + + +/* Push pointer POINTER on FAIL_STACK. + Return 1 if was able to do so and 0 if ran out of memory allocating + space to do so. */ +#define PUSH_PATTERN_OP(POINTER, FAIL_STACK) \ + ((FAIL_STACK_FULL () \ + && !DOUBLE_FAIL_STACK (FAIL_STACK)) \ + ? 0 \ + : ((FAIL_STACK).stack[(FAIL_STACK).avail++].pointer = POINTER, \ + 1)) + +/* Push a pointer value onto the failure stack. + Assumes the variable `fail_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_POINTER(item) \ + fail_stack.stack[fail_stack.avail++].pointer = (unsigned char *) (item) + +/* This pushes an integer-valued item onto the failure stack. + Assumes the variable `fail_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_INT(item) \ + fail_stack.stack[fail_stack.avail++].integer = (item) + +/* Push a fail_stack_elt_t value onto the failure stack. + Assumes the variable `fail_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_ELT(item) \ + fail_stack.stack[fail_stack.avail++] = (item) + +/* These three POP... operations complement the three PUSH... operations. + All assume that `fail_stack' is nonempty. */ +#define POP_FAILURE_POINTER() fail_stack.stack[--fail_stack.avail].pointer +#define POP_FAILURE_INT() fail_stack.stack[--fail_stack.avail].integer +#define POP_FAILURE_ELT() fail_stack.stack[--fail_stack.avail] + +/* Used to omit pushing failure point id's when we're not debugging. */ +#ifdef DEBUG +# define DEBUG_PUSH PUSH_FAILURE_INT +# define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_INT () +#else +# define DEBUG_PUSH(item) +# define DEBUG_POP(item_addr) +#endif + + +/* Push the information about the state we will need + if we ever fail back to it. + + Requires variables fail_stack, regstart, regend, reg_info, and + num_regs_pushed be declared. DOUBLE_FAIL_STACK requires `destination' + be declared. + + Does `return FAILURE_CODE' if runs out of memory. */ + +#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \ + do { \ + char *destination; \ + /* Must be int, so when we don't save any registers, the arithmetic \ + of 0 + -1 isn't done as unsigned. */ \ + /* Can't be int, since there is not a shred of a guarantee that int \ + is wide enough to hold a value of something to which pointer can \ + be assigned */ \ + active_reg_t this_reg; \ + \ + DEBUG_STATEMENT (failure_id++); \ + DEBUG_STATEMENT (nfailure_points_pushed++); \ + DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id); \ + DEBUG_PRINT2 (" Before push, next avail: %d\n", (fail_stack).avail);\ + DEBUG_PRINT2 (" size: %d\n", (fail_stack).size);\ + \ + DEBUG_PRINT2 (" slots needed: %ld\n", NUM_FAILURE_ITEMS); \ + DEBUG_PRINT2 (" available: %d\n", REMAINING_AVAIL_SLOTS); \ + \ + /* Ensure we have enough space allocated for what we will push. */ \ + while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \ + { \ + if (!DOUBLE_FAIL_STACK (fail_stack)) \ + return failure_code; \ + \ + DEBUG_PRINT2 ("\n Doubled stack; size now: %d\n", \ + (fail_stack).size); \ + DEBUG_PRINT2 (" slots available: %d\n", REMAINING_AVAIL_SLOTS);\ + } \ + \ + /* Push the info, starting with the registers. */ \ + DEBUG_PRINT1 ("\n"); \ + \ + if (1) \ + for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \ + this_reg++) \ + { \ + DEBUG_PRINT2 (" Pushing reg: %lu\n", this_reg); \ + DEBUG_STATEMENT (num_regs_pushed++); \ + \ + DEBUG_PRINT2 (" start: %p\n", regstart[this_reg]); \ + PUSH_FAILURE_POINTER (regstart[this_reg]); \ + \ + DEBUG_PRINT2 (" end: %p\n", regend[this_reg]); \ + PUSH_FAILURE_POINTER (regend[this_reg]); \ + \ + DEBUG_PRINT2 (" info: %p\n ", \ + reg_info[this_reg].word.pointer); \ + DEBUG_PRINT2 (" match_null=%d", \ + REG_MATCH_NULL_STRING_P (reg_info[this_reg])); \ + DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg])); \ + DEBUG_PRINT2 (" matched_something=%d", \ + MATCHED_SOMETHING (reg_info[this_reg])); \ + DEBUG_PRINT2 (" ever_matched=%d", \ + EVER_MATCHED_SOMETHING (reg_info[this_reg])); \ + DEBUG_PRINT1 ("\n"); \ + PUSH_FAILURE_ELT (reg_info[this_reg].word); \ + } \ + \ + DEBUG_PRINT2 (" Pushing low active reg: %ld\n", lowest_active_reg);\ + PUSH_FAILURE_INT (lowest_active_reg); \ + \ + DEBUG_PRINT2 (" Pushing high active reg: %ld\n", highest_active_reg);\ + PUSH_FAILURE_INT (highest_active_reg); \ + \ + DEBUG_PRINT2 (" Pushing pattern %p:\n", pattern_place); \ + DEBUG_PRINT_COMPILED_PATTERN (bufp, pattern_place, pend); \ + PUSH_FAILURE_POINTER (pattern_place); \ + \ + DEBUG_PRINT2 (" Pushing string %p: `", string_place); \ + DEBUG_PRINT_DOUBLE_STRING (string_place, string1, size1, string2, \ + size2); \ + DEBUG_PRINT1 ("'\n"); \ + PUSH_FAILURE_POINTER (string_place); \ + \ + DEBUG_PRINT2 (" Pushing failure id: %u\n", failure_id); \ + DEBUG_PUSH (failure_id); \ + } while (0) + +/* This is the number of items that are pushed and popped on the stack + for each register. */ +#define NUM_REG_ITEMS 3 + +/* Individual items aside from the registers. */ +#ifdef DEBUG +# define NUM_NONREG_ITEMS 5 /* Includes failure point id. */ +#else +# define NUM_NONREG_ITEMS 4 +#endif + +/* We push at most this many items on the stack. */ +/* We used to use (num_regs - 1), which is the number of registers + this regexp will save; but that was changed to 5 + to avoid stack overflow for a regexp with lots of parens. */ +#define MAX_FAILURE_ITEMS (5 * NUM_REG_ITEMS + NUM_NONREG_ITEMS) + +/* We actually push this many items. */ +#define NUM_FAILURE_ITEMS \ + (((0 \ + ? 0 : highest_active_reg - lowest_active_reg + 1) \ + * NUM_REG_ITEMS) \ + + NUM_NONREG_ITEMS) + +/* How many items can still be added to the stack without overflowing it. */ +#define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail) + + +/* Pops what PUSH_FAIL_STACK pushes. + + We restore into the parameters, all of which should be lvalues: + STR -- the saved data position. + PAT -- the saved pattern position. + LOW_REG, HIGH_REG -- the highest and lowest active registers. + REGSTART, REGEND -- arrays of string positions. + REG_INFO -- array of information about each subexpression. + + Also assumes the variables `fail_stack' and (if debugging), `bufp', + `pend', `string1', `size1', `string2', and `size2'. */ + +#define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info)\ +{ \ + DEBUG_STATEMENT (unsigned failure_id;) \ + active_reg_t this_reg; \ + const unsigned char *string_temp; \ + \ + assert (!FAIL_STACK_EMPTY ()); \ + \ + /* Remove failure points and point to how many regs pushed. */ \ + DEBUG_PRINT1 ("POP_FAILURE_POINT:\n"); \ + DEBUG_PRINT2 (" Before pop, next avail: %d\n", fail_stack.avail); \ + DEBUG_PRINT2 (" size: %d\n", fail_stack.size); \ + \ + assert (fail_stack.avail >= NUM_NONREG_ITEMS); \ + \ + DEBUG_POP (&failure_id); \ + DEBUG_PRINT2 (" Popping failure id: %u\n", failure_id); \ + \ + /* If the saved string location is NULL, it came from an \ + on_failure_keep_string_jump opcode, and we want to throw away the \ + saved NULL, thus retaining our current position in the string. */ \ + string_temp = POP_FAILURE_POINTER (); \ + if (string_temp != NULL) \ + str = (const char *) string_temp; \ + \ + DEBUG_PRINT2 (" Popping string %p: `", str); \ + DEBUG_PRINT_DOUBLE_STRING (str, string1, size1, string2, size2); \ + DEBUG_PRINT1 ("'\n"); \ + \ + pat = (unsigned char *) POP_FAILURE_POINTER (); \ + DEBUG_PRINT2 (" Popping pattern %p:\n", pat); \ + DEBUG_PRINT_COMPILED_PATTERN (bufp, pat, pend); \ + \ + /* Restore register info. */ \ + high_reg = (active_reg_t) POP_FAILURE_INT (); \ + DEBUG_PRINT2 (" Popping high active reg: %ld\n", high_reg); \ + \ + low_reg = (active_reg_t) POP_FAILURE_INT (); \ + DEBUG_PRINT2 (" Popping low active reg: %ld\n", low_reg); \ + \ + if (1) \ + for (this_reg = high_reg; this_reg >= low_reg; this_reg--) \ + { \ + DEBUG_PRINT2 (" Popping reg: %ld\n", this_reg); \ + \ + reg_info[this_reg].word = POP_FAILURE_ELT (); \ + DEBUG_PRINT2 (" info: %p\n", \ + reg_info[this_reg].word.pointer); \ + \ + regend[this_reg] = (const char *) POP_FAILURE_POINTER (); \ + DEBUG_PRINT2 (" end: %p\n", regend[this_reg]); \ + \ + regstart[this_reg] = (const char *) POP_FAILURE_POINTER (); \ + DEBUG_PRINT2 (" start: %p\n", regstart[this_reg]); \ + } \ + else \ + { \ + for (this_reg = highest_active_reg; this_reg > high_reg; this_reg--) \ + { \ + reg_info[this_reg].word.integer = 0; \ + regend[this_reg] = 0; \ + regstart[this_reg] = 0; \ + } \ + highest_active_reg = high_reg; \ + } \ + \ + set_regs_matched_done = 0; \ + DEBUG_STATEMENT (nfailure_points_popped++); \ +} /* POP_FAILURE_POINT */ + + + +/* Structure for per-register (a.k.a. per-group) information. + Other register information, such as the + starting and ending positions (which are addresses), and the list of + inner groups (which is a bits list) are maintained in separate + variables. + + We are making a (strictly speaking) nonportable assumption here: that + the compiler will pack our bit fields into something that fits into + the type of `word', i.e., is something that fits into one item on the + failure stack. */ + + +/* Declarations and macros for re_match_2. */ + +typedef union +{ + fail_stack_elt_t word; + struct + { + /* This field is one if this group can match the empty string, + zero if not. If not yet determined, `MATCH_NULL_UNSET_VALUE'. */ +#define MATCH_NULL_UNSET_VALUE 3 + unsigned match_null_string_p : 2; + unsigned is_active : 1; + unsigned matched_something : 1; + unsigned ever_matched_something : 1; + } bits; +} register_info_type; + +#define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p) +#define IS_ACTIVE(R) ((R).bits.is_active) +#define MATCHED_SOMETHING(R) ((R).bits.matched_something) +#define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something) + + +/* Call this when have matched a real character; it sets `matched' flags + for the subexpressions which we are currently inside. Also records + that those subexprs have matched. */ +#define SET_REGS_MATCHED() \ + do \ + { \ + if (!set_regs_matched_done) \ + { \ + active_reg_t r; \ + set_regs_matched_done = 1; \ + for (r = lowest_active_reg; r <= highest_active_reg; r++) \ + { \ + MATCHED_SOMETHING (reg_info[r]) \ + = EVER_MATCHED_SOMETHING (reg_info[r]) \ + = 1; \ + } \ + } \ + } \ + while (0) + +/* Registers are set to a sentinel when they haven't yet matched. */ +static char reg_unset_dummy; +#define REG_UNSET_VALUE (®_unset_dummy) +#define REG_UNSET(e) ((e) == REG_UNSET_VALUE) + +/* Subroutine declarations and macros for regex_compile. */ + +static reg_errcode_t regex_compile _RE_ARGS ((const char *pattern, size_t size, + reg_syntax_t syntax, + struct re_pattern_buffer *bufp)); +static void store_op1 _RE_ARGS ((re_opcode_t op, unsigned char *loc, int arg)); +static void store_op2 _RE_ARGS ((re_opcode_t op, unsigned char *loc, + int arg1, int arg2)); +static void insert_op1 _RE_ARGS ((re_opcode_t op, unsigned char *loc, + int arg, unsigned char *end)); +static void insert_op2 _RE_ARGS ((re_opcode_t op, unsigned char *loc, + int arg1, int arg2, unsigned char *end)); +static boolean at_begline_loc_p _RE_ARGS ((const char *pattern, const char *p, + reg_syntax_t syntax)); +static boolean at_endline_loc_p _RE_ARGS ((const char *p, const char *pend, + reg_syntax_t syntax)); +static reg_errcode_t compile_range _RE_ARGS ((const char **p_ptr, + const char *pend, + char *translate, + reg_syntax_t syntax, + unsigned char *b)); + +/* Fetch the next character in the uncompiled pattern---translating it + if necessary. Also cast from a signed character in the constant + string passed to us by the user to an unsigned char that we can use + as an array index (in, e.g., `translate'). */ +#ifndef PATFETCH +# define PATFETCH(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + if (translate) c = (unsigned char) translate[c]; \ + } while (0) +#endif + +/* Fetch the next character in the uncompiled pattern, with no + translation. */ +#define PATFETCH_RAW(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + } while (0) + +/* Go backwards one character in the pattern. */ +#define PATUNFETCH p-- + + +/* If `translate' is non-null, return translate[D], else just D. We + cast the subscript to translate because some data is declared as + `char *', to avoid warnings when a string constant is passed. But + when we use a character as a subscript we must make it unsigned. */ +#ifndef TRANSLATE +# define TRANSLATE(d) \ + (translate ? (char) translate[(unsigned char) (d)] : (d)) +#endif + + +/* Macros for outputting the compiled pattern into `buffer'. */ + +/* If the buffer isn't allocated when it comes in, use this. */ +#define INIT_BUF_SIZE 32 + +/* Make sure we have at least N more bytes of space in buffer. */ +#define GET_BUFFER_SPACE(n) \ + while ((unsigned long) (b - bufp->buffer + (n)) > bufp->allocated) \ + EXTEND_BUFFER () + +/* Make sure we have one more byte of buffer space and then add C to it. */ +#define BUF_PUSH(c) \ + do { \ + GET_BUFFER_SPACE (1); \ + *b++ = (unsigned char) (c); \ + } while (0) + + +/* Ensure we have two more bytes of buffer space and then append C1 and C2. */ +#define BUF_PUSH_2(c1, c2) \ + do { \ + GET_BUFFER_SPACE (2); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + } while (0) + + +/* As with BUF_PUSH_2, except for three bytes. */ +#define BUF_PUSH_3(c1, c2, c3) \ + do { \ + GET_BUFFER_SPACE (3); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + *b++ = (unsigned char) (c3); \ + } while (0) + + +/* Store a jump with opcode OP at LOC to location TO. We store a + relative address offset by the three bytes the jump itself occupies. */ +#define STORE_JUMP(op, loc, to) \ + store_op1 (op, loc, (int) ((to) - (loc) - 3)) + +/* Likewise, for a two-argument jump. */ +#define STORE_JUMP2(op, loc, to, arg) \ + store_op2 (op, loc, (int) ((to) - (loc) - 3), arg) + +/* Like `STORE_JUMP', but for inserting. Assume `b' is the buffer end. */ +#define INSERT_JUMP(op, loc, to) \ + insert_op1 (op, loc, (int) ((to) - (loc) - 3), b) + +/* Like `STORE_JUMP2', but for inserting. Assume `b' is the buffer end. */ +#define INSERT_JUMP2(op, loc, to, arg) \ + insert_op2 (op, loc, (int) ((to) - (loc) - 3), arg, b) + + +/* This is not an arbitrary limit: the arguments which represent offsets + into the pattern are two bytes long. So if 2^16 bytes turns out to + be too small, many things would have to change. */ +/* Any other compiler which, like MSC, has allocation limit below 2^16 + bytes will have to use approach similar to what was done below for + MSC and drop MAX_BUF_SIZE a bit. Otherwise you may end up + reallocating to 0 bytes. Such thing is not going to work too well. + You have been warned!! */ +#if defined _MSC_VER && !defined WIN32 +/* Microsoft C 16-bit versions limit malloc to approx 65512 bytes. + The REALLOC define eliminates a flurry of conversion warnings, + but is not required. */ +# define MAX_BUF_SIZE 65500L +# define REALLOC(p,s) realloc ((p), (size_t) (s)) +#else +# define MAX_BUF_SIZE (1L << 16) +# define REALLOC(p,s) realloc ((p), (s)) +#endif + +/* Extend the buffer by twice its current size via realloc and + reset the pointers that pointed into the old block to point to the + correct places in the new one. If extending the buffer results in it + being larger than MAX_BUF_SIZE, then flag memory exhausted. */ +#define EXTEND_BUFFER() \ + do { \ + unsigned char *old_buffer = bufp->buffer; \ + if (bufp->allocated == MAX_BUF_SIZE) \ + return REG_ESIZE; \ + bufp->allocated <<= 1; \ + if (bufp->allocated > MAX_BUF_SIZE) \ + bufp->allocated = MAX_BUF_SIZE; \ + bufp->buffer = (unsigned char *) REALLOC (bufp->buffer, bufp->allocated);\ + if (bufp->buffer == NULL) \ + return REG_ESPACE; \ + /* If the buffer moved, move all the pointers into it. */ \ + if (old_buffer != bufp->buffer) \ + { \ + b = (b - old_buffer) + bufp->buffer; \ + begalt = (begalt - old_buffer) + bufp->buffer; \ + if (fixup_alt_jump) \ + fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer;\ + if (laststart) \ + laststart = (laststart - old_buffer) + bufp->buffer; \ + if (pending_exact) \ + pending_exact = (pending_exact - old_buffer) + bufp->buffer; \ + } \ + } while (0) + + +/* Since we have one byte reserved for the register number argument to + {start,stop}_memory, the maximum number of groups we can report + things about is what fits in that byte. */ +#define MAX_REGNUM 255 + +/* But patterns can have more than `MAX_REGNUM' registers. We just + ignore the excess. */ +typedef unsigned regnum_t; + + +/* Macros for the compile stack. */ + +/* Since offsets can go either forwards or backwards, this type needs to + be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. */ +/* int may be not enough when sizeof(int) == 2. */ +typedef long pattern_offset_t; + +typedef struct +{ + pattern_offset_t begalt_offset; + pattern_offset_t fixup_alt_jump; + pattern_offset_t inner_group_offset; + pattern_offset_t laststart_offset; + regnum_t regnum; +} compile_stack_elt_t; + + +typedef struct +{ + compile_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} compile_stack_type; + + +#define INIT_COMPILE_STACK_SIZE 32 + +#define COMPILE_STACK_EMPTY (compile_stack.avail == 0) +#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size) + +/* The next available element. */ +#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail]) + + +/* Set the bit for character C in a list. */ +#define SET_LIST_BIT(c) \ + (b[((unsigned char) (c)) / BYTEWIDTH] \ + |= 1 << (((unsigned char) c) % BYTEWIDTH)) + + +/* Get the next unsigned number in the uncompiled pattern. */ +#define GET_UNSIGNED_NUMBER(num) \ + { if (p != pend) \ + { \ + PATFETCH (c); \ + while (ISDIGIT (c)) \ + { \ + if (num < 0) \ + num = 0; \ + num = num * 10 + c - '0'; \ + if (p == pend) \ + break; \ + PATFETCH (c); \ + } \ + } \ + } + +#if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) +/* The GNU C library provides support for user-defined character classes + and the functions from ISO C amendement 1. */ +# ifdef CHARCLASS_NAME_MAX +# define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX +# else +/* This shouldn't happen but some implementation might still have this + problem. Use a reasonable default value. */ +# define CHAR_CLASS_MAX_LENGTH 256 +# endif + +# ifdef _LIBC +# define IS_CHAR_CLASS(string) __wctype (string) +# else +# define IS_CHAR_CLASS(string) wctype (string) +# endif +#else +# define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ + +# define IS_CHAR_CLASS(string) \ + (STREQ (string, "alpha") || STREQ (string, "upper") \ + || STREQ (string, "lower") || STREQ (string, "digit") \ + || STREQ (string, "alnum") || STREQ (string, "xdigit") \ + || STREQ (string, "space") || STREQ (string, "print") \ + || STREQ (string, "punct") || STREQ (string, "graph") \ + || STREQ (string, "cntrl") || STREQ (string, "blank")) +#endif + +#ifndef MATCH_MAY_ALLOCATE + +/* If we cannot allocate large objects within re_match_2_internal, + we make the fail stack and register vectors global. + The fail stack, we grow to the maximum size when a regexp + is compiled. + The register vectors, we adjust in size each time we + compile a regexp, according to the number of registers it needs. */ + +static fail_stack_type fail_stack; + +/* Size with which the following vectors are currently allocated. + That is so we can make them bigger as needed, + but never make them smaller. */ +static int regs_allocated_size; + +static const char ** regstart, ** regend; +static const char ** old_regstart, ** old_regend; +static const char **best_regstart, **best_regend; +static register_info_type *reg_info; +static const char **reg_dummy; +static register_info_type *reg_info_dummy; + +/* Make the register vectors big enough for NUM_REGS registers, + but don't make them smaller. */ + +static +regex_grow_registers (num_regs) + int num_regs; +{ + if (num_regs > regs_allocated_size) + { + RETALLOC_IF (regstart, num_regs, const char *); + RETALLOC_IF (regend, num_regs, const char *); + RETALLOC_IF (old_regstart, num_regs, const char *); + RETALLOC_IF (old_regend, num_regs, const char *); + RETALLOC_IF (best_regstart, num_regs, const char *); + RETALLOC_IF (best_regend, num_regs, const char *); + RETALLOC_IF (reg_info, num_regs, register_info_type); + RETALLOC_IF (reg_dummy, num_regs, const char *); + RETALLOC_IF (reg_info_dummy, num_regs, register_info_type); + + regs_allocated_size = num_regs; + } +} + +#endif /* not MATCH_MAY_ALLOCATE */ + +static boolean group_in_compile_stack _RE_ARGS ((compile_stack_type + compile_stack, + regnum_t regnum)); + +/* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX. + Returns one of error codes defined in `regex.h', or zero for success. + + Assumes the `allocated' (and perhaps `buffer') and `translate' + fields are set in BUFP on entry. + + If it succeeds, results are put in BUFP (if it returns an error, the + contents of BUFP are undefined): + `buffer' is the compiled pattern; + `syntax' is set to SYNTAX; + `used' is set to the length of the compiled pattern; + `fastmap_accurate' is zero; + `re_nsub' is the number of subexpressions in PATTERN; + `not_bol' and `not_eol' are zero; + + The `fastmap' and `newline_anchor' fields are neither + examined nor set. */ + +/* Return, freeing storage we allocated. */ +#define FREE_STACK_RETURN(value) \ + return (free (compile_stack.stack), value) + +static reg_errcode_t +regex_compile (pattern, size, syntax, bufp) + const char *pattern; + size_t size; + reg_syntax_t syntax; + struct re_pattern_buffer *bufp; +{ + /* We fetch characters from PATTERN here. Even though PATTERN is + `char *' (i.e., signed), we declare these variables as unsigned, so + they can be reliably used as array indices. */ + register unsigned char c, c1; + + /* A random temporary spot in PATTERN. */ + const char *p1; + + /* Points to the end of the buffer, where we should append. */ + register unsigned char *b; + + /* Keeps track of unclosed groups. */ + compile_stack_type compile_stack; + + /* Points to the current (ending) position in the pattern. */ + const char *p = pattern; + const char *pend = pattern + size; + + /* How to translate the characters in the pattern. */ + RE_TRANSLATE_TYPE translate = bufp->translate; + + /* Address of the count-byte of the most recently inserted `exactn' + command. This makes it possible to tell if a new exact-match + character can be added to that command or if the character requires + a new `exactn' command. */ + unsigned char *pending_exact = 0; + + /* Address of start of the most recently finished expression. + This tells, e.g., postfix * where to find the start of its + operand. Reset at the beginning of groups and alternatives. */ + unsigned char *laststart = 0; + + /* Address of beginning of regexp, or inside of last group. */ + unsigned char *begalt; + + /* Place in the uncompiled pattern (i.e., the {) to + which to go back if the interval is invalid. */ + const char *beg_interval; + + /* Address of the place where a forward jump should go to the end of + the containing expression. Each alternative of an `or' -- except the + last -- ends with a forward jump of this sort. */ + unsigned char *fixup_alt_jump = 0; + + /* Counts open-groups as they are encountered. Remembered for the + matching close-group on the compile stack, so the same register + number is put in the stop_memory as the start_memory. */ + regnum_t regnum = 0; + +#ifdef DEBUG + DEBUG_PRINT1 ("\nCompiling pattern: "); + if (debug) + { + unsigned debug_count; + + for (debug_count = 0; debug_count < size; debug_count++) + putchar (pattern[debug_count]); + putchar ('\n'); + } +#endif /* DEBUG */ + + /* Initialize the compile stack. */ + compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t); + if (compile_stack.stack == NULL) + return REG_ESPACE; + + compile_stack.size = INIT_COMPILE_STACK_SIZE; + compile_stack.avail = 0; + + /* Initialize the pattern buffer. */ + bufp->syntax = syntax; + bufp->fastmap_accurate = 0; + bufp->not_bol = bufp->not_eol = 0; + + /* Set `used' to zero, so that if we return an error, the pattern + printer (for debugging) will think there's no pattern. We reset it + at the end. */ + bufp->used = 0; + + /* Always count groups, whether or not bufp->no_sub is set. */ + bufp->re_nsub = 0; + +#if !defined emacs && !defined SYNTAX_TABLE + /* Initialize the syntax table. */ + init_syntax_once (); +#endif + + if (bufp->allocated == 0) + { + if (bufp->buffer) + { /* If zero allocated, but buffer is non-null, try to realloc + enough space. This loses if buffer's address is bogus, but + that is the user's responsibility. */ + RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char); + } + else + { /* Caller did not allocate a buffer. Do it for them. */ + bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char); + } + if (!bufp->buffer) FREE_STACK_RETURN (REG_ESPACE); + + bufp->allocated = INIT_BUF_SIZE; + } + + begalt = b = bufp->buffer; + + /* Loop through the uncompiled pattern until we're at the end. */ + while (p != pend) + { + PATFETCH (c); + + switch (c) + { + case '^': + { + if ( /* If at start of pattern, it's an operator. */ + p == pattern + 1 + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's come before. */ + || at_begline_loc_p (pattern, p, syntax)) + BUF_PUSH (begline); + else + goto normal_char; + } + break; + + + case '$': + { + if ( /* If at end of pattern, it's an operator. */ + p == pend + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's next. */ + || at_endline_loc_p (p, pend, syntax)) + BUF_PUSH (endline); + else + goto normal_char; + } + break; + + + case '+': + case '?': + if ((syntax & RE_BK_PLUS_QM) + || (syntax & RE_LIMITED_OPS)) + goto normal_char; + handle_plus: + case '*': + /* If there is no previous pattern... */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + FREE_STACK_RETURN (REG_BADRPT); + else if (!(syntax & RE_CONTEXT_INDEP_OPS)) + goto normal_char; + } + + { + /* Are we optimizing this jump? */ + boolean keep_string_p = false; + + /* 1 means zero (many) matches is allowed. */ + char zero_times_ok = 0, many_times_ok = 0; + + /* If there is a sequence of repetition chars, collapse it + down to just one (the right one). We can't combine + interval operators with these because of, e.g., `a{2}*', + which should only match an even number of `a's. */ + + for (;;) + { + zero_times_ok |= c != '+'; + many_times_ok |= c != '?'; + + if (p == pend) + break; + + PATFETCH (c); + + if (c == '*' + || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?'))) + ; + + else if (syntax & RE_BK_PLUS_QM && c == '\\') + { + if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); + + PATFETCH (c1); + if (!(c1 == '+' || c1 == '?')) + { + PATUNFETCH; + PATUNFETCH; + break; + } + + c = c1; + } + else + { + PATUNFETCH; + break; + } + + /* If we get here, we found another repeat character. */ + } + + /* Star, etc. applied to an empty pattern is equivalent + to an empty pattern. */ + if (!laststart) + break; + + /* Now we know whether or not zero matches is allowed + and also whether or not two or more matches is allowed. */ + if (many_times_ok) + { /* More than one repetition is allowed, so put in at the + end a backward relative jump from `b' to before the next + jump we're going to put in below (which jumps from + laststart to after this jump). + + But if we are at the `*' in the exact sequence `.*\n', + insert an unconditional jump backwards to the ., + instead of the beginning of the loop. This way we only + push a failure point once, instead of every time + through the loop. */ + assert (p - 1 > pattern); + + /* Allocate the space for the jump. */ + GET_BUFFER_SPACE (3); + + /* We know we are not at the first character of the pattern, + because laststart was nonzero. And we've already + incremented `p', by the way, to be the character after + the `*'. Do we have to do something analogous here + for null bytes, because of RE_DOT_NOT_NULL? */ + if (TRANSLATE (*(p - 2)) == TRANSLATE ('.') + && zero_times_ok + && p < pend && TRANSLATE (*p) == TRANSLATE ('\n') + && !(syntax & RE_DOT_NEWLINE)) + { /* We have .*\n. */ + STORE_JUMP (jump, b, laststart); + keep_string_p = true; + } + else + /* Anything else. */ + STORE_JUMP (maybe_pop_jump, b, laststart - 3); + + /* We've added more stuff to the buffer. */ + b += 3; + } + + /* On failure, jump from laststart to b + 3, which will be the + end of the buffer after this jump is inserted. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump + : on_failure_jump, + laststart, b + 3); + pending_exact = 0; + b += 3; + + if (!zero_times_ok) + { + /* At least one repetition is required, so insert a + `dummy_failure_jump' before the initial + `on_failure_jump' instruction of the loop. This + effects a skip over that instruction the first time + we hit that loop. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (dummy_failure_jump, laststart, laststart + 6); + b += 3; + } + } + break; + + + case '.': + laststart = b; + BUF_PUSH (anychar); + break; + + + case '[': + { + boolean had_char_class = false; + + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + /* Ensure that we have enough space to push a charset: the + opcode, the length count, and the bitset; 34 bytes in all. */ + GET_BUFFER_SPACE (34); + + laststart = b; + + /* We test `*p == '^' twice, instead of using an if + statement, so we only need one BUF_PUSH. */ + BUF_PUSH (*p == '^' ? charset_not : charset); + if (*p == '^') + p++; + + /* Remember the first position in the bracket expression. */ + p1 = p; + + /* Push the number of bytes in the bitmap. */ + BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH); + + /* Clear the whole map. */ + bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH); + + /* charset_not matches newline according to a syntax bit. */ + if ((re_opcode_t) b[-2] == charset_not + && (syntax & RE_HAT_LISTS_NOT_NEWLINE)) + SET_LIST_BIT ('\n'); + + /* Read in characters and ranges, setting map bits. */ + for (;;) + { + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + PATFETCH (c); + + /* \ might escape characters inside [...] and [^...]. */ + if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\') + { + if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); + + PATFETCH (c1); + SET_LIST_BIT (c1); + continue; + } + + /* Could be the end of the bracket expression. If it's + not (i.e., when the bracket expression is `[]' so + far), the ']' character bit gets set way below. */ + if (c == ']' && p != p1 + 1) + break; + + /* Look ahead to see if it's a range when the last thing + was a character class. */ + if (had_char_class && c == '-' && *p != ']') + FREE_STACK_RETURN (REG_ERANGE); + + /* Look ahead to see if it's a range when the last thing + was a character: if this is a hyphen not at the + beginning or the end of a list, then it's the range + operator. */ + if (c == '-' + && !(p - 2 >= pattern && p[-2] == '[') + && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^') + && *p != ']') + { + reg_errcode_t ret + = compile_range (&p, pend, translate, syntax, b); + if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); + } + + else if (p[0] == '-' && p[1] != ']') + { /* This handles ranges made up of characters only. */ + reg_errcode_t ret; + + /* Move past the `-'. */ + PATFETCH (c1); + + ret = compile_range (&p, pend, translate, syntax, b); + if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); + } + + /* See if we're at the beginning of a possible character + class. */ + + else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':') + { /* Leave room for the null. */ + char str[CHAR_CLASS_MAX_LENGTH + 1]; + + PATFETCH (c); + c1 = 0; + + /* If pattern is `[[:'. */ + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + for (;;) + { + PATFETCH (c); + if ((c == ':' && *p == ']') || p == pend + || c1 == CHAR_CLASS_MAX_LENGTH) + break; + str[c1++] = c; + } + str[c1] = '\0'; + + /* If isn't a word bracketed by `[:' and `:]': + undo the ending character, the letters, and leave + the leading `:' and `[' (but set bits for them). */ + if (c == ':' && *p == ']') + { +#if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) + boolean is_lower = STREQ (str, "lower"); + boolean is_upper = STREQ (str, "upper"); + wctype_t wt; + int ch; + + wt = IS_CHAR_CLASS (str); + if (wt == 0) + FREE_STACK_RETURN (REG_ECTYPE); + + /* Throw away the ] at the end of the character + class. */ + PATFETCH (c); + + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + for (ch = 0; ch < 1 << BYTEWIDTH; ++ch) + { +# ifdef _LIBC + if (__iswctype (__btowc (ch), wt)) + SET_LIST_BIT (ch); +#else + if (iswctype (btowc (ch), wt)) + SET_LIST_BIT (ch); +#endif + + if (translate && (is_upper || is_lower) + && (ISUPPER (ch) || ISLOWER (ch))) + SET_LIST_BIT (ch); + } + + had_char_class = true; +#else + int ch; + boolean is_alnum = STREQ (str, "alnum"); + boolean is_alpha = STREQ (str, "alpha"); + boolean is_blank = STREQ (str, "blank"); + boolean is_cntrl = STREQ (str, "cntrl"); + boolean is_digit = STREQ (str, "digit"); + boolean is_graph = STREQ (str, "graph"); + boolean is_lower = STREQ (str, "lower"); + boolean is_print = STREQ (str, "print"); + boolean is_punct = STREQ (str, "punct"); + boolean is_space = STREQ (str, "space"); + boolean is_upper = STREQ (str, "upper"); + boolean is_xdigit = STREQ (str, "xdigit"); + + if (!IS_CHAR_CLASS (str)) + FREE_STACK_RETURN (REG_ECTYPE); + + /* Throw away the ] at the end of the character + class. */ + PATFETCH (c); + + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + for (ch = 0; ch < 1 << BYTEWIDTH; ch++) + { + /* This was split into 3 if's to + avoid an arbitrary limit in some compiler. */ + if ( (is_alnum && ISALNUM (ch)) + || (is_alpha && ISALPHA (ch)) + || (is_blank && ISBLANK (ch)) + || (is_cntrl && ISCNTRL (ch))) + SET_LIST_BIT (ch); + if ( (is_digit && ISDIGIT (ch)) + || (is_graph && ISGRAPH (ch)) + || (is_lower && ISLOWER (ch)) + || (is_print && ISPRINT (ch))) + SET_LIST_BIT (ch); + if ( (is_punct && ISPUNCT (ch)) + || (is_space && ISSPACE (ch)) + || (is_upper && ISUPPER (ch)) + || (is_xdigit && ISXDIGIT (ch))) + SET_LIST_BIT (ch); + if ( translate && (is_upper || is_lower) + && (ISUPPER (ch) || ISLOWER (ch))) + SET_LIST_BIT (ch); + } + had_char_class = true; +#endif /* libc || wctype.h */ + } + else + { + c1++; + while (c1--) + PATUNFETCH; + SET_LIST_BIT ('['); + SET_LIST_BIT (':'); + had_char_class = false; + } + } + else + { + had_char_class = false; + SET_LIST_BIT (c); + } + } + + /* Discard any (non)matching list bytes that are all 0 at the + end of the map. Decrease the map-length byte too. */ + while ((int) b[-1] > 0 && b[b[-1] - 1] == 0) + b[-1]--; + b += b[-1]; + } + break; + + + case '(': + if (syntax & RE_NO_BK_PARENS) + goto handle_open; + else + goto normal_char; + + + case ')': + if (syntax & RE_NO_BK_PARENS) + goto handle_close; + else + goto normal_char; + + + case '\n': + if (syntax & RE_NEWLINE_ALT) + goto handle_alt; + else + goto normal_char; + + + case '|': + if (syntax & RE_NO_BK_VBAR) + goto handle_alt; + else + goto normal_char; + + + case '{': + if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES) + goto handle_interval; + else + goto normal_char; + + + case '\\': + if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); + + /* Do not translate the character after the \, so that we can + distinguish, e.g., \B from \b, even if we normally would + translate, e.g., B to b. */ + PATFETCH_RAW (c); + + switch (c) + { + case '(': + if (syntax & RE_NO_BK_PARENS) + goto normal_backslash; + + handle_open: + bufp->re_nsub++; + regnum++; + + if (COMPILE_STACK_FULL) + { + RETALLOC (compile_stack.stack, compile_stack.size << 1, + compile_stack_elt_t); + if (compile_stack.stack == NULL) return REG_ESPACE; + + compile_stack.size <<= 1; + } + + /* These are the values to restore when we hit end of this + group. They are all relative offsets, so that if the + whole pattern moves because of realloc, they will still + be valid. */ + COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer; + COMPILE_STACK_TOP.fixup_alt_jump + = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0; + COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer; + COMPILE_STACK_TOP.regnum = regnum; + + /* We will eventually replace the 0 with the number of + groups inner to this one. But do not push a + start_memory for groups beyond the last one we can + represent in the compiled pattern. */ + if (regnum <= MAX_REGNUM) + { + COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2; + BUF_PUSH_3 (start_memory, regnum, 0); + } + + compile_stack.avail++; + + fixup_alt_jump = 0; + laststart = 0; + begalt = b; + /* If we've reached MAX_REGNUM groups, then this open + won't actually generate any code, so we'll have to + clear pending_exact explicitly. */ + pending_exact = 0; + break; + + + case ')': + if (syntax & RE_NO_BK_PARENS) goto normal_backslash; + + if (COMPILE_STACK_EMPTY) + { + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_backslash; + else + FREE_STACK_RETURN (REG_ERPAREN); + } + + handle_close: + if (fixup_alt_jump) + { /* Push a dummy failure point at the end of the + alternative for a possible future + `pop_failure_jump' to pop. See comments at + `push_dummy_failure' in `re_match_2'. */ + BUF_PUSH (push_dummy_failure); + + /* We allocated space for this jump when we assigned + to `fixup_alt_jump', in the `handle_alt' case below. */ + STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1); + } + + /* See similar code for backslashed left paren above. */ + if (COMPILE_STACK_EMPTY) + { + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_char; + else + FREE_STACK_RETURN (REG_ERPAREN); + } + + /* Since we just checked for an empty stack above, this + ``can't happen''. */ + assert (compile_stack.avail != 0); + { + /* We don't just want to restore into `regnum', because + later groups should continue to be numbered higher, + as in `(ab)c(de)' -- the second group is #2. */ + regnum_t this_group_regnum; + + compile_stack.avail--; + begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset; + fixup_alt_jump + = COMPILE_STACK_TOP.fixup_alt_jump + ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1 + : 0; + laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset; + this_group_regnum = COMPILE_STACK_TOP.regnum; + /* If we've reached MAX_REGNUM groups, then this open + won't actually generate any code, so we'll have to + clear pending_exact explicitly. */ + pending_exact = 0; + + /* We're at the end of the group, so now we know how many + groups were inside this one. */ + if (this_group_regnum <= MAX_REGNUM) + { + unsigned char *inner_group_loc + = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset; + + *inner_group_loc = regnum - this_group_regnum; + BUF_PUSH_3 (stop_memory, this_group_regnum, + regnum - this_group_regnum); + } + } + break; + + + case '|': /* `\|'. */ + if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR) + goto normal_backslash; + handle_alt: + if (syntax & RE_LIMITED_OPS) + goto normal_char; + + /* Insert before the previous alternative a jump which + jumps to this alternative if the former fails. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (on_failure_jump, begalt, b + 6); + pending_exact = 0; + b += 3; + + /* The alternative before this one has a jump after it + which gets executed if it gets matched. Adjust that + jump so it will jump to this alternative's analogous + jump (put in below, which in turn will jump to the next + (if any) alternative's such jump, etc.). The last such + jump jumps to the correct final destination. A picture: + _____ _____ + | | | | + | v | v + a | b | c + + If we are at `b', then fixup_alt_jump right now points to a + three-byte space after `a'. We'll put in the jump, set + fixup_alt_jump to right after `b', and leave behind three + bytes which we'll fill in when we get to after `c'. */ + + if (fixup_alt_jump) + STORE_JUMP (jump_past_alt, fixup_alt_jump, b); + + /* Mark and leave space for a jump after this alternative, + to be filled in later either by next alternative or + when know we're at the end of a series of alternatives. */ + fixup_alt_jump = b; + GET_BUFFER_SPACE (3); + b += 3; + + laststart = 0; + begalt = b; + break; + + + case '{': + /* If \{ is a literal. */ + if (!(syntax & RE_INTERVALS) + /* If we're at `\{' and it's not the open-interval + operator. */ + || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + || (p - 2 == pattern && p == pend)) + goto normal_backslash; + + handle_interval: + { + /* If got here, then the syntax allows intervals. */ + + /* At least (most) this many matches must be made. */ + int lower_bound = -1, upper_bound = -1; + + beg_interval = p - 1; + + if (p == pend) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + FREE_STACK_RETURN (REG_EBRACE); + } + + GET_UNSIGNED_NUMBER (lower_bound); + + if (c == ',') + { + GET_UNSIGNED_NUMBER (upper_bound); + if (upper_bound < 0) upper_bound = RE_DUP_MAX; + } + else + /* Interval such as `{1}' => match exactly once. */ + upper_bound = lower_bound; + + if (lower_bound < 0 || upper_bound > RE_DUP_MAX + || lower_bound > upper_bound) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + FREE_STACK_RETURN (REG_BADBR); + } + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (c != '\\') FREE_STACK_RETURN (REG_EBRACE); + + PATFETCH (c); + } + + if (c != '}') + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + FREE_STACK_RETURN (REG_BADBR); + } + + /* We just parsed a valid interval. */ + + /* If it's invalid to have no preceding re. */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + FREE_STACK_RETURN (REG_BADRPT); + else if (syntax & RE_CONTEXT_INDEP_OPS) + laststart = b; + else + goto unfetch_interval; + } + + /* If the upper bound is zero, don't want to succeed at + all; jump from `laststart' to `b + 3', which will be + the end of the buffer after we insert the jump. */ + if (upper_bound == 0) + { + GET_BUFFER_SPACE (3); + INSERT_JUMP (jump, laststart, b + 3); + b += 3; + } + + /* Otherwise, we have a nontrivial interval. When + we're all done, the pattern will look like: + set_number_at + set_number_at + succeed_n + + jump_n + (The upper bound and `jump_n' are omitted if + `upper_bound' is 1, though.) */ + else + { /* If the upper bound is > 1, we need to insert + more at the end of the loop. */ + unsigned nbytes = 10 + (upper_bound > 1) * 10; + + GET_BUFFER_SPACE (nbytes); + + /* Initialize lower bound of the `succeed_n', even + though it will be set during matching by its + attendant `set_number_at' (inserted next), + because `re_compile_fastmap' needs to know. + Jump to the `jump_n' we might insert below. */ + INSERT_JUMP2 (succeed_n, laststart, + b + 5 + (upper_bound > 1) * 5, + lower_bound); + b += 5; + + /* Code to initialize the lower bound. Insert + before the `succeed_n'. The `5' is the last two + bytes of this `set_number_at', plus 3 bytes of + the following `succeed_n'. */ + insert_op2 (set_number_at, laststart, 5, lower_bound, b); + b += 5; + + if (upper_bound > 1) + { /* More than one repetition is allowed, so + append a backward jump to the `succeed_n' + that starts this interval. + + When we've reached this during matching, + we'll have matched the interval once, so + jump back only `upper_bound - 1' times. */ + STORE_JUMP2 (jump_n, b, laststart + 5, + upper_bound - 1); + b += 5; + + /* The location we want to set is the second + parameter of the `jump_n'; that is `b-2' as + an absolute address. `laststart' will be + the `set_number_at' we're about to insert; + `laststart+3' the number to set, the source + for the relative address. But we are + inserting into the middle of the pattern -- + so everything is getting moved up by 5. + Conclusion: (b - 2) - (laststart + 3) + 5, + i.e., b - laststart. + + We insert this at the beginning of the loop + so that if we fail during matching, we'll + reinitialize the bounds. */ + insert_op2 (set_number_at, laststart, b - laststart, + upper_bound - 1, b); + b += 5; + } + } + pending_exact = 0; + beg_interval = NULL; + } + break; + + unfetch_interval: + /* If an invalid interval, match the characters as literals. */ + assert (beg_interval); + p = beg_interval; + beg_interval = NULL; + + /* normal_char and normal_backslash need `c'. */ + PATFETCH (c); + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (p > pattern && p[-1] == '\\') + goto normal_backslash; + } + goto normal_char; + +#ifdef emacs + /* There is no way to specify the before_dot and after_dot + operators. rms says this is ok. --karl */ + case '=': + BUF_PUSH (at_dot); + break; + + case 's': + laststart = b; + PATFETCH (c); + BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]); + break; + + case 'S': + laststart = b; + PATFETCH (c); + BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]); + break; +#endif /* emacs */ + + + case 'w': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + laststart = b; + BUF_PUSH (wordchar); + break; + + + case 'W': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + laststart = b; + BUF_PUSH (notwordchar); + break; + + + case '<': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (wordbeg); + break; + + case '>': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (wordend); + break; + + case 'b': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (wordbound); + break; + + case 'B': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (notwordbound); + break; + + case '`': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (begbuf); + break; + + case '\'': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (endbuf); + break; + + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + if (syntax & RE_NO_BK_REFS) + goto normal_char; + + c1 = c - '0'; + + if (c1 > regnum) + FREE_STACK_RETURN (REG_ESUBREG); + + /* Can't back reference to a subexpression if inside of it. */ + if (group_in_compile_stack (compile_stack, (regnum_t) c1)) + goto normal_char; + + laststart = b; + BUF_PUSH_2 (duplicate, c1); + break; + + + case '+': + case '?': + if (syntax & RE_BK_PLUS_QM) + goto handle_plus; + else + goto normal_backslash; + + default: + normal_backslash: + /* You might think it would be useful for \ to mean + not to translate; but if we don't translate it + it will never match anything. */ + c = TRANSLATE (c); + goto normal_char; + } + break; + + + default: + /* Expects the character in `c'. */ + normal_char: + /* If no exactn currently being built. */ + if (!pending_exact + + /* If last exactn not at current position. */ + || pending_exact + *pending_exact + 1 != b + + /* We have only one byte following the exactn for the count. */ + || *pending_exact == (1 << BYTEWIDTH) - 1 + + /* If followed by a repetition operator. */ + || *p == '*' || *p == '^' + || ((syntax & RE_BK_PLUS_QM) + ? *p == '\\' && (p[1] == '+' || p[1] == '?') + : (*p == '+' || *p == '?')) + || ((syntax & RE_INTERVALS) + && ((syntax & RE_NO_BK_BRACES) + ? *p == '{' + : (p[0] == '\\' && p[1] == '{')))) + { + /* Start building a new exactn. */ + + laststart = b; + + BUF_PUSH_2 (exactn, 0); + pending_exact = b - 1; + } + + BUF_PUSH (c); + (*pending_exact)++; + break; + } /* switch (c) */ + } /* while p != pend */ + + + /* Through the pattern now. */ + + if (fixup_alt_jump) + STORE_JUMP (jump_past_alt, fixup_alt_jump, b); + + if (!COMPILE_STACK_EMPTY) + FREE_STACK_RETURN (REG_EPAREN); + + /* If we don't want backtracking, force success + the first time we reach the end of the compiled pattern. */ + if (syntax & RE_NO_POSIX_BACKTRACKING) + BUF_PUSH (succeed); + + free (compile_stack.stack); + + /* We have succeeded; set the length of the buffer. */ + bufp->used = b - bufp->buffer; + +#ifdef DEBUG + if (debug) + { + DEBUG_PRINT1 ("\nCompiled pattern: \n"); + print_compiled_pattern (bufp); + } +#endif /* DEBUG */ + +#ifndef MATCH_MAY_ALLOCATE + /* Initialize the failure stack to the largest possible stack. This + isn't necessary unless we're trying to avoid calling alloca in + the search and match routines. */ + { + int num_regs = bufp->re_nsub + 1; + + /* Since DOUBLE_FAIL_STACK refuses to double only if the current size + is strictly greater than re_max_failures, the largest possible stack + is 2 * re_max_failures failure points. */ + if (fail_stack.size < (2 * re_max_failures * MAX_FAILURE_ITEMS)) + { + fail_stack.size = (2 * re_max_failures * MAX_FAILURE_ITEMS); + +# ifdef emacs + if (! fail_stack.stack) + fail_stack.stack + = (fail_stack_elt_t *) xmalloc (fail_stack.size + * sizeof (fail_stack_elt_t)); + else + fail_stack.stack + = (fail_stack_elt_t *) xrealloc (fail_stack.stack, + (fail_stack.size + * sizeof (fail_stack_elt_t))); +# else /* not emacs */ + if (! fail_stack.stack) + fail_stack.stack + = (fail_stack_elt_t *) malloc (fail_stack.size + * sizeof (fail_stack_elt_t)); + else + fail_stack.stack + = (fail_stack_elt_t *) realloc (fail_stack.stack, + (fail_stack.size + * sizeof (fail_stack_elt_t))); +# endif /* not emacs */ + } + + regex_grow_registers (num_regs); + } +#endif /* not MATCH_MAY_ALLOCATE */ + + return REG_NOERROR; +} /* regex_compile */ + +/* Subroutines for `regex_compile'. */ + +/* Store OP at LOC followed by two-byte integer parameter ARG. */ + +static void +store_op1 (op, loc, arg) + re_opcode_t op; + unsigned char *loc; + int arg; +{ + *loc = (unsigned char) op; + STORE_NUMBER (loc + 1, arg); +} + + +/* Like `store_op1', but for two two-byte parameters ARG1 and ARG2. */ + +static void +store_op2 (op, loc, arg1, arg2) + re_opcode_t op; + unsigned char *loc; + int arg1, arg2; +{ + *loc = (unsigned char) op; + STORE_NUMBER (loc + 1, arg1); + STORE_NUMBER (loc + 3, arg2); +} + + +/* Copy the bytes from LOC to END to open up three bytes of space at LOC + for OP followed by two-byte integer parameter ARG. */ + +static void +insert_op1 (op, loc, arg, end) + re_opcode_t op; + unsigned char *loc; + int arg; + unsigned char *end; +{ + register unsigned char *pfrom = end; + register unsigned char *pto = end + 3; + + while (pfrom != loc) + *--pto = *--pfrom; + + store_op1 (op, loc, arg); +} + + +/* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2. */ + +static void +insert_op2 (op, loc, arg1, arg2, end) + re_opcode_t op; + unsigned char *loc; + int arg1, arg2; + unsigned char *end; +{ + register unsigned char *pfrom = end; + register unsigned char *pto = end + 5; + + while (pfrom != loc) + *--pto = *--pfrom; + + store_op2 (op, loc, arg1, arg2); +} + + +/* P points to just after a ^ in PATTERN. Return true if that ^ comes + after an alternative or a begin-subexpression. We assume there is at + least one character before the ^. */ + +static boolean +at_begline_loc_p (pattern, p, syntax) + const char *pattern, *p; + reg_syntax_t syntax; +{ + const char *prev = p - 2; + boolean prev_prev_backslash = prev > pattern && prev[-1] == '\\'; + + return + /* After a subexpression? */ + (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash)) + /* After an alternative? */ + || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash)); +} + + +/* The dual of at_begline_loc_p. This one is for $. We assume there is + at least one character after the $, i.e., `P < PEND'. */ + +static boolean +at_endline_loc_p (p, pend, syntax) + const char *p, *pend; + reg_syntax_t syntax; +{ + const char *next = p; + boolean next_backslash = *next == '\\'; + const char *next_next = p + 1 < pend ? p + 1 : 0; + + return + /* Before a subexpression? */ + (syntax & RE_NO_BK_PARENS ? *next == ')' + : next_backslash && next_next && *next_next == ')') + /* Before an alternative? */ + || (syntax & RE_NO_BK_VBAR ? *next == '|' + : next_backslash && next_next && *next_next == '|'); +} + + +/* Returns true if REGNUM is in one of COMPILE_STACK's elements and + false if it's not. */ + +static boolean +group_in_compile_stack (compile_stack, regnum) + compile_stack_type compile_stack; + regnum_t regnum; +{ + int this_element; + + for (this_element = compile_stack.avail - 1; + this_element >= 0; + this_element--) + if (compile_stack.stack[this_element].regnum == regnum) + return true; + + return false; +} + + +/* Read the ending character of a range (in a bracket expression) from the + uncompiled pattern *P_PTR (which ends at PEND). We assume the + starting character is in `P[-2]'. (`P[-1]' is the character `-'.) + Then we set the translation of all bits between the starting and + ending characters (inclusive) in the compiled pattern B. + + Return an error code. + + We use these short variable names so we can use the same macros as + `regex_compile' itself. */ + +static reg_errcode_t +compile_range (p_ptr, pend, translate, syntax, b) + const char **p_ptr, *pend; + RE_TRANSLATE_TYPE translate; + reg_syntax_t syntax; + unsigned char *b; +{ + unsigned this_char; + + const char *p = *p_ptr; + unsigned int range_start, range_end; + + if (p == pend) + return REG_ERANGE; + + /* Even though the pattern is a signed `char *', we need to fetch + with unsigned char *'s; if the high bit of the pattern character + is set, the range endpoints will be negative if we fetch using a + signed char *. + + We also want to fetch the endpoints without translating them; the + appropriate translation is done in the bit-setting loop below. */ + /* The SVR4 compiler on the 3B2 had trouble with unsigned const char *. */ + range_start = ((const unsigned char *) p)[-2]; + range_end = ((const unsigned char *) p)[0]; + + /* Have to increment the pointer into the pattern string, so the + caller isn't still at the ending character. */ + (*p_ptr)++; + + /* If the start is after the end, the range is empty. */ + if (range_start > range_end) + return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR; + + /* Here we see why `this_char' has to be larger than an `unsigned + char' -- the range is inclusive, so if `range_end' == 0xff + (assuming 8-bit characters), we would otherwise go into an infinite + loop, since all characters <= 0xff. */ + for (this_char = range_start; this_char <= range_end; this_char++) + { + SET_LIST_BIT (TRANSLATE (this_char)); + } + + return REG_NOERROR; +} + +/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in + BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible + characters can start a string that matches the pattern. This fastmap + is used by re_search to skip quickly over impossible starting points. + + The caller must supply the address of a (1 << BYTEWIDTH)-byte data + area as BUFP->fastmap. + + We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in + the pattern buffer. + + Returns 0 if we succeed, -2 if an internal error. */ + +_EXPORT int +re_compile_fastmap (bufp) + struct re_pattern_buffer *bufp; +{ + int j, k; +#ifdef MATCH_MAY_ALLOCATE + fail_stack_type fail_stack; +#endif +#ifndef REGEX_MALLOC + char *destination; +#endif + + register char *fastmap = bufp->fastmap; + unsigned char *pattern = bufp->buffer; + unsigned char *p = pattern; + register unsigned char *pend = pattern + bufp->used; + +#ifdef REL_ALLOC + /* This holds the pointer to the failure stack, when + it is allocated relocatably. */ + fail_stack_elt_t *failure_stack_ptr; +#endif + + /* Assume that each path through the pattern can be null until + proven otherwise. We set this false at the bottom of switch + statement, to which we get only if a particular path doesn't + match the empty string. */ + boolean path_can_be_null = true; + + /* We aren't doing a `succeed_n' to begin with. */ + boolean succeed_n_p = false; + + assert (fastmap != NULL && p != NULL); + + INIT_FAIL_STACK (); + bzero (fastmap, 1 << BYTEWIDTH); /* Assume nothing's valid. */ + bufp->fastmap_accurate = 1; /* It will be when we're done. */ + bufp->can_be_null = 0; + + while (1) + { + if (p == pend || *p == succeed) + { + /* We have reached the (effective) end of pattern. */ + if (!FAIL_STACK_EMPTY ()) + { + bufp->can_be_null |= path_can_be_null; + + /* Reset for next path. */ + path_can_be_null = true; + + p = fail_stack.stack[--fail_stack.avail].pointer; + + continue; + } + else + break; + } + + /* We should never be about to go beyond the end of the pattern. */ + assert (p < pend); + + switch (SWITCH_ENUM_CAST ((re_opcode_t) *p++)) + { + + /* I guess the idea here is to simply not bother with a fastmap + if a backreference is used, since it's too hard to figure out + the fastmap for the corresponding group. Setting + `can_be_null' stops `re_search_2' from using the fastmap, so + that is all we do. */ + case duplicate: + bufp->can_be_null = 1; + goto done; + + + /* Following are the cases which match a character. These end + with `break'. */ + + case exactn: + fastmap[p[1]] = 1; + break; + + + case charset: + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))) + fastmap[j] = 1; + break; + + + case charset_not: + /* Chars beyond end of map must be allowed. */ + for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++) + fastmap[j] = 1; + + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))) + fastmap[j] = 1; + break; + + + case wordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == Sword) + fastmap[j] = 1; + break; + + + case notwordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != Sword) + fastmap[j] = 1; + break; + + + case anychar: + { + int fastmap_newline = fastmap['\n']; + + /* `.' matches anything ... */ + for (j = 0; j < (1 << BYTEWIDTH); j++) + fastmap[j] = 1; + + /* ... except perhaps newline. */ + if (!(bufp->syntax & RE_DOT_NEWLINE)) + fastmap['\n'] = fastmap_newline; + + /* Return if we have already set `can_be_null'; if we have, + then the fastmap is irrelevant. Something's wrong here. */ + else if (bufp->can_be_null) + goto done; + + /* Otherwise, have to check alternative paths. */ + break; + } + +#ifdef emacs + case syntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == (enum syntaxcode) k) + fastmap[j] = 1; + break; + + + case notsyntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != (enum syntaxcode) k) + fastmap[j] = 1; + break; + + + /* All cases after this match the empty string. These end with + `continue'. */ + + + case before_dot: + case at_dot: + case after_dot: + continue; +#endif /* emacs */ + + + case no_op: + case begline: + case endline: + case begbuf: + case endbuf: + case wordbound: + case notwordbound: + case wordbeg: + case wordend: + case push_dummy_failure: + continue; + + + case jump_n: + case pop_failure_jump: + case maybe_pop_jump: + case jump: + case jump_past_alt: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + if (j > 0) + continue; + + /* Jump backward implies we just went through the body of a + loop and matched nothing. Opcode jumped to should be + `on_failure_jump' or `succeed_n'. Just treat it like an + ordinary jump. For a * loop, it has pushed its failure + point already; if so, discard that as redundant. */ + if ((re_opcode_t) *p != on_failure_jump + && (re_opcode_t) *p != succeed_n) + continue; + + p++; + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + + /* If what's on the stack is where we are now, pop it. */ + if (!FAIL_STACK_EMPTY () + && fail_stack.stack[fail_stack.avail - 1].pointer == p) + fail_stack.avail--; + + continue; + + + case on_failure_jump: + case on_failure_keep_string_jump: + handle_on_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + + /* For some patterns, e.g., `(a?)?', `p+j' here points to the + end of the pattern. We don't want to push such a point, + since when we restore it above, entering the switch will + increment `p' past the end of the pattern. We don't need + to push such a point since we obviously won't find any more + fastmap entries beyond `pend'. Such a pattern can match + the null string, though. */ + if (p + j < pend) + { + if (!PUSH_PATTERN_OP (p + j, fail_stack)) + { + RESET_FAIL_STACK (); + return -2; + } + } + else + bufp->can_be_null = 1; + + if (succeed_n_p) + { + EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */ + succeed_n_p = false; + } + + continue; + + + case succeed_n: + /* Get to the number of times to succeed. */ + p += 2; + + /* Increment p past the n for when k != 0. */ + EXTRACT_NUMBER_AND_INCR (k, p); + if (k == 0) + { + p -= 4; + succeed_n_p = true; /* Spaghetti code alert. */ + goto handle_on_failure_jump; + } + continue; + + + case set_number_at: + p += 4; + continue; + + + case start_memory: + case stop_memory: + p += 2; + continue; + + + default: + abort (); /* We have listed all the cases. */ + } /* switch *p++ */ + + /* Getting here means we have found the possible starting + characters for one path of the pattern -- and that the empty + string does not match. We need not follow this path further. + Instead, look at the next alternative (remembered on the + stack), or quit if no more. The test at the top of the loop + does these things. */ + path_can_be_null = false; + p = pend; + } /* while p */ + + /* Set `can_be_null' for the last path (also the first path, if the + pattern is empty). */ + bufp->can_be_null |= path_can_be_null; + + done: + RESET_FAIL_STACK (); + return 0; +} /* re_compile_fastmap */ +#ifdef _LIBC +weak_alias (__re_compile_fastmap, re_compile_fastmap) +#endif + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use + this memory for recording register information. STARTS and ENDS + must be allocated using the malloc library routine, and must each + be at least NUM_REGS * sizeof (regoff_t) bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ + +_EXPORT void +re_set_registers (bufp, regs, num_regs, starts, ends) + struct re_pattern_buffer *bufp; + struct re_registers *regs; + unsigned num_regs; + regoff_t *starts, *ends; +{ + if (num_regs) + { + bufp->regs_allocated = REGS_REALLOCATE; + regs->num_regs = num_regs; + regs->start = starts; + regs->end = ends; + } + else + { + bufp->regs_allocated = REGS_UNALLOCATED; + regs->num_regs = 0; + regs->start = regs->end = (regoff_t *) 0; + } +} +#ifdef _LIBC +weak_alias (__re_set_registers, re_set_registers) +#endif + +/* Searching routines. */ + +/* Like re_search_2, below, but only one string is specified, and + doesn't let you say where to stop matching. */ + +_EXPORT int +re_search (bufp, string, size, startpos, range, regs) + struct re_pattern_buffer *bufp; + const char *string; + int size, startpos, range; + struct re_registers *regs; +{ + return re_search_2 (bufp, NULL, 0, string, size, startpos, range, + regs, size); +} +#ifdef _LIBC +weak_alias (__re_search, re_search) +#endif + + +/* Using the compiled pattern in BUFP->buffer, first tries to match the + virtual concatenation of STRING1 and STRING2, starting first at index + STARTPOS, then at STARTPOS + 1, and so on. + + STRING1 and STRING2 have length SIZE1 and SIZE2, respectively. + + RANGE is how far to scan while trying to match. RANGE = 0 means try + only at STARTPOS; in general, the last start tried is STARTPOS + + RANGE. + + In REGS, return the indices of the virtual concatenation of STRING1 + and STRING2 that matched the entire BUFP->buffer and its contained + subexpressions. + + Do not consider matching one past the index STOP in the virtual + concatenation of STRING1 and STRING2. + + We return either the position in the strings at which the match was + found, -1 if no match, or -2 if error (such as failure + stack overflow). */ + +_EXPORT int +re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int size1, size2; + int startpos; + int range; + struct re_registers *regs; + int stop; +{ + int val; + register char *fastmap = bufp->fastmap; + register RE_TRANSLATE_TYPE translate = bufp->translate; + int total_size = size1 + size2; + int endpos = startpos + range; + + /* Check for out-of-range STARTPOS. */ + if (startpos < 0 || startpos > total_size) + return -1; + + /* Fix up RANGE if it might eventually take us outside + the virtual concatenation of STRING1 and STRING2. + Make sure we won't move STARTPOS below 0 or above TOTAL_SIZE. */ + if (endpos < 0) + range = 0 - startpos; + else if (endpos > total_size) + range = total_size - startpos; + + /* If the search isn't to be a backwards one, don't waste time in a + search for a pattern that must be anchored. */ + if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == begbuf && range > 0) + { + if (startpos > 0) + return -1; + else + range = 1; + } + +#ifdef emacs + /* In a forward search for something that starts with \=. + don't keep searching past point. */ + if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == at_dot && range > 0) + { + range = PT - startpos; + if (range <= 0) + return -1; + } +#endif /* emacs */ + + /* Update the fastmap now if not correct already. */ + if (fastmap && !bufp->fastmap_accurate) + if (re_compile_fastmap (bufp) == -2) + return -2; + + /* Loop through the string, looking for a place to start matching. */ + for (;;) + { + /* If a fastmap is supplied, skip quickly over characters that + cannot be the start of a match. If the pattern can match the + null string, however, we don't need to skip characters; we want + the first null string. */ + if (fastmap && startpos < total_size && !bufp->can_be_null) + { + if (range > 0) /* Searching forwards. */ + { + register const char *d; + register int lim = 0; + int irange = range; + + if (startpos < size1 && startpos + range >= size1) + lim = range - (size1 - startpos); + + d = (startpos >= size1 ? string2 - size1 : string1) + startpos; + + /* Written out as an if-else to avoid testing `translate' + inside the loop. */ + if (translate) + while (range > lim + && !fastmap[(unsigned char) + translate[(unsigned char) *d++]]) + range--; + else + while (range > lim && !fastmap[(unsigned char) *d++]) + range--; + + startpos += irange - range; + } + else /* Searching backwards. */ + { + register char c = (size1 == 0 || startpos >= size1 + ? string2[startpos - size1] + : string1[startpos]); + + if (!fastmap[(unsigned char) TRANSLATE (c)]) + goto advance; + } + } + + /* If can't match the null string, and that's all we have left, fail. */ + if (range >= 0 && startpos == total_size && fastmap + && !bufp->can_be_null) + return -1; + + val = re_match_2_internal (bufp, string1, size1, string2, size2, + startpos, regs, stop); +#ifndef REGEX_MALLOC +# ifdef C_ALLOCA + alloca (0); +# endif +#endif + + if (val >= 0) + return startpos; + + if (val == -2) + return -2; + + advance: + if (!range) + break; + else if (range > 0) + { + range--; + startpos++; + } + else + { + range++; + startpos--; + } + } + return -1; +} /* re_search_2 */ +#ifdef _LIBC +weak_alias (__re_search_2, re_search_2) +#endif + +/* This converts PTR, a pointer into one of the search strings `string1' + and `string2' into an offset from the beginning of that string. */ +#define POINTER_TO_OFFSET(ptr) \ + (FIRST_STRING_P (ptr) \ + ? ((regoff_t) ((ptr) - string1)) \ + : ((regoff_t) ((ptr) - string2 + size1))) + +/* Macros for dealing with the split strings in re_match_2. */ + +#define MATCHING_IN_FIRST_STRING (dend == end_match_1) + +/* Call before fetching a character with *d. This switches over to + string2 if necessary. */ +#define PREFETCH() \ + while (d == dend) \ + { \ + /* End of string2 => fail. */ \ + if (dend == end_match_2) \ + goto fail; \ + /* End of string1 => advance to string2. */ \ + d = string2; \ + dend = end_match_2; \ + } + + +/* Test if at very beginning or at very end of the virtual concatenation + of `string1' and `string2'. If only one string, it's `string2'. */ +#define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2) +#define AT_STRINGS_END(d) ((d) == end2) + + +/* Test if D points to a character which is word-constituent. We have + two special cases to check for: if past the end of string1, look at + the first character in string2; and if before the beginning of + string2, look at the last character in string1. */ +#define WORDCHAR_P(d) \ + (SYNTAX ((d) == end1 ? *string2 \ + : (d) == string2 - 1 ? *(end1 - 1) : *(d)) \ + == Sword) + +/* Disabled due to a compiler bug -- see comment at case wordbound */ +#if 0 +/* Test if the character before D and the one at D differ with respect + to being word-constituent. */ +#define AT_WORD_BOUNDARY(d) \ + (AT_STRINGS_BEG (d) || AT_STRINGS_END (d) \ + || WORDCHAR_P (d - 1) != WORDCHAR_P (d)) +#endif + +/* Free everything we malloc. */ +#ifdef MATCH_MAY_ALLOCATE +# define FREE_VAR(var) if (var) REGEX_FREE (var); var = NULL +# define FREE_VARIABLES() \ + do { \ + REGEX_FREE_STACK (fail_stack.stack); \ + FREE_VAR (regstart); \ + FREE_VAR (regend); \ + FREE_VAR (old_regstart); \ + FREE_VAR (old_regend); \ + FREE_VAR (best_regstart); \ + FREE_VAR (best_regend); \ + FREE_VAR (reg_info); \ + FREE_VAR (reg_dummy); \ + FREE_VAR (reg_info_dummy); \ + } while (0) +#else +# define FREE_VARIABLES() ((void)0) /* Do nothing! But inhibit gcc warning. */ +#endif /* not MATCH_MAY_ALLOCATE */ + +/* These values must meet several constraints. They must not be valid + register values; since we have a limit of 255 registers (because + we use only one byte in the pattern for the register number), we can + use numbers larger than 255. They must differ by 1, because of + NUM_FAILURE_ITEMS above. And the value for the lowest register must + be larger than the value for the highest register, so we do not try + to actually save any registers when none are active. */ +#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH) +#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1) + +/* Matching routines. */ + +#ifndef emacs /* Emacs never uses this. */ +/* re_match is like re_match_2 except it takes only a single string. */ + +_EXPORT int +re_match (bufp, string, size, pos, regs) + struct re_pattern_buffer *bufp; + const char *string; + int size, pos; + struct re_registers *regs; +{ + int result = re_match_2_internal (bufp, NULL, 0, string, size, + pos, regs, size); +# ifndef REGEX_MALLOC +# ifdef C_ALLOCA + alloca (0); +# endif +# endif + return result; +} +# ifdef _LIBC +weak_alias (__re_match, re_match) +# endif +#endif /* not emacs */ + +static boolean group_match_null_string_p _RE_ARGS ((unsigned char **p, + unsigned char *end, + register_info_type *reg_info)); +static boolean alt_match_null_string_p _RE_ARGS ((unsigned char *p, + unsigned char *end, + register_info_type *reg_info)); +static boolean common_op_match_null_string_p _RE_ARGS ((unsigned char **p, + unsigned char *end, + register_info_type *reg_info)); +static int bcmp_translate _RE_ARGS ((const char *s1, const char *s2, + int len, char *translate)); + +/* re_match_2 matches the compiled pattern in BUFP against the + the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1 + and SIZE2, respectively). We start matching at POS, and stop + matching at STOP. + + If REGS is non-null and the `no_sub' field of BUFP is nonzero, we + store offsets for the substring each group matched in REGS. See the + documentation for exactly how many groups we fill. + + We return -1 if no match, -2 if an internal error (such as the + failure stack overflowing). Otherwise, we return the length of the + matched substring. */ + +_EXPORT int +re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int size1, size2; + int pos; + struct re_registers *regs; + int stop; +{ + int result = re_match_2_internal (bufp, string1, size1, string2, size2, + pos, regs, stop); +#ifndef REGEX_MALLOC +# ifdef C_ALLOCA + alloca (0); +# endif +#endif + return result; +} +#ifdef _LIBC +weak_alias (__re_match_2, re_match_2) +#endif + +/* This is a separate function so that we can force an alloca cleanup + afterwards. */ +static int +re_match_2_internal (bufp, string1, size1, string2, size2, pos, regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int size1, size2; + int pos; + struct re_registers *regs; + int stop; +{ + /* General temporaries. */ + int mcnt; + unsigned char *p1; + + /* Just past the end of the corresponding string. */ + const char *end1, *end2; + + /* Pointers into string1 and string2, just past the last characters in + each to consider matching. */ + const char *end_match_1, *end_match_2; + + /* Where we are in the data, and the end of the current string. */ + const char *d, *dend; + + /* Where we are in the pattern, and the end of the pattern. */ + unsigned char *p = bufp->buffer; + register unsigned char *pend = p + bufp->used; + + /* Mark the opcode just after a start_memory, so we can test for an + empty subpattern when we get to the stop_memory. */ + unsigned char *just_past_start_mem = 0; + + /* We use this to map every character in the string. */ + RE_TRANSLATE_TYPE translate = bufp->translate; + + /* Failure point stack. Each place that can handle a failure further + down the line pushes a failure point on this stack. It consists of + restart, regend, and reg_info for all registers corresponding to + the subexpressions we're currently inside, plus the number of such + registers, and, finally, two char *'s. The first char * is where + to resume scanning the pattern; the second one is where to resume + scanning the strings. If the latter is zero, the failure point is + a ``dummy''; if a failure happens and the failure point is a dummy, + it gets discarded and the next next one is tried. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, this is global. */ + fail_stack_type fail_stack; +#endif +#ifdef DEBUG + static unsigned failure_id = 0; + unsigned nfailure_points_pushed = 0, nfailure_points_popped = 0; +#endif + +#ifdef REL_ALLOC + /* This holds the pointer to the failure stack, when + it is allocated relocatably. */ + fail_stack_elt_t *failure_stack_ptr; +#endif + + /* We fill all the registers internally, independent of what we + return, for use in backreferences. The number here includes + an element for register zero. */ + size_t num_regs = bufp->re_nsub + 1; + + /* The currently active registers. */ + active_reg_t lowest_active_reg = NO_LOWEST_ACTIVE_REG; + active_reg_t highest_active_reg = NO_HIGHEST_ACTIVE_REG; + + /* Information on the contents of registers. These are pointers into + the input strings; they record just what was matched (on this + attempt) by a subexpression part of the pattern, that is, the + regnum-th regstart pointer points to where in the pattern we began + matching and the regnum-th regend points to right after where we + stopped matching the regnum-th subexpression. (The zeroth register + keeps track of what the whole pattern matches.) */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **regstart, **regend; +#endif + + /* If a group that's operated upon by a repetition operator fails to + match anything, then the register for its start will need to be + restored because it will have been set to wherever in the string we + are when we last see its open-group operator. Similarly for a + register's end. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **old_regstart, **old_regend; +#endif + + /* The is_active field of reg_info helps us keep track of which (possibly + nested) subexpressions we are currently in. The matched_something + field of reg_info[reg_num] helps us tell whether or not we have + matched any of the pattern so far this time through the reg_num-th + subexpression. These two fields get reset each time through any + loop their register is in. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, this is global. */ + register_info_type *reg_info; +#endif + + /* The following record the register info as found in the above + variables when we find a match better than any we've seen before. + This happens as we backtrack through the failure points, which in + turn happens only if we have not yet matched the entire string. */ + unsigned best_regs_set = false; +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **best_regstart, **best_regend; +#endif + + /* Logically, this is `best_regend[0]'. But we don't want to have to + allocate space for that if we're not allocating space for anything + else (see below). Also, we never need info about register 0 for + any of the other register vectors, and it seems rather a kludge to + treat `best_regend' differently than the rest. So we keep track of + the end of the best match so far in a separate variable. We + initialize this to NULL so that when we backtrack the first time + and need to test it, it's not garbage. */ + const char *match_end = NULL; + + /* This helps SET_REGS_MATCHED avoid doing redundant work. */ + int set_regs_matched_done = 0; + + /* Used when we pop values we don't care about. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **reg_dummy; + register_info_type *reg_info_dummy; +#endif + +#ifdef DEBUG + /* Counts the total number of registers pushed. */ + unsigned num_regs_pushed = 0; +#endif + + DEBUG_PRINT1 ("\n\nEntering re_match_2.\n"); + + INIT_FAIL_STACK (); + +#ifdef MATCH_MAY_ALLOCATE + /* Do not bother to initialize all the register variables if there are + no groups in the pattern, as it takes a fair amount of time. If + there are groups, we include space for register 0 (the whole + pattern), even though we never use it, since it simplifies the + array indexing. We should fix this. */ + if (bufp->re_nsub) + { + regstart = REGEX_TALLOC (num_regs, const char *); + regend = REGEX_TALLOC (num_regs, const char *); + old_regstart = REGEX_TALLOC (num_regs, const char *); + old_regend = REGEX_TALLOC (num_regs, const char *); + best_regstart = REGEX_TALLOC (num_regs, const char *); + best_regend = REGEX_TALLOC (num_regs, const char *); + reg_info = REGEX_TALLOC (num_regs, register_info_type); + reg_dummy = REGEX_TALLOC (num_regs, const char *); + reg_info_dummy = REGEX_TALLOC (num_regs, register_info_type); + + if (!(regstart && regend && old_regstart && old_regend && reg_info + && best_regstart && best_regend && reg_dummy && reg_info_dummy)) + { + FREE_VARIABLES (); + return -2; + } + } + else + { + /* We must initialize all our variables to NULL, so that + `FREE_VARIABLES' doesn't try to free them. */ + regstart = regend = old_regstart = old_regend = best_regstart + = best_regend = reg_dummy = NULL; + reg_info = reg_info_dummy = (register_info_type *) NULL; + } +#endif /* MATCH_MAY_ALLOCATE */ + + /* The starting position is bogus. */ + if (pos < 0 || pos > size1 + size2) + { + FREE_VARIABLES (); + return -1; + } + + /* Initialize subexpression text positions to -1 to mark ones that no + start_memory/stop_memory has been seen for. Also initialize the + register information struct. */ + for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) + { + regstart[mcnt] = regend[mcnt] + = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE; + + REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE; + IS_ACTIVE (reg_info[mcnt]) = 0; + MATCHED_SOMETHING (reg_info[mcnt]) = 0; + EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0; + } + + /* We move `string1' into `string2' if the latter's empty -- but not if + `string1' is null. */ + if (size2 == 0 && string1 != NULL) + { + string2 = string1; + size2 = size1; + string1 = 0; + size1 = 0; + } + end1 = string1 + size1; + end2 = string2 + size2; + + /* Compute where to stop matching, within the two strings. */ + if (stop <= size1) + { + end_match_1 = string1 + stop; + end_match_2 = string2; + } + else + { + end_match_1 = end1; + end_match_2 = string2 + stop - size1; + } + + /* `p' scans through the pattern as `d' scans through the data. + `dend' is the end of the input string that `d' points within. `d' + is advanced into the following input string whenever necessary, but + this happens before fetching; therefore, at the beginning of the + loop, `d' can be pointing at the end of a string, but it cannot + equal `string2'. */ + if (size1 > 0 && pos <= size1) + { + d = string1 + pos; + dend = end_match_1; + } + else + { + d = string2 + pos - size1; + dend = end_match_2; + } + + DEBUG_PRINT1 ("The compiled pattern is:\n"); + DEBUG_PRINT_COMPILED_PATTERN (bufp, p, pend); + DEBUG_PRINT1 ("The string to match is: `"); + DEBUG_PRINT_DOUBLE_STRING (d, string1, size1, string2, size2); + DEBUG_PRINT1 ("'\n"); + + /* This loops over pattern commands. It exits by returning from the + function if the match is complete, or it drops through if the match + fails at this starting point in the input data. */ + for (;;) + { +#ifdef _LIBC + DEBUG_PRINT2 ("\n%p: ", p); +#else + DEBUG_PRINT2 ("\n0x%x: ", p); +#endif + + if (p == pend) + { /* End of pattern means we might have succeeded. */ + DEBUG_PRINT1 ("end of pattern ... "); + + /* If we haven't matched the entire string, and we want the + longest match, try backtracking. */ + if (d != end_match_2) + { + /* 1 if this match ends in the same string (string1 or string2) + as the best previous match. */ + boolean same_str_p = (FIRST_STRING_P (match_end) + == MATCHING_IN_FIRST_STRING); + /* 1 if this match is the best seen so far. */ + boolean best_match_p; + + /* AIX compiler got confused when this was combined + with the previous declaration. */ + if (same_str_p) + best_match_p = d > match_end; + else + best_match_p = !MATCHING_IN_FIRST_STRING; + + DEBUG_PRINT1 ("backtracking.\n"); + + if (!FAIL_STACK_EMPTY ()) + { /* More failure points to try. */ + + /* If exceeds best match so far, save it. */ + if (!best_regs_set || best_match_p) + { + best_regs_set = true; + match_end = d; + + DEBUG_PRINT1 ("\nSAVING match as best so far.\n"); + + for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) + { + best_regstart[mcnt] = regstart[mcnt]; + best_regend[mcnt] = regend[mcnt]; + } + } + goto fail; + } + + /* If no failure points, don't restore garbage. And if + last match is real best match, don't restore second + best one. */ + else if (best_regs_set && !best_match_p) + { + restore_best_regs: + /* Restore best match. It may happen that `dend == + end_match_1' while the restored d is in string2. + For example, the pattern `x.*y.*z' against the + strings `x-' and `y-z-', if the two strings are + not consecutive in memory. */ + DEBUG_PRINT1 ("Restoring best registers.\n"); + + d = match_end; + dend = ((d >= string1 && d <= end1) + ? end_match_1 : end_match_2); + + for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) + { + regstart[mcnt] = best_regstart[mcnt]; + regend[mcnt] = best_regend[mcnt]; + } + } + } /* d != end_match_2 */ + + succeed_label: + DEBUG_PRINT1 ("Accepting match.\n"); + + /* If caller wants register contents data back, do it. */ + if (regs && !bufp->no_sub) + { + /* Have the register data arrays been allocated? */ + if (bufp->regs_allocated == REGS_UNALLOCATED) + { /* No. So allocate them with malloc. We need one + extra element beyond `num_regs' for the `-1' marker + GNU code uses. */ + regs->num_regs = MAX (RE_NREGS, num_regs + 1); + regs->start = TALLOC (regs->num_regs, regoff_t); + regs->end = TALLOC (regs->num_regs, regoff_t); + if (regs->start == NULL || regs->end == NULL) + { + FREE_VARIABLES (); + return -2; + } + bufp->regs_allocated = REGS_REALLOCATE; + } + else if (bufp->regs_allocated == REGS_REALLOCATE) + { /* Yes. If we need more elements than were already + allocated, reallocate them. If we need fewer, just + leave it alone. */ + if (regs->num_regs < num_regs + 1) + { + regs->num_regs = num_regs + 1; + RETALLOC (regs->start, regs->num_regs, regoff_t); + RETALLOC (regs->end, regs->num_regs, regoff_t); + if (regs->start == NULL || regs->end == NULL) + { + FREE_VARIABLES (); + return -2; + } + } + } + else + { + /* These braces fend off a "empty body in an else-statement" + warning under GCC when assert expands to nothing. */ + assert (bufp->regs_allocated == REGS_FIXED); + } + + /* Convert the pointer data in `regstart' and `regend' to + indices. Register zero has to be set differently, + since we haven't kept track of any info for it. */ + if (regs->num_regs > 0) + { + regs->start[0] = pos; + regs->end[0] = (MATCHING_IN_FIRST_STRING + ? ((regoff_t) (d - string1)) + : ((regoff_t) (d - string2 + size1))); + } + + /* Go through the first `min (num_regs, regs->num_regs)' + registers, since that is all we initialized. */ + for (mcnt = 1; (unsigned) mcnt < MIN (num_regs, regs->num_regs); + mcnt++) + { + if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt])) + regs->start[mcnt] = regs->end[mcnt] = -1; + else + { + regs->start[mcnt] + = (regoff_t) POINTER_TO_OFFSET (regstart[mcnt]); + regs->end[mcnt] + = (regoff_t) POINTER_TO_OFFSET (regend[mcnt]); + } + } + + /* If the regs structure we return has more elements than + were in the pattern, set the extra elements to -1. If + we (re)allocated the registers, this is the case, + because we always allocate enough to have at least one + -1 at the end. */ + for (mcnt = num_regs; (unsigned) mcnt < regs->num_regs; mcnt++) + regs->start[mcnt] = regs->end[mcnt] = -1; + } /* regs && !bufp->no_sub */ + + DEBUG_PRINT4 ("%u failure points pushed, %u popped (%u remain).\n", + nfailure_points_pushed, nfailure_points_popped, + nfailure_points_pushed - nfailure_points_popped); + DEBUG_PRINT2 ("%u registers pushed.\n", num_regs_pushed); + + mcnt = d - pos - (MATCHING_IN_FIRST_STRING + ? string1 + : string2 - size1); + + DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt); + + FREE_VARIABLES (); + return mcnt; + } + + /* Otherwise match next pattern command. */ + switch (SWITCH_ENUM_CAST ((re_opcode_t) *p++)) + { + /* Ignore these. Used to ignore the n of succeed_n's which + currently have n == 0. */ + case no_op: + DEBUG_PRINT1 ("EXECUTING no_op.\n"); + break; + + case succeed: + DEBUG_PRINT1 ("EXECUTING succeed.\n"); + goto succeed_label; + + /* Match the next n pattern characters exactly. The following + byte in the pattern defines n, and the n bytes after that + are the characters to match. */ + case exactn: + mcnt = *p++; + DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt); + + /* This is written out as an if-else so we don't waste time + testing `translate' inside the loop. */ + if (translate) + { + do + { + PREFETCH (); + if ((unsigned char) translate[(unsigned char) *d++] + != (unsigned char) *p++) + goto fail; + } + while (--mcnt); + } + else + { + do + { + PREFETCH (); + if (*d++ != (char) *p++) goto fail; + } + while (--mcnt); + } + SET_REGS_MATCHED (); + break; + + + /* Match any character except possibly a newline or a null. */ + case anychar: + DEBUG_PRINT1 ("EXECUTING anychar.\n"); + + PREFETCH (); + + if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n') + || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000')) + goto fail; + + SET_REGS_MATCHED (); + DEBUG_PRINT2 (" Matched `%d'.\n", *d); + d++; + break; + + + case charset: + case charset_not: + { + register unsigned char c; + boolean not = (re_opcode_t) *(p - 1) == charset_not; + + DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : ""); + + PREFETCH (); + c = TRANSLATE (*d); /* The character to match. */ + + /* Cast to `unsigned' instead of `unsigned char' in case the + bit list is a full 32 bytes long. */ + if (c < (unsigned) (*p * BYTEWIDTH) + && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + + p += 1 + *p; + + if (!not) goto fail; + + SET_REGS_MATCHED (); + d++; + break; + } + + + /* The beginning of a group is represented by start_memory. + The arguments are the register number in the next byte, and the + number of groups inner to this one in the next. The text + matched within the group is recorded (in the internal + registers data structure) under the register number. */ + case start_memory: + DEBUG_PRINT3 ("EXECUTING start_memory %d (%d):\n", *p, p[1]); + + /* Find out if this group can match the empty string. */ + p1 = p; /* To send to group_match_null_string_p. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[*p]) + = group_match_null_string_p (&p1, pend, reg_info); + + /* Save the position in the string where we were the last time + we were at this open-group operator in case the group is + operated upon by a repetition operator, e.g., with `(a*)*b' + against `ab'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regstart[*p]) ? d : regstart[*p] + : regstart[*p]; + DEBUG_PRINT2 (" old_regstart: %d\n", + POINTER_TO_OFFSET (old_regstart[*p])); + + regstart[*p] = d; + DEBUG_PRINT2 (" regstart: %d\n", POINTER_TO_OFFSET (regstart[*p])); + + IS_ACTIVE (reg_info[*p]) = 1; + MATCHED_SOMETHING (reg_info[*p]) = 0; + + /* Clear this whenever we change the register activity status. */ + set_regs_matched_done = 0; + + /* This is the new highest active register. */ + highest_active_reg = *p; + + /* If nothing was active before, this is the new lowest active + register. */ + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *p; + + /* Move past the register number and inner group count. */ + p += 2; + just_past_start_mem = p; + + break; + + + /* The stop_memory opcode represents the end of a group. Its + arguments are the same as start_memory's: the register + number, and the number of inner groups. */ + case stop_memory: + DEBUG_PRINT3 ("EXECUTING stop_memory %d (%d):\n", *p, p[1]); + + /* We need to save the string position the last time we were at + this close-group operator in case the group is operated + upon by a repetition operator, e.g., with `((a*)*(b*)*)*' + against `aba'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regend[*p]) ? d : regend[*p] + : regend[*p]; + DEBUG_PRINT2 (" old_regend: %d\n", + POINTER_TO_OFFSET (old_regend[*p])); + + regend[*p] = d; + DEBUG_PRINT2 (" regend: %d\n", POINTER_TO_OFFSET (regend[*p])); + + /* This register isn't active anymore. */ + IS_ACTIVE (reg_info[*p]) = 0; + + /* Clear this whenever we change the register activity status. */ + set_regs_matched_done = 0; + + /* If this was the only register active, nothing is active + anymore. */ + if (lowest_active_reg == highest_active_reg) + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + else + { /* We must scan for the new highest active register, since + it isn't necessarily one less than now: consider + (a(b)c(d(e)f)g). When group 3 ends, after the f), the + new highest active register is 1. */ + unsigned char r = *p - 1; + while (r > 0 && !IS_ACTIVE (reg_info[r])) + r--; + + /* If we end up at register zero, that means that we saved + the registers as the result of an `on_failure_jump', not + a `start_memory', and we jumped to past the innermost + `stop_memory'. For example, in ((.)*) we save + registers 1 and 2 as a result of the *, but when we pop + back to the second ), we are at the stop_memory 1. + Thus, nothing is active. */ + if (r == 0) + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + else + highest_active_reg = r; + } + + /* If just failed to match something this time around with a + group that's operated on by a repetition operator, try to + force exit from the ``loop'', and restore the register + information for this group that we had before trying this + last match. */ + if ((!MATCHED_SOMETHING (reg_info[*p]) + || just_past_start_mem == p - 1) + && (p + 2) < pend) + { + boolean is_a_jump_n = false; + + p1 = p + 2; + mcnt = 0; + switch ((re_opcode_t) *p1++) + { + case jump_n: + is_a_jump_n = true; + case pop_failure_jump: + case maybe_pop_jump: + case jump: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (is_a_jump_n) + p1 += 2; + break; + + default: + /* do nothing */ ; + } + p1 += mcnt; + + /* If the next operation is a jump backwards in the pattern + to an on_failure_jump right before the start_memory + corresponding to this stop_memory, exit from the loop + by forcing a failure after pushing on the stack the + on_failure_jump's jump in the pattern, and d. */ + if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump + && (re_opcode_t) p1[3] == start_memory && p1[4] == *p) + { + /* If this group ever matched anything, then restore + what its registers were before trying this last + failed match, e.g., with `(a*)*b' against `ab' for + regstart[1], and, e.g., with `((a*)*(b*)*)*' + against `aba' for regend[3]. + + Also restore the registers for inner groups for, + e.g., `((a*)(b*))*' against `aba' (register 3 would + otherwise get trashed). */ + + if (EVER_MATCHED_SOMETHING (reg_info[*p])) + { + unsigned r; + + EVER_MATCHED_SOMETHING (reg_info[*p]) = 0; + + /* Restore this and inner groups' (if any) registers. */ + for (r = *p; r < (unsigned) *p + (unsigned) *(p + 1); + r++) + { + regstart[r] = old_regstart[r]; + + /* xx why this test? */ + if (old_regend[r] >= regstart[r]) + regend[r] = old_regend[r]; + } + } + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + PUSH_FAILURE_POINT (p1 + mcnt, d, -2); + + goto fail; + } + } + + /* Move past the register number and the inner group count. */ + p += 2; + break; + + + /* \ has been turned into a `duplicate' command which is + followed by the numeric value of as the register number. */ + case duplicate: + { + register const char *d2, *dend2; + int regno = *p++; /* Get which register to match against. */ + DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno); + + /* Can't back reference a group which we've never matched. */ + if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno])) + goto fail; + + /* Where in input to try to start matching. */ + d2 = regstart[regno]; + + /* Where to stop matching; if both the place to start and + the place to stop matching are in the same string, then + set to the place to stop, otherwise, for now have to use + the end of the first string. */ + + dend2 = ((FIRST_STRING_P (regstart[regno]) + == FIRST_STRING_P (regend[regno])) + ? regend[regno] : end_match_1); + for (;;) + { + /* If necessary, advance to next segment in register + contents. */ + while (d2 == dend2) + { + if (dend2 == end_match_2) break; + if (dend2 == regend[regno]) break; + + /* End of string1 => advance to string2. */ + d2 = string2; + dend2 = regend[regno]; + } + /* At end of register contents => success */ + if (d2 == dend2) break; + + /* If necessary, advance to next segment in data. */ + PREFETCH (); + + /* How many characters left in this segment to match. */ + mcnt = dend - d; + + /* Want how many consecutive characters we can match in + one shot, so, if necessary, adjust the count. */ + if (mcnt > dend2 - d2) + mcnt = dend2 - d2; + + /* Compare that many; failure if mismatch, else move + past them. */ + if (translate + ? bcmp_translate (d, d2, mcnt, translate) + : memcmp (d, d2, mcnt)) + goto fail; + d += mcnt, d2 += mcnt; + + /* Do this because we've match some characters. */ + SET_REGS_MATCHED (); + } + } + break; + + + /* begline matches the empty string at the beginning of the string + (unless `not_bol' is set in `bufp'), and, if + `newline_anchor' is set, after newlines. */ + case begline: + DEBUG_PRINT1 ("EXECUTING begline.\n"); + + if (AT_STRINGS_BEG (d)) + { + if (!bufp->not_bol) break; + } + else if (d[-1] == '\n' && bufp->newline_anchor) + { + break; + } + /* In all other cases, we fail. */ + goto fail; + + + /* endline is the dual of begline. */ + case endline: + DEBUG_PRINT1 ("EXECUTING endline.\n"); + + if (AT_STRINGS_END (d)) + { + if (!bufp->not_eol) break; + } + + /* We have to ``prefetch'' the next character. */ + else if ((d == end1 ? *string2 : *d) == '\n' + && bufp->newline_anchor) + { + break; + } + goto fail; + + + /* Match at the very beginning of the data. */ + case begbuf: + DEBUG_PRINT1 ("EXECUTING begbuf.\n"); + if (AT_STRINGS_BEG (d)) + break; + goto fail; + + + /* Match at the very end of the data. */ + case endbuf: + DEBUG_PRINT1 ("EXECUTING endbuf.\n"); + if (AT_STRINGS_END (d)) + break; + goto fail; + + + /* on_failure_keep_string_jump is used to optimize `.*\n'. It + pushes NULL as the value for the string on the stack. Then + `pop_failure_point' will keep the current value for the + string, instead of restoring it. To see why, consider + matching `foo\nbar' against `.*\n'. The .* matches the foo; + then the . fails against the \n. But the next thing we want + to do is match the \n against the \n; if we restored the + string value, we would be back at the foo. + + Because this is used only in specific cases, we don't need to + check all the things that `on_failure_jump' does, to make + sure the right things get saved on the stack. Hence we don't + share its code. The only reason to push anything on the + stack at all is that otherwise we would have to change + `anychar's code to do something besides goto fail in this + case; that seems worse than this. */ + case on_failure_keep_string_jump: + DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); +#ifdef _LIBC + DEBUG_PRINT3 (" %d (to %p):\n", mcnt, p + mcnt); +#else + DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt); +#endif + + PUSH_FAILURE_POINT (p + mcnt, NULL, -2); + break; + + + /* Uses of on_failure_jump: + + Each alternative starts with an on_failure_jump that points + to the beginning of the next alternative. Each alternative + except the last ends with a jump that in effect jumps past + the rest of the alternatives. (They really jump to the + ending jump of the following alternative, because tensioning + these jumps is a hassle.) + + Repeats start with an on_failure_jump that points past both + the repetition text and either the following jump or + pop_failure_jump back to this on_failure_jump. */ + case on_failure_jump: + on_failure: + DEBUG_PRINT1 ("EXECUTING on_failure_jump"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); +#ifdef _LIBC + DEBUG_PRINT3 (" %d (to %p)", mcnt, p + mcnt); +#else + DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt); +#endif + + /* If this on_failure_jump comes right before a group (i.e., + the original * applied to a group), save the information + for that group and all inner ones, so that if we fail back + to this point, the group's information will be correct. + For example, in \(a*\)*\1, we need the preceding group, + and in \(zz\(a*\)b*\)\2, we need the inner group. */ + + /* We can't use `p' to check ahead because we push + a failure point to `p + mcnt' after we do this. */ + p1 = p; + + /* We need to skip no_op's before we look for the + start_memory in case this on_failure_jump is happening as + the result of a completed succeed_n, as in \(a\)\{1,3\}b\1 + against aba. */ + while (p1 < pend && (re_opcode_t) *p1 == no_op) + p1++; + + if (p1 < pend && (re_opcode_t) *p1 == start_memory) + { + /* We have a new highest active register now. This will + get reset at the start_memory we are about to get to, + but we will have saved all the registers relevant to + this repetition op, as described above. */ + highest_active_reg = *(p1 + 1) + *(p1 + 2); + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *(p1 + 1); + } + + DEBUG_PRINT1 (":\n"); + PUSH_FAILURE_POINT (p + mcnt, d, -2); + break; + + + /* A smart repeat ends with `maybe_pop_jump'. + We change it to either `pop_failure_jump' or `jump'. */ + case maybe_pop_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt); + { + register unsigned char *p2 = p; + + /* Compare the beginning of the repeat with what in the + pattern follows its end. If we can establish that there + is nothing that they would both match, i.e., that we + would have to backtrack because of (as in, e.g., `a*a') + then we can change to pop_failure_jump, because we'll + never have to backtrack. + + This is not true in the case of alternatives: in + `(a|ab)*' we do need to backtrack to the `ab' alternative + (e.g., if the string was `ab'). But instead of trying to + detect that here, the alternative has put on a dummy + failure point which is what we will end up popping. */ + + /* Skip over open/close-group commands. + If what follows this loop is a ...+ construct, + look at what begins its body, since we will have to + match at least one of that. */ + while (1) + { + if (p2 + 2 < pend + && ((re_opcode_t) *p2 == stop_memory + || (re_opcode_t) *p2 == start_memory)) + p2 += 3; + else if (p2 + 6 < pend + && (re_opcode_t) *p2 == dummy_failure_jump) + p2 += 6; + else + break; + } + + p1 = p + mcnt; + /* p1[0] ... p1[2] are the `on_failure_jump' corresponding + to the `maybe_finalize_jump' of this case. Examine what + follows. */ + + /* If we're at the end of the pattern, we can change. */ + if (p2 == pend) + { + /* Consider what happens when matching ":\(.*\)" + against ":/". I don't really understand this code + yet. */ + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT1 + (" End of pattern: change to `pop_failure_jump'.\n"); + } + + else if ((re_opcode_t) *p2 == exactn + || (bufp->newline_anchor && (re_opcode_t) *p2 == endline)) + { + register unsigned char c + = *p2 == (unsigned char) endline ? '\n' : p2[2]; + + if ((re_opcode_t) p1[3] == exactn && p1[5] != c) + { + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT3 (" %c != %c => pop_failure_jump.\n", + c, p1[5]); + } + + else if ((re_opcode_t) p1[3] == charset + || (re_opcode_t) p1[3] == charset_not) + { + int not = (re_opcode_t) p1[3] == charset_not; + + if (c < (unsigned char) (p1[4] * BYTEWIDTH) + && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + + /* `not' is equal to 1 if c would match, which means + that we can't change to pop_failure_jump. */ + if (!not) + { + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); + } + } + } + else if ((re_opcode_t) *p2 == charset) + { +#ifdef DEBUG + register unsigned char c + = *p2 == (unsigned char) endline ? '\n' : p2[2]; +#endif + +#if 0 + if ((re_opcode_t) p1[3] == exactn + && ! ((int) p2[1] * BYTEWIDTH > (int) p1[5] + && (p2[2 + p1[5] / BYTEWIDTH] + & (1 << (p1[5] % BYTEWIDTH))))) +#else + if ((re_opcode_t) p1[3] == exactn + && ! ((int) p2[1] * BYTEWIDTH > (int) p1[4] + && (p2[2 + p1[4] / BYTEWIDTH] + & (1 << (p1[4] % BYTEWIDTH))))) +#endif + { + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT3 (" %c != %c => pop_failure_jump.\n", + c, p1[5]); + } + + else if ((re_opcode_t) p1[3] == charset_not) + { + int idx; + /* We win if the charset_not inside the loop + lists every character listed in the charset after. */ + for (idx = 0; idx < (int) p2[1]; idx++) + if (! (p2[2 + idx] == 0 + || (idx < (int) p1[4] + && ((p2[2 + idx] & ~ p1[5 + idx]) == 0)))) + break; + + if (idx == p2[1]) + { + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); + } + } + else if ((re_opcode_t) p1[3] == charset) + { + int idx; + /* We win if the charset inside the loop + has no overlap with the one after the loop. */ + for (idx = 0; + idx < (int) p2[1] && idx < (int) p1[4]; + idx++) + if ((p2[2 + idx] & p1[5 + idx]) != 0) + break; + + if (idx == p2[1] || idx == p1[4]) + { + p[-3] = (unsigned char) pop_failure_jump; + DEBUG_PRINT1 (" No match => pop_failure_jump.\n"); + } + } + } + } + p -= 2; /* Point at relative address again. */ + if ((re_opcode_t) p[-1] != pop_failure_jump) + { + p[-1] = (unsigned char) jump; + DEBUG_PRINT1 (" Match => jump.\n"); + goto unconditional_jump; + } + /* Note fall through. */ + + + /* The end of a simple repeat has a pop_failure_jump back to + its matching on_failure_jump, where the latter will push a + failure point. The pop_failure_jump takes off failure + points put on by this pop_failure_jump's matching + on_failure_jump; we got through the pattern to here from the + matching on_failure_jump, so didn't fail. */ + case pop_failure_jump: + { + /* We need to pass separate storage for the lowest and + highest registers, even though we don't care about the + actual values. Otherwise, we will restore only one + register from the stack, since lowest will == highest in + `pop_failure_point'. */ + active_reg_t dummy_low_reg, dummy_high_reg; + unsigned char *pdummy; + const char *sdummy; + + DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n"); + POP_FAILURE_POINT (sdummy, pdummy, + dummy_low_reg, dummy_high_reg, + reg_dummy, reg_dummy, reg_info_dummy); + } + /* Note fall through. */ + + unconditional_jump: +#ifdef _LIBC + DEBUG_PRINT2 ("\n%p: ", p); +#else + DEBUG_PRINT2 ("\n0x%x: ", p); +#endif + /* Note fall through. */ + + /* Unconditionally jump (without popping any failure points). */ + case jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */ + DEBUG_PRINT2 ("EXECUTING jump %d ", mcnt); + p += mcnt; /* Do the jump. */ +#ifdef _LIBC + DEBUG_PRINT2 ("(to %p).\n", p); +#else + DEBUG_PRINT2 ("(to 0x%x).\n", p); +#endif + break; + + + /* We need this opcode so we can detect where alternatives end + in `group_match_null_string_p' et al. */ + case jump_past_alt: + DEBUG_PRINT1 ("EXECUTING jump_past_alt.\n"); + goto unconditional_jump; + + + /* Normally, the on_failure_jump pushes a failure point, which + then gets popped at pop_failure_jump. We will end up at + pop_failure_jump, also, and with a pattern of, say, `a+', we + are skipping over the on_failure_jump, so we have to push + something meaningless for pop_failure_jump to pop. */ + case dummy_failure_jump: + DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n"); + /* It doesn't matter what we push for the string here. What + the code at `fail' tests is the value for the pattern. */ + PUSH_FAILURE_POINT (NULL, NULL, -2); + goto unconditional_jump; + + + /* At the end of an alternative, we need to push a dummy failure + point in case we are followed by a `pop_failure_jump', because + we don't want the failure point for the alternative to be + popped. For example, matching `(a|ab)*' against `aab' + requires that we match the `ab' alternative. */ + case push_dummy_failure: + DEBUG_PRINT1 ("EXECUTING push_dummy_failure.\n"); + /* See comments just above at `dummy_failure_jump' about the + two zeroes. */ + PUSH_FAILURE_POINT (NULL, NULL, -2); + break; + + /* Have to succeed matching what follows at least n times. + After that, handle like `on_failure_jump'. */ + case succeed_n: + EXTRACT_NUMBER (mcnt, p + 2); + DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt); + + assert (mcnt >= 0); + /* Originally, this is how many times we HAVE to succeed. */ + if (mcnt > 0) + { + mcnt--; + p += 2; + STORE_NUMBER_AND_INCR (p, mcnt); +#ifdef _LIBC + DEBUG_PRINT3 (" Setting %p to %d.\n", p - 2, mcnt); +#else + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p - 2, mcnt); +#endif + } + else if (mcnt == 0) + { +#ifdef _LIBC + DEBUG_PRINT2 (" Setting two bytes from %p to no_op.\n", p+2); +#else + DEBUG_PRINT2 (" Setting two bytes from 0x%x to no_op.\n", p+2); +#endif + p[2] = (unsigned char) no_op; + p[3] = (unsigned char) no_op; + goto on_failure; + } + break; + + case jump_n: + EXTRACT_NUMBER (mcnt, p + 2); + DEBUG_PRINT2 ("EXECUTING jump_n %d.\n", mcnt); + + /* Originally, this is how many times we CAN jump. */ + if (mcnt) + { + mcnt--; + STORE_NUMBER (p + 2, mcnt); +#ifdef _LIBC + DEBUG_PRINT3 (" Setting %p to %d.\n", p + 2, mcnt); +#else + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p + 2, mcnt); +#endif + goto unconditional_jump; + } + /* If don't have to jump any more, skip over the rest of command. */ + else + p += 4; + break; + + case set_number_at: + { + DEBUG_PRINT1 ("EXECUTING set_number_at.\n"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); + p1 = p + mcnt; + EXTRACT_NUMBER_AND_INCR (mcnt, p); +#ifdef _LIBC + DEBUG_PRINT3 (" Setting %p to %d.\n", p1, mcnt); +#else + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p1, mcnt); +#endif + STORE_NUMBER (p1, mcnt); + break; + } + +#if 0 + /* The DEC Alpha C compiler 3.x generates incorrect code for the + test WORDCHAR_P (d - 1) != WORDCHAR_P (d) in the expansion of + AT_WORD_BOUNDARY, so this code is disabled. Expanding the + macro and introducing temporary variables works around the bug. */ + + case wordbound: + DEBUG_PRINT1 ("EXECUTING wordbound.\n"); + if (AT_WORD_BOUNDARY (d)) + break; + goto fail; + + case notwordbound: + DEBUG_PRINT1 ("EXECUTING notwordbound.\n"); + if (AT_WORD_BOUNDARY (d)) + goto fail; + break; +#else + case wordbound: + { + boolean prevchar, thischar; + + DEBUG_PRINT1 ("EXECUTING wordbound.\n"); + if (AT_STRINGS_BEG (d) || AT_STRINGS_END (d)) + break; + + prevchar = WORDCHAR_P (d - 1); + thischar = WORDCHAR_P (d); + if (prevchar != thischar) + break; + goto fail; + } + + case notwordbound: + { + boolean prevchar, thischar; + + DEBUG_PRINT1 ("EXECUTING notwordbound.\n"); + if (AT_STRINGS_BEG (d) || AT_STRINGS_END (d)) + goto fail; + + prevchar = WORDCHAR_P (d - 1); + thischar = WORDCHAR_P (d); + if (prevchar != thischar) + goto fail; + break; + } +#endif + + case wordbeg: + DEBUG_PRINT1 ("EXECUTING wordbeg.\n"); + if (WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1))) + break; + goto fail; + + case wordend: + DEBUG_PRINT1 ("EXECUTING wordend.\n"); + if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1) + && (!WORDCHAR_P (d) || AT_STRINGS_END (d))) + break; + goto fail; + +#ifdef emacs + case before_dot: + DEBUG_PRINT1 ("EXECUTING before_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) >= point) + goto fail; + break; + + case at_dot: + DEBUG_PRINT1 ("EXECUTING at_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) != point) + goto fail; + break; + + case after_dot: + DEBUG_PRINT1 ("EXECUTING after_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) <= point) + goto fail; + break; + + case syntaxspec: + DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt); + mcnt = *p++; + goto matchsyntax; + + case wordchar: + DEBUG_PRINT1 ("EXECUTING Emacs wordchar.\n"); + mcnt = (int) Sword; + matchsyntax: + PREFETCH (); + /* Can't use *d++ here; SYNTAX may be an unsafe macro. */ + d++; + if (SYNTAX (d[-1]) != (enum syntaxcode) mcnt) + goto fail; + SET_REGS_MATCHED (); + break; + + case notsyntaxspec: + DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt); + mcnt = *p++; + goto matchnotsyntax; + + case notwordchar: + DEBUG_PRINT1 ("EXECUTING Emacs notwordchar.\n"); + mcnt = (int) Sword; + matchnotsyntax: + PREFETCH (); + /* Can't use *d++ here; SYNTAX may be an unsafe macro. */ + d++; + if (SYNTAX (d[-1]) == (enum syntaxcode) mcnt) + goto fail; + SET_REGS_MATCHED (); + break; + +#else /* not emacs */ + case wordchar: + DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n"); + PREFETCH (); + if (!WORDCHAR_P (d)) + goto fail; + SET_REGS_MATCHED (); + d++; + break; + + case notwordchar: + DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n"); + PREFETCH (); + if (WORDCHAR_P (d)) + goto fail; + SET_REGS_MATCHED (); + d++; + break; +#endif /* not emacs */ + + default: + abort (); + } + continue; /* Successfully executed one pattern command; keep going. */ + + + /* We goto here if a matching operation fails. */ + fail: + if (!FAIL_STACK_EMPTY ()) + { /* A restart point is known. Restore to that state. */ + DEBUG_PRINT1 ("\nFAIL:\n"); + POP_FAILURE_POINT (d, p, + lowest_active_reg, highest_active_reg, + regstart, regend, reg_info); + + /* If this failure point is a dummy, try the next one. */ + if (!p) + goto fail; + + /* If we failed to the end of the pattern, don't examine *p. */ + assert (p <= pend); + if (p < pend) + { + boolean is_a_jump_n = false; + + /* If failed to a backwards jump that's part of a repetition + loop, need to pop this failure point and use the next one. */ + switch ((re_opcode_t) *p) + { + case jump_n: + is_a_jump_n = true; + case maybe_pop_jump: + case pop_failure_jump: + case jump: + p1 = p + 1; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + + if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n) + || (!is_a_jump_n + && (re_opcode_t) *p1 == on_failure_jump)) + goto fail; + break; + default: + /* do nothing */ ; + } + } + + if (d >= string1 && d <= end1) + dend = end_match_1; + } + else + break; /* Matching at this starting point really fails. */ + } /* for (;;) */ + + if (best_regs_set) + goto restore_best_regs; + + FREE_VARIABLES (); + + return -1; /* Failure to match. */ +} /* re_match_2 */ + +/* Subroutine definitions for re_match_2. */ + + +/* We are passed P pointing to a register number after a start_memory. + + Return true if the pattern up to the corresponding stop_memory can + match the empty string, and false otherwise. + + If we find the matching stop_memory, sets P to point to one past its number. + Otherwise, sets P to an undefined byte less than or equal to END. + + We don't handle duplicates properly (yet). */ + +static boolean +group_match_null_string_p (p, end, reg_info) + unsigned char **p, *end; + register_info_type *reg_info; +{ + int mcnt; + /* Point to after the args to the start_memory. */ + unsigned char *p1 = *p + 2; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and return true or + false, as appropriate, when we get to one that can't, or to the + matching stop_memory. */ + + switch ((re_opcode_t) *p1) + { + /* Could be either a loop or a series of alternatives. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + /* If the next operation is not a jump backwards in the + pattern. */ + + if (mcnt >= 0) + { + /* Go through the on_failure_jumps of the alternatives, + seeing if any of the alternatives cannot match nothing. + The last alternative starts with only a jump, + whereas the rest start with on_failure_jump and end + with a jump, e.g., here is the pattern for `a|b|c': + + /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6 + /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3 + /exactn/1/c + + So, we have to first go through the first (n-1) + alternatives and then deal with the last one separately. */ + + + /* Deal with the first (n-1) alternatives, which start + with an on_failure_jump (see above) that jumps to right + past a jump_past_alt. */ + + while ((re_opcode_t) p1[mcnt-3] == jump_past_alt) + { + /* `mcnt' holds how many bytes long the alternative + is, including the ending `jump_past_alt' and + its number. */ + + if (!alt_match_null_string_p (p1, p1 + mcnt - 3, + reg_info)) + return false; + + /* Move to right after this alternative, including the + jump_past_alt. */ + p1 += mcnt; + + /* Break if it's the beginning of an n-th alternative + that doesn't begin with an on_failure_jump. */ + if ((re_opcode_t) *p1 != on_failure_jump) + break; + + /* Still have to check that it's not an n-th + alternative that starts with an on_failure_jump. */ + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if ((re_opcode_t) p1[mcnt-3] != jump_past_alt) + { + /* Get to the beginning of the n-th alternative. */ + p1 -= 3; + break; + } + } + + /* Deal with the last alternative: go back and get number + of the `jump_past_alt' just before it. `mcnt' contains + the length of the alternative. */ + EXTRACT_NUMBER (mcnt, p1 - 2); + + if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info)) + return false; + + p1 += mcnt; /* Get past the n-th alternative. */ + } /* if mcnt > 0 */ + break; + + + case stop_memory: + assert (p1[1] == **p); + *p = p1 + 2; + return true; + + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return false; +} /* group_match_null_string_p */ + + +/* Similar to group_match_null_string_p, but doesn't deal with alternatives: + It expects P to be the first byte of a single alternative and END one + byte past the last. The alternative can contain groups. */ + +static boolean +alt_match_null_string_p (p, end, reg_info) + unsigned char *p, *end; + register_info_type *reg_info; +{ + int mcnt; + unsigned char *p1 = p; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and break when we get + to one that can't. */ + + switch ((re_opcode_t) *p1) + { + /* It's a loop. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + break; + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return true; +} /* alt_match_null_string_p */ + + +/* Deals with the ops common to group_match_null_string_p and + alt_match_null_string_p. + + Sets P to one after the op and its arguments, if any. */ + +static boolean +common_op_match_null_string_p (p, end, reg_info) + unsigned char **p, *end; + register_info_type *reg_info; +{ + int mcnt; + boolean ret; + int reg_no; + unsigned char *p1 = *p; + + switch ((re_opcode_t) *p1++) + { + case no_op: + case begline: + case endline: + case begbuf: + case endbuf: + case wordbeg: + case wordend: + case wordbound: + case notwordbound: +#ifdef emacs + case before_dot: + case at_dot: + case after_dot: +#endif + break; + + case start_memory: + reg_no = *p1; + assert (reg_no > 0 && reg_no <= MAX_REGNUM); + ret = group_match_null_string_p (&p1, end, reg_info); + + /* Have to set this here in case we're checking a group which + contains a group and a back reference to it. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret; + + if (!ret) + return false; + break; + + /* If this is an optimized succeed_n for zero times, make the jump. */ + case jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (mcnt >= 0) + p1 += mcnt; + else + return false; + break; + + case succeed_n: + /* Get to the number of times to succeed. */ + p1 += 2; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + if (mcnt == 0) + { + p1 -= 4; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + } + else + return false; + break; + + case duplicate: + if (!REG_MATCH_NULL_STRING_P (reg_info[*p1])) + return false; + break; + + case set_number_at: + p1 += 4; + + default: + /* All other opcodes mean we cannot match the empty string. */ + return false; + } + + *p = p1; + return true; +} /* common_op_match_null_string_p */ + + +/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN + bytes; nonzero otherwise. */ + +static int +bcmp_translate (s1, s2, len, translate) + const char *s1, *s2; + register int len; + RE_TRANSLATE_TYPE translate; +{ + register const unsigned char *p1 = (const unsigned char *) s1; + register const unsigned char *p2 = (const unsigned char *) s2; + while (len) + { + if (translate[*p1++] != translate[*p2++]) return 1; + len--; + } + return 0; +} + +/* Entry points for GNU code. */ + +/* re_compile_pattern is the GNU regular expression compiler: it + compiles PATTERN (of length SIZE) and puts the result in BUFP. + Returns 0 if the pattern was valid, otherwise an error string. + + Assumes the `allocated' (and perhaps `buffer') and `translate' fields + are set in BUFP on entry. + + We call regex_compile to do the actual compilation. */ + +_EXPORT const char * +re_compile_pattern (pattern, length, bufp) + const char *pattern; + size_t length; + struct re_pattern_buffer *bufp; +{ + reg_errcode_t ret; + + /* GNU code is written to assume at least RE_NREGS registers will be set + (and at least one extra will be -1). */ + bufp->regs_allocated = REGS_UNALLOCATED; + + /* And GNU code determines whether or not to get register information + by passing null for the REGS argument to re_match, etc., not by + setting no_sub. */ + bufp->no_sub = 0; + + /* Match anchors at newline. */ + bufp->newline_anchor = 1; + + ret = regex_compile (pattern, length, re_syntax_options, bufp); + + if (!ret) + return NULL; + return gettext (re_error_msgid[(int) ret]); +} +#ifdef _LIBC +weak_alias (__re_compile_pattern, re_compile_pattern) +#endif + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them unless specifically requested. */ + +#if defined _REGEX_RE_COMP || defined _LIBC + +/* BSD has one and only one pattern buffer. */ +static struct re_pattern_buffer re_comp_buf; + +char * +#ifdef _LIBC +/* Make these definitions weak in libc, so POSIX programs can redefine + these names if they don't use our functions, and still use + regcomp/regexec below without link errors. */ +weak_function +#endif +re_comp (s) + const char *s; +{ + reg_errcode_t ret; + + if (!s) + { + if (!re_comp_buf.buffer) + return gettext ("No previous regular expression"); + return 0; + } + + if (!re_comp_buf.buffer) + { + re_comp_buf.buffer = (unsigned char *) malloc (200); + if (re_comp_buf.buffer == NULL) + return (char *) gettext (re_error_msgid[(int) REG_ESPACE]); + re_comp_buf.allocated = 200; + + re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH); + if (re_comp_buf.fastmap == NULL) + return (char *) gettext (re_error_msgid[(int) REG_ESPACE]); + } + + /* Since `re_exec' always passes NULL for the `regs' argument, we + don't need to initialize the pattern buffer fields which affect it. */ + + /* Match anchors at newlines. */ + re_comp_buf.newline_anchor = 1; + + ret = regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf); + + if (!ret) + return NULL; + + /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */ + return (char *) gettext (re_error_msgid[(int) ret]); +} + + +int +#ifdef _LIBC +weak_function +#endif +re_exec (s) + const char *s; +{ + const int len = strlen (s); + return + 0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0); +} + +#endif /* _REGEX_RE_COMP */ + +/* POSIX.2 functions. Don't define these for Emacs. */ + +#ifndef emacs + +/* regcomp takes a regular expression as a string and compiles it. + + PREG is a regex_t *. We do not expect any fields to be initialized, + since POSIX says we shouldn't. Thus, we set + + `buffer' to the compiled pattern; + `used' to the length of the compiled pattern; + `syntax' to RE_SYNTAX_POSIX_EXTENDED if the + REG_EXTENDED bit in CFLAGS is set; otherwise, to + RE_SYNTAX_POSIX_BASIC; + `newline_anchor' to REG_NEWLINE being set in CFLAGS; + `fastmap' and `fastmap_accurate' to zero; + `re_nsub' to the number of subexpressions in PATTERN. + + PATTERN is the address of the pattern string. + + CFLAGS is a series of bits which affect compilation. + + If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we + use POSIX basic syntax. + + If REG_NEWLINE is set, then . and [^...] don't match newline. + Also, regexec will try a match beginning after every newline. + + If REG_ICASE is set, then we considers upper- and lowercase + versions of letters to be equivalent when matching. + + If REG_NOSUB is set, then when PREG is passed to regexec, that + routine will report only success or failure, and nothing about the + registers. + + It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for + the return codes and their meanings.) */ + +_EXPORT int +regcomp (preg, pattern, cflags) + regex_t *preg; + const char *pattern; + int cflags; +{ + reg_errcode_t ret; + reg_syntax_t syntax + = (cflags & REG_EXTENDED) ? + RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC; + + /* regex_compile will allocate the space for the compiled pattern. */ + preg->buffer = 0; + preg->allocated = 0; + preg->used = 0; + + /* Don't bother to use a fastmap when searching. This simplifies the + REG_NEWLINE case: if we used a fastmap, we'd have to put all the + characters after newlines into the fastmap. This way, we just try + every character. */ + preg->fastmap = 0; + + if (cflags & REG_ICASE) + { + unsigned i; + + preg->translate + = (RE_TRANSLATE_TYPE) malloc (CHAR_SET_SIZE + * sizeof (*(RE_TRANSLATE_TYPE)0)); + if (preg->translate == NULL) + return (int) REG_ESPACE; + + /* Map uppercase characters to corresponding lowercase ones. */ + for (i = 0; i < CHAR_SET_SIZE; i++) + preg->translate[i] = ISUPPER (i) ? tolower (i) : i; + } + else + preg->translate = NULL; + + /* If REG_NEWLINE is set, newlines are treated differently. */ + if (cflags & REG_NEWLINE) + { /* REG_NEWLINE implies neither . nor [^...] match newline. */ + syntax &= ~RE_DOT_NEWLINE; + syntax |= RE_HAT_LISTS_NOT_NEWLINE; + /* It also changes the matching behavior. */ + preg->newline_anchor = 1; + } + else + preg->newline_anchor = 0; + + preg->no_sub = !!(cflags & REG_NOSUB); + + /* POSIX says a null character in the pattern terminates it, so we + can use strlen here in compiling the pattern. */ + ret = regex_compile (pattern, strlen (pattern), syntax, preg); + + /* POSIX doesn't distinguish between an unmatched open-group and an + unmatched close-group: both are REG_EPAREN. */ + if (ret == REG_ERPAREN) ret = REG_EPAREN; + + return (int) ret; +} +#ifdef _LIBC +weak_alias (__regcomp, regcomp) +#endif + + +/* regexec searches for a given pattern, specified by PREG, in the + string STRING. + + If NMATCH is zero or REG_NOSUB was set in the cflags argument to + `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at + least NMATCH elements, and we set them to the offsets of the + corresponding matched substrings. + + EFLAGS specifies `execution flags' which affect matching: if + REG_NOTBOL is set, then ^ does not match at the beginning of the + string; if REG_NOTEOL is set, then $ does not match at the end. + + We return 0 if we find a match and REG_NOMATCH if not. */ + +_EXPORT int +regexec (preg, string, nmatch, pmatch, eflags) + const regex_t *preg; + const char *string; + size_t nmatch; + regmatch_t pmatch[]; + int eflags; +{ + int ret; + struct re_registers regs; + regex_t private_preg; + int len = strlen (string); + boolean want_reg_info = !preg->no_sub && nmatch > 0; + + private_preg = *preg; + + private_preg.not_bol = !!(eflags & REG_NOTBOL); + private_preg.not_eol = !!(eflags & REG_NOTEOL); + + /* The user has told us exactly how many registers to return + information about, via `nmatch'. We have to pass that on to the + matching routines. */ + private_preg.regs_allocated = REGS_FIXED; + + if (want_reg_info) + { + regs.num_regs = nmatch; + regs.start = TALLOC (nmatch, regoff_t); + regs.end = TALLOC (nmatch, regoff_t); + if (regs.start == NULL || regs.end == NULL) + return (int) REG_NOMATCH; + } + + /* Perform the searching operation. */ + ret = re_search (&private_preg, string, len, + /* start: */ 0, /* range: */ len, + want_reg_info ? ®s : (struct re_registers *) 0); + + /* Copy the register information to the POSIX structure. */ + if (want_reg_info) + { + if (ret >= 0) + { + unsigned r; + + for (r = 0; r < nmatch; r++) + { + pmatch[r].rm_so = regs.start[r]; + pmatch[r].rm_eo = regs.end[r]; + } + } + + /* If we needed the temporary register info, free the space now. */ + free (regs.start); + free (regs.end); + } + + /* We want zero return to mean success, unlike `re_search'. */ + return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH; +} +#ifdef _LIBC +weak_alias (__regexec, regexec) +#endif + + +/* Returns a message corresponding to an error code, ERRCODE, returned + from either regcomp or regexec. We don't use PREG here. */ + +_EXPORT size_t +regerror (errcode, preg, errbuf, errbuf_size) + int errcode; + const regex_t *preg; + char *errbuf; + size_t errbuf_size; +{ + const char *msg; + size_t msg_size; + + preg = NULL; //---make mwcc stop complaining + + if (errcode < 0 + || errcode >= (int) (sizeof (re_error_msgid) + / sizeof (re_error_msgid[0]))) + /* Only error codes returned by the rest of the code should be passed + to this routine. If we are given anything else, or if other regex + code generates an invalid error code, then the program has a bug. + Dump core so we can fix it. */ + abort (); + + msg = gettext (re_error_msgid[errcode]); + + msg_size = strlen (msg) + 1; /* Includes the null. */ + + if (errbuf_size != 0) + { + if (msg_size > errbuf_size) + { + memcpy (errbuf, msg, errbuf_size - 1); + errbuf[errbuf_size - 1] = 0; + } + else + memcpy (errbuf, msg, msg_size); + } + + return msg_size; +} +#ifdef _LIBC +weak_alias (__regerror, regerror) +#endif + + +/* Free dynamically allocated space used by PREG. */ + +_EXPORT void +regfree (preg) + regex_t *preg; +{ + if (preg->buffer != NULL) + free (preg->buffer); + preg->buffer = NULL; + + preg->allocated = 0; + preg->used = 0; + + if (preg->fastmap != NULL) + free (preg->fastmap); + preg->fastmap = NULL; + preg->fastmap_accurate = 0; + + if (preg->translate != NULL) + free (preg->translate); + preg->translate = NULL; +} +#ifdef _LIBC +weak_alias (__regfree, regfree) +#endif + +#endif /* not emacs */ diff --git a/src/prefs/Jamfile b/src/prefs/Jamfile index b275211343..a02a80ba4f 100644 --- a/src/prefs/Jamfile +++ b/src/prefs/Jamfile @@ -10,6 +10,7 @@ SubInclude OBOS_TOP src prefs filetypes ; SubInclude OBOS_TOP src prefs fonts ; SubInclude OBOS_TOP src prefs keyboard ; SubInclude OBOS_TOP src prefs keymap ; +SubInclude OBOS_TOP src prefs mail ; SubInclude OBOS_TOP src prefs media ; SubInclude OBOS_TOP src prefs menu ; SubInclude OBOS_TOP src prefs mouse ; diff --git a/src/prefs/mail/Account.cpp b/src/prefs/mail/Account.cpp new file mode 100644 index 0000000000..f0594c9518 --- /dev/null +++ b/src/prefs/mail/Account.cpp @@ -0,0 +1,671 @@ +/* Account - provides an "account" view on the mail chains +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include "Account.h" +#include "ConfigViews.h" +#include "CenterContainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static BList gAccounts; +static BListView *gListView; +static BView *gConfigView; + +const char *kInboundFilterAddOnPath = "mail_daemon/inbound_filters"; +const char *kOutboundFilterAddOnPath = "mail_daemon/outbound_filters"; +const char *kSystemFilterAddOnPath = "mail_daemon/system_filters"; +const char *kInboundProtocolAddOnPath = "mail_daemon/inbound_protocols"; +const char *kOutboundProtocolAddOnPath = "mail_daemon/outbound_protocols"; + + +//--------------------------------------------------------------------------------------- +// #pragma mark - + + +AccountItem::AccountItem(const char *label,Account *account,int32 type) + : BStringItem(label), + account(account), + type(type) +{ +} + + +AccountItem::~AccountItem() +{ +} + + +void AccountItem::Update(BView *owner, const BFont *font) +{ + if (type == ACCOUNT_ITEM) + font = be_bold_font; + + BStringItem::Update(owner,font); +} + + +void AccountItem::DrawItem(BView *owner, BRect rect, bool complete) +{ + owner->PushState(); + if (type == ACCOUNT_ITEM) + { +// BFont font; +// owner->GetFont(&font); +// font.SetFace(B_BOLD_FACE); + owner->SetFont(be_bold_font); //&font); + } + BStringItem::DrawItem(owner,rect,complete); + owner->PopState(); +} + + +//--------------------------------------------------------------------------------------- +// #pragma mark - + + +Account::Account(BMailChain *inbound,BMailChain *outbound) + : fInbound(inbound), + fOutbound(outbound), + + fAccountItem(NULL), + fInboundItem(NULL), + fOutboundItem(NULL), + fFilterItem(NULL) +{ + fSettings = fInbound ? fInbound : fOutbound; + + BString label; + if (fSettings) + label << fSettings->Name(); + else + label << MDR_DIALECT_CHOICE ("Unnamed","名称未定"); + fAccountItem = new AccountItem(label.String(),this,ACCOUNT_ITEM); + + fInboundItem = new AccountItem(MDR_DIALECT_CHOICE (" · Incoming"," - 受信"),this,INBOUND_ITEM); + fOutboundItem = new AccountItem(MDR_DIALECT_CHOICE (" · Outgoing"," - 送信"),this,OUTBOUND_ITEM); + fFilterItem = new AccountItem(MDR_DIALECT_CHOICE (" · E-mail Filters"," - フィルタ"),this,FILTER_ITEM); +} + + +Account::~Account() +{ + if (gListView) + { + gListView->RemoveItem(fAccountItem); + gListView->RemoveItem(fInboundItem); + gListView->RemoveItem(fOutboundItem); + gListView->RemoveItem(fFilterItem); + } + delete fAccountItem; delete fFilterItem; + delete fInboundItem; delete fOutboundItem; + + delete fInbound; + delete fOutbound; +} + + +void Account::AddToListView() +{ + if (!gListView) + return; + + gListView->AddItem(fAccountItem); + + if (fInbound) + gListView->AddItem(fInboundItem); + + if (fOutbound) + gListView->AddItem(fOutboundItem); + + if (fOutbound || fInbound) + gListView->AddItem(fFilterItem); +} + + +void Account::SetName(const char *name) +{ + if (fInbound) + fInbound->SetName(name); + if (fOutbound) + fOutbound->SetName(name); + + if (name && *name) + { + fAccountItem->SetText(name); + gListView->InvalidateItem(gListView->IndexOf(fAccountItem)); + } +} + + +const char *Account::Name() const +{ + if (fInbound) + return fInbound->Name(); + if (fOutbound) + return fOutbound->Name(); + + return NULL; +} + + +void Account::SetRealName(const char *realName) +{ + BMessage *msg; + if (fInbound && (msg = fInbound->MetaData()) != NULL) + { + if (msg->ReplaceString("real_name",realName) < B_OK) + msg->AddString("real_name",realName); + } + if (fOutbound && (msg = fOutbound->MetaData()) != NULL) + { + if (msg->ReplaceString("real_name",realName) < B_OK) + msg->AddString("real_name",realName); + } +} + + +const char *Account::RealName() const +{ + if (fInbound && fInbound->MetaData()) + return fInbound->MetaData()->FindString("real_name"); + if (fOutbound && fOutbound->MetaData()) + return fOutbound->MetaData()->FindString("real_name"); + + if (fInbound) + fInbound->MetaData()->PrintToStream(); + + return NULL; +} + + +void Account::SetReturnAddress(const char *returnAddress) +{ + BMessage *msg; + if (fInbound && (msg = fInbound->MetaData()) != NULL) + { + if (msg->ReplaceString("reply_to",returnAddress) < B_OK) + msg->AddString("reply_to",returnAddress); + } + if (fOutbound && (msg = fOutbound->MetaData()) != NULL) + { + if (msg->ReplaceString("reply_to",returnAddress) < B_OK) + msg->AddString("reply_to",returnAddress); + } +} + + +const char *Account::ReturnAddress() const +{ + if (fInbound && fInbound->MetaData()) + return fInbound->MetaData()->FindString("reply_to"); + if (fOutbound && fOutbound->MetaData()) + return fOutbound->MetaData()->FindString("reply_to"); + + return NULL; +} + + +void Account::CopyMetaData(BMailChain *targetChain, BMailChain *sourceChain) +{ + BMessage *otherMsg, *thisMsg; + if (sourceChain && (otherMsg = sourceChain->MetaData()) != NULL + && (thisMsg = targetChain->MetaData()) != NULL) + { + const char *string; + if ((string = otherMsg->FindString("real_name")) != NULL) + { + if (thisMsg->ReplaceString("real_name",string) < B_OK) + thisMsg->AddString("real_name",string); + } + if ((string = otherMsg->FindString("reply_to")) != NULL) + { + if (thisMsg->ReplaceString("reply_to",string) < B_OK) + thisMsg->AddString("reply_to",string); + } + if ((string = sourceChain->Name()) != NULL) + targetChain->SetName(string); + } +} + + +void Account::CreateInbound() +{ + + if (!(fInbound = NewMailChain())) + { + (new BAlert( + MDR_DIALECT_CHOICE ("E-mail","メール"), + MDR_DIALECT_CHOICE ("Could not create inbound chain.","受信チェーンは作成できませんでした。"), + MDR_DIALECT_CHOICE ("Ok","了解")))->Go(); + return; + } + fInbound->SetChainDirection(inbound); + + BPath path,addOnPath; + find_directory(B_USER_ADDONS_DIRECTORY,&addOnPath); + + BMessage msg; + entry_ref ref; + + // Protocol + path = addOnPath; + path.Append(kInboundProtocolAddOnPath); + path.Append("POP3"); + if (!BEntry(path.Path()).Exists()) { + find_directory(B_BEOS_ADDONS_DIRECTORY,&path); + path.Append(kInboundProtocolAddOnPath); + path.Append("POP3"); + } + BEntry(path.Path()).GetRef(&ref); + fInbound->AddFilter(msg,ref); + + // Message Parser + path = addOnPath; + path.Append(kSystemFilterAddOnPath); + path.Append("Message Parser"); + if (!BEntry(path.Path()).Exists()) { + find_directory(B_BEOS_ADDONS_DIRECTORY,&path); + path.Append(kSystemFilterAddOnPath); + path.Append("Message Parser"); + } + BEntry(path.Path()).GetRef(&ref); + fInbound->AddFilter(msg,ref); + + // New Mail Notification + path = addOnPath; + path.Append(kSystemFilterAddOnPath); + path.Append(MDR_DIALECT_CHOICE ("New Mail Notification", "着信通知方法")); + if (!BEntry(path.Path()).Exists()) { + find_directory(B_BEOS_ADDONS_DIRECTORY,&path); + path.Append(kSystemFilterAddOnPath); + path.Append(MDR_DIALECT_CHOICE ("New Mail Notification", "着信通知方法")); + } + BEntry(path.Path()).GetRef(&ref); + fInbound->AddFilter(msg,ref); + + // Inbox + path = addOnPath; + path.Append(kSystemFilterAddOnPath); + path.Append(MDR_DIALECT_CHOICE ("Inbox", "受信箱")); + if (!BEntry(path.Path()).Exists()) { + find_directory(B_BEOS_ADDONS_DIRECTORY,&path); + path.Append(kSystemFilterAddOnPath); + path.Append(MDR_DIALECT_CHOICE ("Inbox", "受信箱")); + } + BEntry(path.Path()).GetRef(&ref); + fInbound->AddFilter(msg,ref); + + // set already made account settings + CopyMetaData(fInbound,fOutbound); +} + + +void Account::CreateOutbound() +{ + + if (!(fOutbound = NewMailChain())) + { + (new BAlert( + MDR_DIALECT_CHOICE ("E-mail","メール"), + MDR_DIALECT_CHOICE ("Could not create outbound chain.","送信チェーンは作成できませんでした。"), + MDR_DIALECT_CHOICE ("Ok","了解")))->Go(); + return; + } + fOutbound->SetChainDirection(outbound); + + BPath path,addOnPath; + find_directory(B_USER_ADDONS_DIRECTORY,&addOnPath); + + BMessage msg; + entry_ref ref; + + path = addOnPath; + path.Append(kSystemFilterAddOnPath); + path.Append(MDR_DIALECT_CHOICE ("Outbox", "送信箱")); + if (!BEntry(path.Path()).Exists()) { + find_directory(B_BEOS_ADDONS_DIRECTORY,&path); + path.Append(kSystemFilterAddOnPath); + path.Append(MDR_DIALECT_CHOICE ("Outbox", "送信箱")); + } + BEntry(path.Path()).GetRef(&ref); + fOutbound->AddFilter(msg,ref); + + path = addOnPath; + path.Append(kOutboundProtocolAddOnPath); + path.Append("SMTP"); + if (!BEntry(path.Path()).Exists()) { + find_directory(B_BEOS_ADDONS_DIRECTORY,&path); + path.Append(kOutboundProtocolAddOnPath); + path.Append("SMTP"); + } + BEntry(path.Path()).GetRef(&ref); + fOutbound->AddFilter(msg,ref); + + // set already made account settings + CopyMetaData(fOutbound,fInbound); +} + + +void Account::SetType(int32 type) +{ + if (type < INBOUND_TYPE || type > IN_AND_OUTBOUND_TYPE) + return; + + int32 index = gListView->IndexOf(fAccountItem) + 1; + + // missing inbound + if ((type == INBOUND_TYPE || type == IN_AND_OUTBOUND_TYPE) && !Inbound()) + { + if (!fInbound) + CreateInbound(); + + if (fInbound) + gListView->AddItem(fInboundItem,index); + } + if (Inbound()) + index++; + + // missing outbound + if ((type == OUTBOUND_TYPE || type == IN_AND_OUTBOUND_TYPE) && !Outbound()) + { + if (!fOutbound) + CreateOutbound(); + + if (fOutbound) + gListView->AddItem(fOutboundItem,index); + } + if (Outbound()) + index++; + + // missing filter + if (!gListView->HasItem(fFilterItem)) + gListView->AddItem(fFilterItem,index); + + // remove inbound + if (type == OUTBOUND_TYPE && Inbound()) + gListView->RemoveItem(fInboundItem); + + // remove outbound + if (type == INBOUND_TYPE && Outbound()) + gListView->RemoveItem(fOutboundItem); +} + + +int32 Account::Type() const +{ + return Inbound() ? (Outbound() ? 2 : 0) : (Outbound() ? 1 : -1); +} + + +void Account::Selected(int32 type) +{ + if (!gConfigView) + return; + + gConfigView->Hide(); + ((CenterContainer *)gConfigView)->DeleteChildren(); + + switch (type) + { + case ACCOUNT_ITEM: + gConfigView->AddChild(new AccountConfigView(gConfigView->Bounds(),this)); + break; + case INBOUND_ITEM: + { + if (!fInbound) + break; + + int32 count = fInbound->CountFilters(); + for (int32 i = 0;;i++) + { + BMessage *msg = new BMessage(); + entry_ref *ref = new entry_ref; + + // we just want to have the first and the last two filters: + // Protocol, Parser, Notifier, Folder + if (i == 2) + { + i = count - 2; + if (i < 2) // defensive programming... + i = 3; + } + + if (fInbound->GetFilter(i,msg,ref) < B_OK) + { + delete msg; + delete ref; + break; + } + + // the filter view takes ownership of "msg" and "ref" + FilterConfigView *view; + if (i == 0) + view = new ProtocolsConfigView(fInbound,i,msg,ref); + else + view = new FilterConfigView(fInbound,i,msg,ref); + + if (view->InitCheck() >= B_OK) + gConfigView->AddChild(view); + else + delete view; + } + break; + } + case OUTBOUND_ITEM: + { + if (!fOutbound) + break; + + // we just want to have the first and the last filter here + int32 count = fOutbound->CountFilters(); + for (int32 i = 0;i < count;i += count-1) + { + BMessage *msg = new BMessage(); + entry_ref *ref = new entry_ref; + + if (fOutbound->GetFilter(i,msg,ref) < B_OK) + { + delete msg; + delete ref; + break; + } + + // the filter view takes ownership of "msg" and "ref" + if (FilterConfigView *view = new FilterConfigView(fOutbound,i,msg,ref)) + { + if (view->InitCheck() >= B_OK) + gConfigView->AddChild(view); + else + delete view; + } + } + break; + } + case FILTER_ITEM: + { + gConfigView->AddChild(new FiltersConfigView(gConfigView->Bounds(),this)); + break; + } + } + ((CenterContainer *)gConfigView)->Layout(); + gConfigView->Show(); +} + + +void Account::Remove(int32 type) +{ + // this should only be called if necessary, but if it's used + // in the GUI, this will always be the case + ((CenterContainer *)gConfigView)->DeleteChildren(); + + switch (type) + { + case ACCOUNT_ITEM: + gListView->RemoveItem(fAccountItem); + gListView->RemoveItem(fInboundItem); + gListView->RemoveItem(fOutboundItem); + gListView->RemoveItem(fFilterItem); + return; + case INBOUND_ITEM: + if (!fInbound || !gListView) + return; + + gListView->RemoveItem(fInboundItem); + if (!Outbound()) + gListView->RemoveItem(fFilterItem); + break; + case OUTBOUND_ITEM: + if (!fOutbound || !gListView) + return; + + gListView->RemoveItem(fOutboundItem); + if (!Inbound()) + gListView->RemoveItem(fFilterItem); + break; + } +} + + +BMailChain *Account::Inbound() const +{ + return gListView && gListView->HasItem(fInboundItem) ? fInbound : NULL; +} + + +BMailChain *Account::Outbound() const +{ + return gListView && gListView->HasItem(fOutboundItem) ? fOutbound : NULL; +} + + +void Account::Save() +{ + if (Inbound()) + fInbound->Save(); + else + Delete(INBOUND_TYPE); + + if (Outbound()) + fOutbound->Save(); + else + Delete(OUTBOUND_TYPE); +} + + +void Account::Delete(int32 type) +{ + if (fInbound && (type == INBOUND_TYPE || type == IN_AND_OUTBOUND_TYPE)) + fInbound->Delete(); + + if (fOutbound && (type == OUTBOUND_TYPE || type == IN_AND_OUTBOUND_TYPE)) + fOutbound->Delete(); +} + + +// #pragma mark - + + +int Accounts::Compare(const void *_a, const void *_b) +{ + const char *a = (*(Account **)_a)->Name(); + const char *b = (*(Account **)_b)->Name(); + + if (!a) + return b != 0; + + return strcasecmp(a,b); +} + + +void Accounts::Create(BListView *listView, BView *configView) +{ + gListView = listView; + gConfigView = configView; + + BList inbound,outbound; + + GetInboundMailChains(&inbound); + GetOutboundMailChains(&outbound); + + // create inbound accounts and assign matching outbound chains + + for (int32 i = inbound.CountItems();i-- > 0;) + { + BMailChain *inChain = (BMailChain *)inbound.ItemAt(i); + BMailChain *outChain = NULL; + for (int32 j = outbound.CountItems();j-- > 0;) + { + outChain = (BMailChain *)outbound.ItemAt(j); + + if (!strcmp(inChain->Name(),outChain->Name())) + break; + outChain = NULL; + } + gAccounts.AddItem(new Account(inChain,outChain)); + inbound.RemoveItem(i); + if (outChain) + outbound.RemoveItem(outChain); + } + + // create remaining outbound only accounts + + for (int32 i = outbound.CountItems();i-- > 0;) + { + BMailChain *outChain = (BMailChain *)outbound.ItemAt(i); + + gAccounts.AddItem(new Account(NULL,outChain)); + outbound.RemoveItem(i); + } + + // sort the list alphabetically + gAccounts.SortItems(Accounts::Compare); + + for (int32 i = 0;Account *account = (Account *)gAccounts.ItemAt(i);i++) + account->AddToListView(); +} + + +void Accounts::NewAccount() +{ + Account *account = new Account(); + gAccounts.AddItem(account); + account->AddToListView(); +} + + +void Accounts::Save() +{ + for (int32 i = gAccounts.CountItems();i-- > 0;) + ((Account *)gAccounts.ItemAt(i))->Save(); +} + + +void Accounts::Delete() +{ + for (int32 i = gAccounts.CountItems();i-- > 0;) + { + Account *account = (Account *)gAccounts.RemoveItem(i); + delete account; + } +} + diff --git a/src/prefs/mail/Account.h b/src/prefs/mail/Account.h new file mode 100644 index 0000000000..e4772f7c95 --- /dev/null +++ b/src/prefs/mail/Account.h @@ -0,0 +1,104 @@ +#ifndef ACCOUNT_H +#define ACCOUNT_H +/* Account - provides an "account" view on the mail chains +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + +class BView; +class BListView; +class BStringItem; +class BMailChain; + +class Account; +class Accounts; + + +enum item_types +{ + ACCOUNT_ITEM = 0, + INBOUND_ITEM, + OUTBOUND_ITEM, + FILTER_ITEM +}; + +class AccountItem : public BStringItem +{ + public: + AccountItem(const char *label,Account *account,int32 type); + ~AccountItem(); + + virtual void Update(BView *owner,const BFont *font); + virtual void DrawItem(BView *owner,BRect rect,bool complete); + + Account *account; + int32 type; +}; + + +//------------------------------------------------------------- + + +enum account_types +{ + INBOUND_TYPE = 0, + OUTBOUND_TYPE, + IN_AND_OUTBOUND_TYPE +}; + +class Account +{ + public: + Account(BMailChain *inbound = NULL,BMailChain *outbound = NULL); + ~Account(); + + void SetName(const char *name); + const char *Name() const; + + void SetRealName(const char *realName); + const char *RealName() const; + + void SetReturnAddress(const char *returnAddress); + const char *ReturnAddress() const; + + void Selected(int32 type); + void Remove(int32 type); + + void SetType(int32 type); + int32 Type() const; + + BMailChain *Inbound() const; + BMailChain *Outbound() const; + + void Save(); + void Delete(int32 type = IN_AND_OUTBOUND_TYPE); + + private: + friend Accounts; + void AddToListView(); + private: + void CreateInbound(); + void CreateOutbound(); + void CopyMetaData(BMailChain *targetChain, + BMailChain *sourceChain); + + BMailChain *fSettings, *fInbound, *fOutbound; + AccountItem *fAccountItem, *fInboundItem, *fOutboundItem, *fFilterItem; +}; + +class Accounts +{ + public: + static void Create(BListView *listView,BView *configView); + static void NewAccount(); + static void Save(); + static void Delete(); + + private: + static int Compare(const void *,const void *); +}; + +#endif /* ACCOUNT_H */ diff --git a/src/prefs/mail/CenterContainer.cpp b/src/prefs/mail/CenterContainer.cpp new file mode 100644 index 0000000000..1467f2c4a1 --- /dev/null +++ b/src/prefs/mail/CenterContainer.cpp @@ -0,0 +1,98 @@ +/* CenterContainer - a container which centers its contents in the middle +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include "CenterContainer.h" +#include + + +CenterContainer::CenterContainer(BRect rect,bool centerHoriz) + : BView(rect,NULL,B_FOLLOW_ALL,0), + fSpacing(7), + fWidth(0), + fCenterHoriz(centerHoriz) +{ +} + + +void CenterContainer::AttachedToWindow() +{ + if (Parent() != NULL) + SetViewColor(Parent()->ViewColor()); +} + + +void CenterContainer::AllAttached() +{ + Layout(); +} + + +void CenterContainer::FrameResized(float width,float height) +{ + Layout(); +} + + +void CenterContainer::GetPreferredSize(float *width, float *height) +{ + // calculate dimensions (and, well, layout views) + if (fWidth == 0) + Layout(); + + if (width) + *width = fWidth; + if (height) + *height = fHeight; +} + + +void CenterContainer::Layout() +{ + // compute the size of all views + fHeight = 0; fWidth = 0; + for (int32 i = 0;BView *view = ChildAt(i);i++) + { + if (i != 0) // the spacing between to items + fHeight += fSpacing; + fHeight += view->Bounds().Height(); + + if (view->Bounds().Width() > fWidth) + fWidth = view->Bounds().Width(); + } + + // layout views + float y = (Bounds().Height() - fHeight) / 2; + for (int32 i = 0;BView *view = ChildAt(i);i++) + { + view->MoveTo(fCenterHoriz ? (Bounds().Width() - view->Bounds().Width()) / 2 + : view->Frame().left, + y); + y += view->Bounds().Height() + fSpacing; + } +} + + +void CenterContainer::SetSpacing(float spacing) +{ + if (fSpacing == spacing) + return; + + fSpacing = spacing; + Layout(); +} + + +void CenterContainer::DeleteChildren() +{ + // remove all child views + for (int32 i = CountChildren();i-- > 0;) + { + BView *view = ChildAt(i); + if (RemoveChild(view)) + delete view; + } +} + diff --git a/src/prefs/mail/CenterContainer.h b/src/prefs/mail/CenterContainer.h new file mode 100644 index 0000000000..624eaf35ad --- /dev/null +++ b/src/prefs/mail/CenterContainer.h @@ -0,0 +1,31 @@ +#ifndef CENTER_CONTAINER_H +#define CENTER_CONTAINER_H +/* CenterContainer - a container which centers its contents in the middle +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include + + +class CenterContainer : public BView +{ + public: + CenterContainer(BRect rect,bool centerHoriz = true); + + virtual void AttachedToWindow(); + virtual void AllAttached(); + virtual void FrameResized(float width, float height); + virtual void GetPreferredSize(float *width, float *height); + + void Layout(); + void SetSpacing(float spacing); + void DeleteChildren(); + + private: + float fSpacing, fWidth, fHeight; + bool fCenterHoriz; +}; + +#endif /* CENTER_CONTAINER_H */ diff --git a/src/prefs/mail/ConfigViews.cpp b/src/prefs/mail/ConfigViews.cpp new file mode 100644 index 0000000000..2a81df70e8 --- /dev/null +++ b/src/prefs/mail/ConfigViews.cpp @@ -0,0 +1,847 @@ +/* ConfigViews - config views for the account, protocols, and filters +** +** Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. +*/ + + +#include "ConfigViews.h" +#include "Account.h" +#include "CenterContainer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +// AccountConfigView +const uint32 kMsgAccountTypeChanged = 'atch'; +const uint32 kMsgAccountNameChanged = 'anmc'; + +// ProtocolsConfigView +const uint32 kMsgProtocolChanged = 'prch'; + +// FiltersConfigView +const uint32 kMsgItemDragged = 'itdr'; +const uint32 kMsgFilterMoved = 'flmv'; +const uint32 kMsgChainSelected = 'chsl'; +const uint32 kMsgAddFilter = 'addf'; +const uint32 kMsgRemoveFilter = 'rmfi'; +const uint32 kMsgFilterSelected = 'fsel'; + + +AccountConfigView::AccountConfigView(BRect rect,Account *account) + : BBox(rect), + fAccount(account) +{ + SetLabel(MDR_DIALECT_CHOICE ("Account Configuration","アカウント設定")); + BMailChain *settings = account->Inbound() ? account->Inbound() : account->Outbound(); + + rect = Bounds().InsetByCopy(8,8); + rect.top += 10; + CenterContainer *view = new CenterContainer(rect,false); + view->SetSpacing(5); + + // determine font height + font_height fontHeight; + view->GetFontHeight(&fontHeight); + int32 height = (int32)(fontHeight.ascent + fontHeight.descent + fontHeight.leading) + 5; + + rect = view->Bounds(); + rect.bottom = height + 5; + + float labelWidth = view->StringWidth(MDR_DIALECT_CHOICE ("Account Name:","アカウント名:")) + 6; + + view->AddChild(fNameControl = new BTextControl(rect,NULL,MDR_DIALECT_CHOICE ("Account Name:","アカウント名:"),NULL,new BMessage(kMsgAccountNameChanged))); + fNameControl->SetDivider(labelWidth); + view->AddChild(fRealNameControl = new BTextControl(rect,NULL,MDR_DIALECT_CHOICE ("Real Name:","名前    :"),NULL,NULL)); + fRealNameControl->SetDivider(labelWidth); + view->AddChild(fReturnAddressControl = new BTextControl(rect,NULL,MDR_DIALECT_CHOICE ("Return Address:","返信アドレス:"),NULL,NULL)); + fReturnAddressControl->SetDivider(labelWidth); +// control->TextView()->HideTyping(true); + + BPopUpMenu *chainsPopUp = new BPopUpMenu(B_EMPTY_STRING); + const char *chainModes[] = { + MDR_DIALECT_CHOICE ("Inbound Only","受信のみ"), + MDR_DIALECT_CHOICE ("Outbound Only","送信のみ"), + MDR_DIALECT_CHOICE ("Inbound & Outbound","送受信")}; + BMenuItem *item; + for (int32 i = 0;i < 3;i++) + chainsPopUp->AddItem(item = new BMenuItem(chainModes[i],new BMessage(kMsgAccountTypeChanged))); + + fTypeField = new BMenuField(rect,NULL,MDR_DIALECT_CHOICE ("Account Type:","用途    :"),chainsPopUp); + fTypeField->SetDivider(labelWidth + 3); + view->AddChild(fTypeField); + + float w,h; + view->GetPreferredSize(&w,&h); + ResizeTo(w + 15,h + 22); + view->ResizeTo(w,h); + + AddChild(view); +} + + +void AccountConfigView::DetachedFromWindow() +{ + fAccount->SetName(fNameControl->Text()); + fAccount->SetRealName(fRealNameControl->Text()); + fAccount->SetReturnAddress(fReturnAddressControl->Text()); +} + + +void AccountConfigView::AttachedToWindow() +{ + UpdateViews(); + fNameControl->SetTarget(this); + fTypeField->Menu()->SetTargetForItems(this); +} + + +void AccountConfigView::MessageReceived(BMessage *msg) +{ + switch (msg->what) + { + case kMsgAccountTypeChanged: + { + int32 index; + if (msg->FindInt32("index",&index) < B_OK) + break; + + if (fAccount->Type() < 0) + { + fNameControl->SetEnabled(true); + fRealNameControl->SetEnabled(true); + fReturnAddressControl->SetEnabled(true); + } + fAccount->SetType(index); + UpdateViews(); + break; + } + case kMsgAccountNameChanged: + fAccount->SetName(fNameControl->Text()); + break; + + default: + BView::MessageReceived(msg); + } +} + + +void AccountConfigView::UpdateViews() +{ + if (!fAccount->Inbound() && !fAccount->Outbound()) + { + if (BMenuItem *item = fTypeField->Menu()->FindMarked()) + item->SetMarked(false); + fTypeField->Menu()->Superitem()->SetLabel(MDR_DIALECT_CHOICE ("