mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-27 10:57:34 +09:00
Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1
This commit is contained in:
parent
23e0d82436
commit
e400f4130a
1564 changed files with 510348 additions and 0 deletions
6
mailnews/extensions/bayesian-spam-filter/moz.build
Normal file
6
mailnews/extensions/bayesian-spam-filter/moz.build
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ['src']
|
||||
11
mailnews/extensions/bayesian-spam-filter/src/moz.build
Normal file
11
mailnews/extensions/bayesian-spam-filter/src/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SOURCES += [
|
||||
'nsBayesianFilter.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'mail'
|
||||
|
||||
2758
mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp
Normal file
2758
mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp
Normal file
File diff suppressed because it is too large
Load diff
404
mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.h
Normal file
404
mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.h
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsBayesianFilter_h__
|
||||
#define nsBayesianFilter_h__
|
||||
|
||||
#include <stdio.h>
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIMsgFilterPlugin.h"
|
||||
#include "nsISemanticUnitScanner.h"
|
||||
#include "PLDHashTable.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsStringGlue.h"
|
||||
#include "nsWeakReference.h"
|
||||
#include "nsIObserver.h"
|
||||
|
||||
// XXX can't simply byte align arenas, must at least 2-byte align.
|
||||
#define PL_ARENA_CONST_ALIGN_MASK 1
|
||||
#include "plarena.h"
|
||||
|
||||
#define DEFAULT_MIN_INTERVAL_BETWEEN_WRITES 15*60*1000
|
||||
|
||||
struct Token;
|
||||
class TokenEnumeration;
|
||||
class TokenAnalyzer;
|
||||
class nsIMsgWindow;
|
||||
class nsIMimeHeaders;
|
||||
class nsIUTF8StringEnumerator;
|
||||
struct BaseToken;
|
||||
struct CorpusToken;
|
||||
|
||||
/**
|
||||
* Helper class to enumerate Token objects in a PLDHashTable
|
||||
* safely and without copying (see bugzilla #174859). The
|
||||
* enumeration is safe to use until an Add()
|
||||
* or Remove() is performed on the table.
|
||||
*/
|
||||
class TokenEnumeration {
|
||||
public:
|
||||
TokenEnumeration(PLDHashTable* table);
|
||||
bool hasMoreTokens();
|
||||
BaseToken* nextToken();
|
||||
|
||||
private:
|
||||
PLDHashTable::Iterator mIterator;
|
||||
};
|
||||
|
||||
// A trait is some aspect of a message, like being junk or tagged as
|
||||
// Personal, that the statistical classifier should track. The Trait
|
||||
// structure is a per-token representation of information pertaining to
|
||||
// a message trait.
|
||||
//
|
||||
// Traits per token are maintained as a linked list.
|
||||
//
|
||||
struct TraitPerToken
|
||||
{
|
||||
uint32_t mId; // identifying number for a trait
|
||||
uint32_t mCount; // count of messages with this token and trait
|
||||
uint32_t mNextLink; // index in mTraitStore for the next trait, or 0
|
||||
// for none
|
||||
TraitPerToken(uint32_t aId, uint32_t aCount); // inititializer
|
||||
};
|
||||
|
||||
// An Analysis is the statistical results for a particular message, a
|
||||
// particular token, and for a particular pair of trait/antitrait, that
|
||||
// is then used in subsequent analysis to score the message.
|
||||
//
|
||||
// Analyses per token are maintained as a linked list.
|
||||
//
|
||||
struct AnalysisPerToken
|
||||
{
|
||||
uint32_t mTraitIndex; // index representing a protrait/antitrait pair.
|
||||
// So if we are analyzing 3 different traits, then
|
||||
// the first trait is 0, the second 1, etc.
|
||||
double mDistance; // absolute value of mProbability - 0.5
|
||||
double mProbability; // relative indicator of match of trait to token
|
||||
uint32_t mNextLink; // index in mAnalysisStore for the Analysis object
|
||||
// for the next trait index, or 0 for none.
|
||||
// initializer
|
||||
AnalysisPerToken(uint32_t aTraitIndex, double aDistance, double aProbability);
|
||||
};
|
||||
|
||||
class TokenHash {
|
||||
public:
|
||||
|
||||
virtual ~TokenHash();
|
||||
/**
|
||||
* Clears out the previous message tokens.
|
||||
*/
|
||||
nsresult clearTokens();
|
||||
uint32_t countTokens();
|
||||
TokenEnumeration getTokens();
|
||||
BaseToken* add(const char* word);
|
||||
|
||||
protected:
|
||||
TokenHash(uint32_t entrySize);
|
||||
PLArenaPool mWordPool;
|
||||
uint32_t mEntrySize;
|
||||
PLDHashTable mTokenTable;
|
||||
char* copyWord(const char* word, uint32_t len);
|
||||
BaseToken* get(const char* word);
|
||||
};
|
||||
|
||||
class Tokenizer: public TokenHash {
|
||||
public:
|
||||
Tokenizer();
|
||||
~Tokenizer();
|
||||
|
||||
Token* get(const char* word);
|
||||
|
||||
// The training set keeps an occurrence count on each word. This count
|
||||
// is supposed to count the # of messsages it occurs in.
|
||||
// When add/remove is called while tokenizing a message and NOT the training set,
|
||||
//
|
||||
Token* add(const char* word, uint32_t count = 1);
|
||||
|
||||
Token* copyTokens();
|
||||
|
||||
void tokenize(const char* text);
|
||||
|
||||
/**
|
||||
* Creates specific tokens based on the mime headers for the message being tokenized
|
||||
*/
|
||||
void tokenizeHeaders(nsIUTF8StringEnumerator * aHeaderNames, nsIUTF8StringEnumerator * aHeaderValues);
|
||||
|
||||
void tokenizeAttachment(const char * aContentType, const char * aFileName);
|
||||
|
||||
nsCString mBodyDelimiters; // delimiters for body tokenization
|
||||
nsCString mHeaderDelimiters; // delimiters for header tokenization
|
||||
|
||||
// arrays of extra headers to tokenize / to not tokenize
|
||||
nsTArray<nsCString> mEnabledHeaders;
|
||||
nsTArray<nsCString> mDisabledHeaders;
|
||||
// Delimiters used in tokenizing a particular header.
|
||||
// Parallel array to mEnabledHeaders
|
||||
nsTArray<nsCString> mEnabledHeadersDelimiters;
|
||||
bool mCustomHeaderTokenization; // Are there any preference-set tokenization customizations?
|
||||
uint32_t mMaxLengthForToken; // maximum length of a token
|
||||
// should we convert iframe to div during tokenization?
|
||||
bool mIframeToDiv;
|
||||
|
||||
private:
|
||||
|
||||
void tokenize_ascii_word(char * word);
|
||||
void tokenize_japanese_word(char* chunk);
|
||||
inline void addTokenForHeader(const char * aTokenPrefix, nsACString& aValue,
|
||||
bool aTokenizeValue = false, const char* aDelimiters = nullptr);
|
||||
nsresult stripHTML(const nsAString& inString, nsAString& outString);
|
||||
// helper function to escape \n, \t, etc from a CString
|
||||
void UnescapeCString(nsCString& aCString);
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsISemanticUnitScanner> mScanner;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements storage of a collection of message tokens and counts for
|
||||
* a corpus of classified messages
|
||||
*/
|
||||
|
||||
class CorpusStore: public TokenHash {
|
||||
public:
|
||||
CorpusStore();
|
||||
~CorpusStore();
|
||||
|
||||
/**
|
||||
* retrieve the token structure for a particular string
|
||||
*
|
||||
* @param word the character representation of the token
|
||||
*
|
||||
* @return token structure containing counts, null if not found
|
||||
*/
|
||||
CorpusToken* get(const char* word);
|
||||
|
||||
/**
|
||||
* add tokens to the storage, or increment counts if already exists.
|
||||
*
|
||||
* @param aTokenizer tokenizer for the list of tokens to remember
|
||||
* @param aTraitId id for the trait whose counts will be remembered
|
||||
* @param aCount number of new messages represented by the token list
|
||||
*/
|
||||
void rememberTokens(Tokenizer& aTokenizer, uint32_t aTraitId, uint32_t aCount);
|
||||
|
||||
/**
|
||||
* decrement counts for tokens in the storage, removing if all counts
|
||||
* are zero
|
||||
*
|
||||
* @param aTokenizer tokenizer for the list of tokens to forget
|
||||
* @param aTraitId id for the trait whose counts will be removed
|
||||
* @param aCount number of messages represented by the token list
|
||||
*/
|
||||
void forgetTokens(Tokenizer& aTokenizer, uint32_t aTraitId, uint32_t aCount);
|
||||
|
||||
/**
|
||||
* write the corpus information to file storage
|
||||
*
|
||||
* @param aMaximumTokenCount prune tokens if number of tokens exceeds
|
||||
* this value. == 0 for no pruning
|
||||
*/
|
||||
void writeTrainingData(uint32_t aMaximumTokenCount);
|
||||
|
||||
/**
|
||||
* read the corpus information from file storage
|
||||
*/
|
||||
void readTrainingData();
|
||||
|
||||
/**
|
||||
* delete the local corpus storage file and data
|
||||
*/
|
||||
nsresult resetTrainingData();
|
||||
|
||||
/**
|
||||
* get the count of messages whose tokens are stored that are associated
|
||||
* with a trait
|
||||
*
|
||||
* @param aTraitId identifier for the trait
|
||||
* @return number of messages for that trait
|
||||
*/
|
||||
uint32_t getMessageCount(uint32_t aTraitId);
|
||||
|
||||
/**
|
||||
* set the count of messages whose tokens are stored that are associated
|
||||
* with a trait
|
||||
*
|
||||
* @param aTraitId identifier for the trait
|
||||
* @param aCount number of messages for that trait
|
||||
*/
|
||||
void setMessageCount(uint32_t aTraitId, uint32_t aCount);
|
||||
|
||||
/**
|
||||
* get the count of messages associated with a particular token and trait
|
||||
*
|
||||
* @param token the token string and associated counts
|
||||
* @param aTraitId identifier for the trait
|
||||
*/
|
||||
uint32_t getTraitCount(CorpusToken *token, uint32_t aTraitId);
|
||||
|
||||
/**
|
||||
* Add (or remove) data from a particular file to the corpus data.
|
||||
*
|
||||
* @param aFile the file with the data, in the format:
|
||||
*
|
||||
* Format of the trait file for version 1:
|
||||
* [0xFCA93601] (the 01 is the version)
|
||||
* for each trait to write:
|
||||
* [id of trait to write] (0 means end of list)
|
||||
* [number of messages per trait]
|
||||
* for each token with non-zero count
|
||||
* [count]
|
||||
* [length of word]word
|
||||
*
|
||||
* @param aIsAdd should the data be added, or removed? true if adding,
|
||||
* else removing.
|
||||
*
|
||||
* @param aRemapCount number of items in the parallel arrays aFromTraits,
|
||||
* aToTraits. These arrays allow conversion of the
|
||||
* trait id stored in the file (which may be originated
|
||||
* externally) to the trait id used in the local corpus
|
||||
* (which is defined locally using nsIMsgTraitService).
|
||||
*
|
||||
* @param aFromTraits array of trait ids used in aFile. If aFile contains
|
||||
* trait ids that are not in this array, they are not
|
||||
* remapped, but assummed to be local trait ids.
|
||||
*
|
||||
* @param aToTraits array of trait ids, corresponding to elements of
|
||||
* aFromTraits, that represent the local trait ids to be
|
||||
* used in storing data from aFile into the local corpus.
|
||||
*
|
||||
*/
|
||||
nsresult UpdateData(nsIFile *aFile, bool aIsAdd,
|
||||
uint32_t aRemapCount, uint32_t *aFromTraits,
|
||||
uint32_t *aToTraits);
|
||||
|
||||
/**
|
||||
* remove all counts (message and tokens) for a trait id
|
||||
*
|
||||
* @param aTrait trait id for the trait to remove
|
||||
*/
|
||||
nsresult ClearTrait(uint32_t aTrait);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* return the local corpus storage file for junk traits
|
||||
*/
|
||||
nsresult getTrainingFile(nsIFile ** aFile);
|
||||
|
||||
/**
|
||||
* return the local corpus storage file for non-junk traits
|
||||
*/
|
||||
nsresult getTraitFile(nsIFile ** aFile);
|
||||
|
||||
/**
|
||||
* read token strings from the data file
|
||||
*
|
||||
* @param stream file stream with token data
|
||||
* @param fileSize file size
|
||||
* @param aTraitId id for the trait whose counts will be read
|
||||
* @param aIsAdd true to add the counts, false to remove them
|
||||
*
|
||||
* @return true if successful, false if error
|
||||
*/
|
||||
bool readTokens(FILE* stream, int64_t fileSize, uint32_t aTraitId,
|
||||
bool aIsAdd);
|
||||
|
||||
/**
|
||||
* write token strings to the data file
|
||||
*/
|
||||
bool writeTokens(FILE* stream, bool shrink, uint32_t aTraitId);
|
||||
|
||||
/**
|
||||
* remove counts for a token string
|
||||
*/
|
||||
void remove(const char* word, uint32_t aTraitId, uint32_t aCount);
|
||||
|
||||
/**
|
||||
* add counts for a token string, adding the token string if new
|
||||
*/
|
||||
CorpusToken* add(const char* word, uint32_t aTraitId, uint32_t aCount);
|
||||
|
||||
/**
|
||||
* change counts in a trait in the traits array, adding the trait if needed
|
||||
*/
|
||||
nsresult updateTrait(CorpusToken* token, uint32_t aTraitId,
|
||||
int32_t aCountChange);
|
||||
nsCOMPtr<nsIFile> mTrainingFile; // file used to store junk training data
|
||||
nsCOMPtr<nsIFile> mTraitFile; // file used to store non-junk
|
||||
// training data
|
||||
nsTArray<TraitPerToken> mTraitStore; // memory for linked-list of counts
|
||||
uint32_t mNextTraitIndex; // index in mTraitStore to first empty
|
||||
// TraitPerToken
|
||||
nsTArray<uint32_t> mMessageCounts; // count of messages per trait
|
||||
// represented in the store
|
||||
nsTArray<uint32_t> mMessageCountsId; // Parallel array to mMessageCounts, with
|
||||
// the corresponding trait ID
|
||||
};
|
||||
|
||||
class nsBayesianFilter : public nsIJunkMailPlugin, nsIMsgCorpus,
|
||||
nsIObserver, nsSupportsWeakReference {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMSGFILTERPLUGIN
|
||||
NS_DECL_NSIJUNKMAILPLUGIN
|
||||
NS_DECL_NSIMSGCORPUS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
nsBayesianFilter();
|
||||
|
||||
nsresult Init();
|
||||
|
||||
nsresult tokenizeMessage(const char* messageURI, nsIMsgWindow *aMsgWindow, TokenAnalyzer* analyzer);
|
||||
void classifyMessage(Tokenizer& tokens, const char* messageURI,
|
||||
nsIJunkMailClassificationListener* listener);
|
||||
|
||||
void classifyMessage(
|
||||
Tokenizer& tokenizer,
|
||||
const char* messageURI,
|
||||
nsTArray<uint32_t>& aProTraits,
|
||||
nsTArray<uint32_t>& aAntiTraits,
|
||||
nsIJunkMailClassificationListener* listener,
|
||||
nsIMsgTraitClassificationListener* aTraitListener,
|
||||
nsIMsgTraitDetailListener* aDetailListener);
|
||||
|
||||
void observeMessage(Tokenizer& tokens, const char* messageURI,
|
||||
nsTArray<uint32_t>& oldClassifications,
|
||||
nsTArray<uint32_t>& newClassifications,
|
||||
nsIJunkMailClassificationListener* listener,
|
||||
nsIMsgTraitClassificationListener* aTraitListener);
|
||||
|
||||
|
||||
protected:
|
||||
virtual ~nsBayesianFilter();
|
||||
|
||||
static void TimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
|
||||
CorpusStore mCorpus;
|
||||
double mJunkProbabilityThreshold;
|
||||
int32_t mMaximumTokenCount;
|
||||
bool mTrainingDataDirty;
|
||||
int32_t mMinFlushInterval; // in milliseconds, must be positive
|
||||
//and not too close to 0
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
|
||||
// index in mAnalysisStore for first empty AnalysisPerToken
|
||||
uint32_t mNextAnalysisIndex;
|
||||
// memory for linked list of AnalysisPerToken objects
|
||||
nsTArray<AnalysisPerToken> mAnalysisStore;
|
||||
/**
|
||||
* Determine the location in mAnalysisStore where the AnalysisPerToken
|
||||
* object for a particular token and trait is stored
|
||||
*/
|
||||
uint32_t getAnalysisIndex(Token& token, uint32_t aTraitIndex);
|
||||
/**
|
||||
* Set the value of the AnalysisPerToken object for a particular
|
||||
* token and trait
|
||||
*/
|
||||
nsresult setAnalysis(Token& token, uint32_t aTraitIndex,
|
||||
double aDistance, double aProbability);
|
||||
};
|
||||
|
||||
#endif // _nsBayesianFilter_h__
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsBayesianFilterCID_h__
|
||||
#define nsBayesianFilterCID_h__
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "nsIFactory.h"
|
||||
#include "nsIComponentManager.h"
|
||||
|
||||
#include "nsIMsgMdnGenerator.h"
|
||||
|
||||
#define NS_BAYESIANFILTER_CONTRACTID \
|
||||
"@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter"
|
||||
#define NS_BAYESIANFILTER_CID \
|
||||
{ /* F1070BFA-D539-11D6-90CA-00039310A47A */ \
|
||||
0xF1070BFA, 0xD539, 0x11D6, \
|
||||
{ 0x90, 0xCA, 0x00, 0x03, 0x93, 0x10, 0xA4, 0x7A }}
|
||||
|
||||
#endif /* nsBayesianFilterCID_h__ */
|
||||
259
mailnews/extensions/bayesian-spam-filter/src/nsIncompleteGamma.h
Normal file
259
mailnews/extensions/bayesian-spam-filter/src/nsIncompleteGamma.h
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsIncompleteGamma_h__
|
||||
#define nsIncompleteGamma_h__
|
||||
|
||||
/* An implementation of the incomplete gamma functions for real
|
||||
arguments. P is defined as
|
||||
|
||||
x
|
||||
/
|
||||
1 [ a - 1 - t
|
||||
P(a, x) = -------- I t e dt
|
||||
Gamma(a) ]
|
||||
/
|
||||
0
|
||||
|
||||
and
|
||||
|
||||
infinity
|
||||
/
|
||||
1 [ a - 1 - t
|
||||
Q(a, x) = -------- I t e dt
|
||||
Gamma(a) ]
|
||||
/
|
||||
x
|
||||
|
||||
so that P(a,x) + Q(a,x) = 1.
|
||||
|
||||
Both a series expansion and a continued fraction exist. This
|
||||
implementation uses the more efficient method based on the arguments.
|
||||
|
||||
Either case involves calculating a multiplicative term:
|
||||
e^(-x)*x^a/Gamma(a).
|
||||
Here we calculate the log of this term. Most math libraries have a
|
||||
"lgamma" function but it is not re-entrant. Some libraries have a
|
||||
"lgamma_r" which is re-entrant. Use it if possible. I have included a
|
||||
simple replacement but it is certainly not as accurate.
|
||||
|
||||
Relative errors are almost always < 1e-10 and usually < 1e-14. Very
|
||||
small and very large arguments cause trouble.
|
||||
|
||||
The region where a < 0.5 and x < 0.5 has poor error properties and is
|
||||
not too stable. Get a better routine if you need results in this
|
||||
region.
|
||||
|
||||
The error argument will be set negative if there is a domain error or
|
||||
positive for an internal calculation error, currently lack of
|
||||
convergence. A value is always returned, though.
|
||||
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
|
||||
// the main routine
|
||||
static double nsIncompleteGammaP (double a, double x, int *error);
|
||||
|
||||
// nsLnGamma(z): either a wrapper around lgamma_r or the internal function.
|
||||
// C_m = B[2*m]/(2*m*(2*m-1)) where B is a Bernoulli number
|
||||
static const double C_1 = 1.0 / 12.0;
|
||||
static const double C_2 = -1.0 / 360.0;
|
||||
static const double C_3 = 1.0 / 1260.0;
|
||||
static const double C_4 = -1.0 / 1680.0;
|
||||
static const double C_5 = 1.0 / 1188.0;
|
||||
static const double C_6 = -691.0 / 360360.0;
|
||||
static const double C_7 = 1.0 / 156.0;
|
||||
static const double C_8 = -3617.0 / 122400.0;
|
||||
static const double C_9 = 43867.0 / 244188.0;
|
||||
static const double C_10 = -174611.0 / 125400.0;
|
||||
static const double C_11 = 77683.0 / 5796.0;
|
||||
|
||||
// truncated asymptotic series in 1/z
|
||||
static inline double lngamma_asymp (double z)
|
||||
{
|
||||
double w, w2, sum;
|
||||
w = 1.0 / z;
|
||||
w2 = w * w;
|
||||
sum = w * (w2 * (w2 * (w2 * (w2 * (w2 * (w2 * (w2 * (w2 * (w2
|
||||
* (C_11 * w2 + C_10) + C_9) + C_8) + C_7) + C_6)
|
||||
+ C_5) + C_4) + C_3) + C_2) + C_1);
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
struct fact_table_s
|
||||
{
|
||||
double fact;
|
||||
double lnfact;
|
||||
};
|
||||
|
||||
// for speed and accuracy
|
||||
static const struct fact_table_s FactTable[] = {
|
||||
{1.000000000000000, 0.0000000000000000000000e+00},
|
||||
{1.000000000000000, 0.0000000000000000000000e+00},
|
||||
{2.000000000000000, 6.9314718055994530942869e-01},
|
||||
{6.000000000000000, 1.7917594692280550007892e+00},
|
||||
{24.00000000000000, 3.1780538303479456197550e+00},
|
||||
{120.0000000000000, 4.7874917427820459941458e+00},
|
||||
{720.0000000000000, 6.5792512120101009952602e+00},
|
||||
{5040.000000000000, 8.5251613610654142999881e+00},
|
||||
{40320.00000000000, 1.0604602902745250228925e+01},
|
||||
{362880.0000000000, 1.2801827480081469610995e+01},
|
||||
{3628800.000000000, 1.5104412573075515295248e+01},
|
||||
{39916800.00000000, 1.7502307845873885839769e+01},
|
||||
{479001600.0000000, 1.9987214495661886149228e+01},
|
||||
{6227020800.000000, 2.2552163853123422886104e+01},
|
||||
{87178291200.00000, 2.5191221182738681499610e+01},
|
||||
{1307674368000.000, 2.7899271383840891566988e+01},
|
||||
{20922789888000.00, 3.0671860106080672803835e+01},
|
||||
{355687428096000.0, 3.3505073450136888885825e+01},
|
||||
{6402373705728000., 3.6395445208033053576674e+01}
|
||||
};
|
||||
#define FactTableLength (int)(sizeof(FactTable)/sizeof(FactTable[0]))
|
||||
|
||||
// for speed
|
||||
static const double ln_2pi_2 = 0.918938533204672741803; // log(2*PI)/2
|
||||
|
||||
/* A simple lgamma function, not very robust.
|
||||
|
||||
Valid for z_in > 0 ONLY.
|
||||
|
||||
For z_in > 8 precision is quite good, relative errors < 1e-14 and
|
||||
usually better. For z_in < 8 relative errors increase but are usually
|
||||
< 1e-10. In two small regions, 1 +/- .001 and 2 +/- .001 errors
|
||||
increase quickly.
|
||||
*/
|
||||
static double nsLnGamma (double z_in, int *gsign)
|
||||
{
|
||||
double scale, z, sum, result;
|
||||
*gsign = 1;
|
||||
|
||||
int zi = (int) z_in;
|
||||
if (z_in == (double) zi)
|
||||
{
|
||||
if (0 < zi && zi <= FactTableLength)
|
||||
return FactTable[zi - 1].lnfact; // gamma(z) = (z-1)!
|
||||
}
|
||||
|
||||
for (scale = 1.0, z = z_in; z < 8.0; ++z)
|
||||
scale *= z;
|
||||
|
||||
sum = lngamma_asymp (z);
|
||||
result = (z - 0.5) * log (z) - z + ln_2pi_2 - log (scale);
|
||||
result += sum;
|
||||
return result;
|
||||
}
|
||||
|
||||
// log( e^(-x)*x^a/Gamma(a) )
|
||||
static inline double lnPQfactor (double a, double x)
|
||||
{
|
||||
int gsign; // ignored because a > 0
|
||||
return a * log (x) - x - nsLnGamma (a, &gsign);
|
||||
}
|
||||
|
||||
static double Pseries (double a, double x, int *error)
|
||||
{
|
||||
double sum, term;
|
||||
const double eps = 2.0 * DBL_EPSILON;
|
||||
const int imax = 5000;
|
||||
int i;
|
||||
|
||||
sum = term = 1.0 / a;
|
||||
for (i = 1; i < imax; ++i)
|
||||
{
|
||||
term *= x / (a + i);
|
||||
sum += term;
|
||||
if (fabs (term) < eps * fabs (sum))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= imax)
|
||||
*error = 1;
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static double Qcontfrac (double a, double x, int *error)
|
||||
{
|
||||
double result, D, C, e, f, term;
|
||||
const double eps = 2.0 * DBL_EPSILON;
|
||||
const double small =
|
||||
DBL_EPSILON * DBL_EPSILON * DBL_EPSILON * DBL_EPSILON;
|
||||
const int imax = 5000;
|
||||
int i;
|
||||
|
||||
// modified Lentz method
|
||||
f = x - a + 1.0;
|
||||
if (fabs (f) < small)
|
||||
f = small;
|
||||
C = f + 1.0 / small;
|
||||
D = 1.0 / f;
|
||||
result = D;
|
||||
for (i = 1; i < imax; ++i)
|
||||
{
|
||||
e = i * (a - i);
|
||||
f += 2.0;
|
||||
D = f + e * D;
|
||||
if (fabs (D) < small)
|
||||
D = small;
|
||||
D = 1.0 / D;
|
||||
C = f + e / C;
|
||||
if (fabs (C) < small)
|
||||
C = small;
|
||||
term = C * D;
|
||||
result *= term;
|
||||
if (fabs (term - 1.0) < eps)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= imax)
|
||||
*error = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
static double nsIncompleteGammaP (double a, double x, int *error)
|
||||
{
|
||||
double result, dom, ldom;
|
||||
// domain errors. the return values are meaningless but have
|
||||
// to return something.
|
||||
*error = -1;
|
||||
if (a <= 0.0)
|
||||
return 1.0;
|
||||
if (x < 0.0)
|
||||
return 0.0;
|
||||
*error = 0;
|
||||
if (x == 0.0)
|
||||
return 0.0;
|
||||
|
||||
ldom = lnPQfactor (a, x);
|
||||
dom = exp (ldom);
|
||||
// might need to adjust the crossover point
|
||||
if (a <= 0.5)
|
||||
{
|
||||
if (x < a + 1.0)
|
||||
result = dom * Pseries (a, x, error);
|
||||
else
|
||||
result = 1.0 - dom * Qcontfrac (a, x, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x < a)
|
||||
result = dom * Pseries (a, x, error);
|
||||
else
|
||||
result = 1.0 - dom * Qcontfrac (a, x, error);
|
||||
}
|
||||
|
||||
// not clear if this can ever happen
|
||||
if (result > 1.0)
|
||||
result = 1.0;
|
||||
if (result < 0.0)
|
||||
result = 0.0;
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
36
mailnews/extensions/dsn/content/am-dsn.js
Normal file
36
mailnews/extensions/dsn/content/am-dsn.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var useCustomPrefs;
|
||||
var requestAlways;
|
||||
var gIdentity;
|
||||
|
||||
function onInit()
|
||||
{
|
||||
useCustomPrefs = document.getElementById("identity.dsn_use_custom_prefs");
|
||||
requestAlways = document.getElementById("identity.dsn_always_request_on");
|
||||
|
||||
EnableDisableCustomSettings();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function onSave()
|
||||
{
|
||||
}
|
||||
|
||||
function EnableDisableCustomSettings() {
|
||||
if (useCustomPrefs && (useCustomPrefs.getAttribute("value") == "false"))
|
||||
requestAlways.setAttribute("disabled", "true");
|
||||
else
|
||||
requestAlways.removeAttribute("disabled");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function onPreInit(account, accountValues)
|
||||
{
|
||||
gIdentity = account.defaultIdentity;
|
||||
}
|
||||
57
mailnews/extensions/dsn/content/am-dsn.xul
Normal file
57
mailnews/extensions/dsn/content/am-dsn.xul
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-dsn.dtd">
|
||||
|
||||
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="parent.onPanelLoaded('am-dsn.xul');">
|
||||
|
||||
<stringbundle id="bundle_smime" src="chrome://messenger/locale/am-dsn.properties"/>
|
||||
<script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
|
||||
<script type="application/javascript" src="chrome://messenger/content/am-dsn.js"/>
|
||||
|
||||
<dialogheader title="&pane.title;"/>
|
||||
|
||||
<groupbox>
|
||||
|
||||
<caption label="&pane.title;"/>
|
||||
|
||||
<hbox id="prefChoices" align="center">
|
||||
<radiogroup id="identity.dsn_use_custom_prefs"
|
||||
wsm_persist="true"
|
||||
genericattr="true"
|
||||
preftype="bool"
|
||||
prefstring="mail.identity.%identitykey%.dsn_use_custom_prefs"
|
||||
oncommand="EnableDisableCustomSettings();">
|
||||
|
||||
<radio id="identity.select_global_prefs"
|
||||
value="false"
|
||||
label="&useGlobalPrefs.label;"
|
||||
accesskey="&useGlobalPrefs.accesskey;"/>
|
||||
|
||||
<radio id="identity.select_custom_prefs"
|
||||
value="true"
|
||||
label="&useCustomPrefs.label;"
|
||||
accesskey="&useCustomPrefs.accesskey;"/>
|
||||
</radiogroup>
|
||||
</hbox>
|
||||
|
||||
<vbox id="dsnSettings" class="indent" align="start">
|
||||
<checkbox id="identity.dsn_always_request_on"
|
||||
label="&requestAlways.label;"
|
||||
accesskey="&requestAlways.accesskey;"
|
||||
wsm_persist="true"
|
||||
genericattr="true"
|
||||
iscontrolcontainer="true"
|
||||
preftype="bool"
|
||||
prefstring="mail.identity.%identitykey%.dsn_always_request_on"/>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
</page>
|
||||
9
mailnews/extensions/dsn/content/dsn.js
Normal file
9
mailnews/extensions/dsn/content/dsn.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* default prefs for dsn
|
||||
*/
|
||||
pref("mail.identity.default.dsn_use_custom_prefs", false); // false: Use global true: Use custom
|
||||
pref("mail.identity.default.dsn_always_request_on", false);
|
||||
9
mailnews/extensions/dsn/jar.mn
Normal file
9
mailnews/extensions/dsn/jar.mn
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifdef MOZ_SUITE
|
||||
messenger.jar:
|
||||
content/messenger/am-dsn.xul (content/am-dsn.xul)
|
||||
content/messenger/am-dsn.js (content/am-dsn.js)
|
||||
#endif
|
||||
15
mailnews/extensions/dsn/moz.build
Normal file
15
mailnews/extensions/dsn/moz.build
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'src/dsn-service.js',
|
||||
'src/dsn-service.manifest',
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
JS_PREFERENCE_FILES += [
|
||||
'content/dsn.js',
|
||||
]
|
||||
24
mailnews/extensions/dsn/src/dsn-service.js
Normal file
24
mailnews/extensions/dsn/src/dsn-service.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function DSNService() {}
|
||||
|
||||
DSNService.prototype = {
|
||||
name: "dsn",
|
||||
chromePackageName: "messenger",
|
||||
showPanel: function(server) {
|
||||
// don't show the panel for news, rss, or local accounts
|
||||
return (server.type != "nntp" && server.type != "rss" &&
|
||||
server.type != "none");
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]),
|
||||
classID: Components.ID("{849dab91-9bc9-4508-a0ee-c2453e7c092d}"),
|
||||
};
|
||||
|
||||
var components = [DSNService];
|
||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|
||||
3
mailnews/extensions/dsn/src/dsn-service.manifest
Normal file
3
mailnews/extensions/dsn/src/dsn-service.manifest
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
component {849dab91-9bc9-4508-a0ee-c2453e7c092d} dsn-service.js
|
||||
contract @mozilla.org/accountmanager/extension;1?name=dsn {849dab91-9bc9-4508-a0ee-c2453e7c092d}
|
||||
category mailnews-accountmanager-extensions dsn-account-manager-extension @mozilla.org/accountmanager/extension;1?name=dsn
|
||||
5
mailnews/extensions/fts3/data/README
Normal file
5
mailnews/extensions/fts3/data/README
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
The data files in this directory come from the ICU project:
|
||||
http://bugs.icu-project.org/trac/browser/icu/trunk/source/data/unidata/norm2
|
||||
|
||||
They are intended to be consumed by the ICU project's gennorm2 script. We have
|
||||
our own script that processes them.
|
||||
264
mailnews/extensions/fts3/data/generate_table.py
Normal file
264
mailnews/extensions/fts3/data/generate_table.py
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
#!/usr/bin/python
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is Mozilla Thunderbird.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Mozilla Japan.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2010
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Makoto Kato <m_kato@ga2.so-net.ne.jp>
|
||||
# Andrew Sutherland <asutherland@asutherland.org>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import re
|
||||
|
||||
def printTable(f, t):
|
||||
i = f
|
||||
while i <= t:
|
||||
c = array[i]
|
||||
print "0x%04x," % c,
|
||||
i = i + 1
|
||||
if not i % 8:
|
||||
print "\n\t",
|
||||
|
||||
print '''/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Japan.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Makoto Kato <m_kato@ga2.so-net.ne.jp>
|
||||
* Andrew Sutherland <asutherland@asutherland.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/* THIS FILE IS GENERATED BY generate_table.py. DON'T EDIT THIS */
|
||||
'''
|
||||
|
||||
p = re.compile('([0-9A-F]{4,5})(?:\.\.([0-9A-F]{4,5}))?[=\>]([0-9A-F]{4,5})?')
|
||||
G_FROM = 1
|
||||
G_TO = 2
|
||||
G_FIRSTVAL = 3
|
||||
|
||||
# Array whose value at index i is the unicode value unicode character i should
|
||||
# map to.
|
||||
array = []
|
||||
# Contents of gNormalizeTable. We insert zero entries for sub-pages where we
|
||||
# have no mappings. We insert references to the tables where we do have
|
||||
# such tables.
|
||||
globalTable = []
|
||||
globalTable.append("0")
|
||||
# The (exclusive) upper bound of the conversion table, unicode character-wise.
|
||||
# This is 0x10000 because our generated table is only 16-bit. This also limits
|
||||
# the values we can map to; we perform an identity mapping for target values
|
||||
# that >= maxmapping.
|
||||
maxmapping = 0x10000
|
||||
sizePerTable = 64
|
||||
|
||||
# Map characters that the mapping tells us to obliterate to the NUKE_CHAR
|
||||
# (such lines look like "FFF0..FFF8>")
|
||||
# We do this because if we didn't do this, we would emit these characters as
|
||||
# part of a token, which we definitely don't want.
|
||||
NUKE_CHAR = 0x20
|
||||
|
||||
# --- load case folding table
|
||||
# entries in the file look like:
|
||||
# 0041>0061
|
||||
# 02D8>0020 0306
|
||||
# 2000..200A>0020
|
||||
#
|
||||
# The 0041 (uppercase A) tells us it lowercases to 0061 (lowercase a).
|
||||
# The 02D8 is a "spacing clone[s] of diacritic" breve which gets decomposed into
|
||||
# a space character and a breve. This entry/type of entry also shows up in
|
||||
# 'nfkc.txt'.
|
||||
# The 2000..200A covers a range of space characters and maps them down to the
|
||||
# 'normal' space character.
|
||||
|
||||
file = open('nfkc_cf.txt')
|
||||
|
||||
m = None
|
||||
line = "\n"
|
||||
i = 0x0
|
||||
while i < maxmapping and line:
|
||||
if not m:
|
||||
line = file.readline()
|
||||
m = p.match(line)
|
||||
if not m:
|
||||
continue
|
||||
low = int(m.group(G_FROM), 16)
|
||||
# if G_TO is present, use it, otherwise fallback to low
|
||||
high = m.group(G_TO) and int(m.group(G_TO), 16) or low
|
||||
# if G_FIRSTVAL is present use it, otherwise use NUKE_CHAR
|
||||
val = (m.group(G_FIRSTVAL) and int(m.group(G_FIRSTVAL), 16)
|
||||
or NUKE_CHAR)
|
||||
continue
|
||||
|
||||
|
||||
if i >= low and i <= high:
|
||||
if val >= maxmapping:
|
||||
array.append(i)
|
||||
else:
|
||||
array.append(val)
|
||||
if i == high:
|
||||
m = None
|
||||
else:
|
||||
array.append(i)
|
||||
i = i + 1
|
||||
file.close()
|
||||
|
||||
# --- load normalization / decomposition table
|
||||
# It is important that this file gets processed second because the other table
|
||||
# will tell us about mappings from uppercase U with diaeresis to lowercase u
|
||||
# with diaeresis. We obviously don't want that clobbering our value. (Although
|
||||
# this would work out if we propagated backwards rather than forwards...)
|
||||
#
|
||||
# - entries in this file that we care about look like:
|
||||
# 00A0>0020
|
||||
# 0100=0041 0304
|
||||
#
|
||||
# They are found in the "Canonical and compatibility decomposition mappings"
|
||||
# section.
|
||||
#
|
||||
# The 00A0 is mapping NBSP to the normal space character.
|
||||
# The 0100 (a capital A with a bar over top of) is equivalent to 0041 (capital
|
||||
# A) plus a 0304 (combining overline). We do not care about the combining
|
||||
# marks which is why our regular expression does not capture it.
|
||||
#
|
||||
#
|
||||
# - entries that we do not care about look like:
|
||||
# 0300..0314:230
|
||||
#
|
||||
# These map marks to their canonical combining class which appears to be a way
|
||||
# of specifying the precedence / order in which marks should be combined. The
|
||||
# key thing is we don't care about them.
|
||||
file = open('nfkc.txt')
|
||||
line = file.readline()
|
||||
m = p.match(line)
|
||||
while line:
|
||||
if not m:
|
||||
line = file.readline()
|
||||
m = p.match(line)
|
||||
continue
|
||||
|
||||
low = int(m.group(G_FROM), 16)
|
||||
# if G_TO is present, use it, otherwise fallback to low
|
||||
high = m.group(G_TO) and int(m.group(G_TO), 16) or low
|
||||
# if G_FIRSTVAL is present use it, otherwise fall back to NUKE_CHAR
|
||||
val = m.group(G_FIRSTVAL) and int(m.group(G_FIRSTVAL), 16) or NUKE_CHAR
|
||||
for i in range(low, high+1):
|
||||
if i < maxmapping and val < maxmapping:
|
||||
array[i] = val
|
||||
m = None
|
||||
file.close()
|
||||
|
||||
# --- generate a normalized table to support case and accent folding
|
||||
|
||||
i = 0
|
||||
needTerm = False;
|
||||
while i < maxmapping:
|
||||
if not i % sizePerTable:
|
||||
# table is empty?
|
||||
j = i
|
||||
while j < i + sizePerTable:
|
||||
if array[j] != j:
|
||||
break
|
||||
j += 1
|
||||
|
||||
if j == i + sizePerTable:
|
||||
if i:
|
||||
globalTable.append("0")
|
||||
i += sizePerTable
|
||||
continue
|
||||
|
||||
if needTerm:
|
||||
print "};\n"
|
||||
globalTable.append("gNormalizeTable%04x" % i)
|
||||
print "static const unsigned short gNormalizeTable%04x[] = {\n\t" % i,
|
||||
print "/* U+%04x */\n\t" % i,
|
||||
needTerm = True
|
||||
# Decomposition does not case-fold, so we want to compensate by
|
||||
# performing a lookup here. Because decomposition chains can be
|
||||
# example: 01d5, a capital U with a diaeresis and a bar. yes, really.
|
||||
# 01d5 -> 00dc -> 0055 (U) -> 0075 (u)
|
||||
c = array[i]
|
||||
while c != array[c]:
|
||||
c = array[c]
|
||||
if c >= 0x41 and c <= 0x5a:
|
||||
raise Exception('got an uppercase character somehow: %x => %x'
|
||||
% (i, c))
|
||||
print "0x%04x," % c,
|
||||
i = i + 1
|
||||
if not i % 8:
|
||||
print "\n\t",
|
||||
|
||||
print "};\n\nstatic const unsigned short* gNormalizeTable[] = {",
|
||||
i = 0
|
||||
while i < (maxmapping / sizePerTable):
|
||||
if not i % 4:
|
||||
print "\n\t",
|
||||
print globalTable[i] + ",",
|
||||
i += 1
|
||||
|
||||
print '''
|
||||
};
|
||||
|
||||
unsigned int normalize_character(const unsigned int c)
|
||||
{
|
||||
if (c >= ''' + ('0x%x' % (maxmapping,)) + ''' || !gNormalizeTable[c >> 6])
|
||||
return c;
|
||||
return gNormalizeTable[c >> 6][c & 0x3f];
|
||||
}
|
||||
'''
|
||||
5786
mailnews/extensions/fts3/data/nfkc.txt
Normal file
5786
mailnews/extensions/fts3/data/nfkc.txt
Normal file
File diff suppressed because it is too large
Load diff
5376
mailnews/extensions/fts3/data/nfkc_cf.txt
Normal file
5376
mailnews/extensions/fts3/data/nfkc_cf.txt
Normal file
File diff suppressed because it is too large
Load diff
11
mailnews/extensions/fts3/public/moz.build
Normal file
11
mailnews/extensions/fts3/public/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIFts3Tokenizer.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'fts3tok'
|
||||
|
||||
15
mailnews/extensions/fts3/public/nsIFts3Tokenizer.idl
Normal file
15
mailnews/extensions/fts3/public/nsIFts3Tokenizer.idl
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface mozIStorageConnection;
|
||||
|
||||
[scriptable, uuid(136c88ea-7003-4fe8-8835-333fd18e598c)]
|
||||
interface nsIFts3Tokenizer : nsISupports {
|
||||
// register FTS3 tokenizer module for "mozporter" tokenizer
|
||||
// mozporter is based by porter tokenizer with bi-gram tokenizer for CJK
|
||||
void registerTokenizer(in mozIStorageConnection connection);
|
||||
};
|
||||
1929
mailnews/extensions/fts3/src/Normalize.c
Normal file
1929
mailnews/extensions/fts3/src/Normalize.c
Normal file
File diff suppressed because it is too large
Load diff
3
mailnews/extensions/fts3/src/README.mozilla
Normal file
3
mailnews/extensions/fts3/src/README.mozilla
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fts3_porter.c code is from SQLite3.
|
||||
|
||||
This customized tokenizer "mozporter" by Mozilla supports CJK indexing using bi-gram. So you have to use bi-gram search string if you wanto to search CJK character.
|
||||
1150
mailnews/extensions/fts3/src/fts3_porter.c
Normal file
1150
mailnews/extensions/fts3/src/fts3_porter.c
Normal file
File diff suppressed because it is too large
Load diff
148
mailnews/extensions/fts3/src/fts3_tokenizer.h
Normal file
148
mailnews/extensions/fts3/src/fts3_tokenizer.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
** 2006 July 10
|
||||
**
|
||||
** The author disclaims copyright to this source code.
|
||||
**
|
||||
*************************************************************************
|
||||
** Defines the interface to tokenizers used by fulltext-search. There
|
||||
** are three basic components:
|
||||
**
|
||||
** sqlite3_tokenizer_module is a singleton defining the tokenizer
|
||||
** interface functions. This is essentially the class structure for
|
||||
** tokenizers.
|
||||
**
|
||||
** sqlite3_tokenizer is used to define a particular tokenizer, perhaps
|
||||
** including customization information defined at creation time.
|
||||
**
|
||||
** sqlite3_tokenizer_cursor is generated by a tokenizer to generate
|
||||
** tokens from a particular input.
|
||||
*/
|
||||
#ifndef _FTS3_TOKENIZER_H_
|
||||
#define _FTS3_TOKENIZER_H_
|
||||
|
||||
/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time.
|
||||
** If tokenizers are to be allowed to call sqlite3_*() functions, then
|
||||
** we will need a way to register the API consistently.
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
|
||||
/*
|
||||
** Structures used by the tokenizer interface. When a new tokenizer
|
||||
** implementation is registered, the caller provides a pointer to
|
||||
** an sqlite3_tokenizer_module containing pointers to the callback
|
||||
** functions that make up an implementation.
|
||||
**
|
||||
** When an fts3 table is created, it passes any arguments passed to
|
||||
** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the
|
||||
** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer
|
||||
** implementation. The xCreate() function in turn returns an
|
||||
** sqlite3_tokenizer structure representing the specific tokenizer to
|
||||
** be used for the fts3 table (customized by the tokenizer clause arguments).
|
||||
**
|
||||
** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen()
|
||||
** method is called. It returns an sqlite3_tokenizer_cursor object
|
||||
** that may be used to tokenize a specific input buffer based on
|
||||
** the tokenization rules supplied by a specific sqlite3_tokenizer
|
||||
** object.
|
||||
*/
|
||||
typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module;
|
||||
typedef struct sqlite3_tokenizer sqlite3_tokenizer;
|
||||
typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor;
|
||||
|
||||
struct sqlite3_tokenizer_module {
|
||||
|
||||
/*
|
||||
** Structure version. Should always be set to 0.
|
||||
*/
|
||||
int iVersion;
|
||||
|
||||
/*
|
||||
** Create a new tokenizer. The values in the argv[] array are the
|
||||
** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL
|
||||
** TABLE statement that created the fts3 table. For example, if
|
||||
** the following SQL is executed:
|
||||
**
|
||||
** CREATE .. USING fts3( ... , tokenizer <tokenizer-name> arg1 arg2)
|
||||
**
|
||||
** then argc is set to 2, and the argv[] array contains pointers
|
||||
** to the strings "arg1" and "arg2".
|
||||
**
|
||||
** This method should return either SQLITE_OK (0), or an SQLite error
|
||||
** code. If SQLITE_OK is returned, then *ppTokenizer should be set
|
||||
** to point at the newly created tokenizer structure. The generic
|
||||
** sqlite3_tokenizer.pModule variable should not be initialised by
|
||||
** this callback. The caller will do so.
|
||||
*/
|
||||
int (*xCreate)(
|
||||
int argc, /* Size of argv array */
|
||||
const char *const*argv, /* Tokenizer argument strings */
|
||||
sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
|
||||
);
|
||||
|
||||
/*
|
||||
** Destroy an existing tokenizer. The fts3 module calls this method
|
||||
** exactly once for each successful call to xCreate().
|
||||
*/
|
||||
int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
|
||||
|
||||
/*
|
||||
** Create a tokenizer cursor to tokenize an input buffer. The caller
|
||||
** is responsible for ensuring that the input buffer remains valid
|
||||
** until the cursor is closed (using the xClose() method).
|
||||
*/
|
||||
int (*xOpen)(
|
||||
sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
|
||||
const char *pInput, int nBytes, /* Input buffer */
|
||||
sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
|
||||
);
|
||||
|
||||
/*
|
||||
** Destroy an existing tokenizer cursor. The fts3 module calls this
|
||||
** method exactly once for each successful call to xOpen().
|
||||
*/
|
||||
int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
|
||||
|
||||
/*
|
||||
** Retrieve the next token from the tokenizer cursor pCursor. This
|
||||
** method should either return SQLITE_OK and set the values of the
|
||||
** "OUT" variables identified below, or SQLITE_DONE to indicate that
|
||||
** the end of the buffer has been reached, or an SQLite error code.
|
||||
**
|
||||
** *ppToken should be set to point at a buffer containing the
|
||||
** normalized version of the token (i.e. after any case-folding and/or
|
||||
** stemming has been performed). *pnBytes should be set to the length
|
||||
** of this buffer in bytes. The input text that generated the token is
|
||||
** identified by the byte offsets returned in *piStartOffset and
|
||||
** *piEndOffset. *piStartOffset should be set to the index of the first
|
||||
** byte of the token in the input buffer. *piEndOffset should be set
|
||||
** to the index of the first byte just past the end of the token in
|
||||
** the input buffer.
|
||||
**
|
||||
** The buffer *ppToken is set to point at is managed by the tokenizer
|
||||
** implementation. It is only required to be valid until the next call
|
||||
** to xNext() or xClose().
|
||||
*/
|
||||
/* TODO(shess) current implementation requires pInput to be
|
||||
** nul-terminated. This should either be fixed, or pInput/nBytes
|
||||
** should be converted to zInput.
|
||||
*/
|
||||
int (*xNext)(
|
||||
sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
|
||||
const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
|
||||
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
|
||||
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
|
||||
int *piPosition /* OUT: Number of tokens returned before this one */
|
||||
);
|
||||
};
|
||||
|
||||
struct sqlite3_tokenizer {
|
||||
const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
|
||||
/* Tokenizer implementations will typically add additional fields */
|
||||
};
|
||||
|
||||
struct sqlite3_tokenizer_cursor {
|
||||
sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
|
||||
/* Tokenizer implementations will typically add additional fields */
|
||||
};
|
||||
|
||||
#endif /* _FTS3_TOKENIZER_H_ */
|
||||
18
mailnews/extensions/fts3/src/moz.build
Normal file
18
mailnews/extensions/fts3/src/moz.build
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SOURCES += [
|
||||
'fts3_porter.c',
|
||||
'Normalize.c',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'nsFts3Tokenizer.cpp',
|
||||
'nsGlodaRankerFunction.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'mail'
|
||||
|
||||
CXXFLAGS += CONFIG['SQLITE_CFLAGS']
|
||||
72
mailnews/extensions/fts3/src/nsFts3Tokenizer.cpp
Normal file
72
mailnews/extensions/fts3/src/nsFts3Tokenizer.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsFts3Tokenizer.h"
|
||||
|
||||
#include "nsGlodaRankerFunction.h"
|
||||
|
||||
#include "nsIFts3Tokenizer.h"
|
||||
#include "mozIStorageConnection.h"
|
||||
#include "mozIStorageStatement.h"
|
||||
#include "nsStringGlue.h"
|
||||
|
||||
extern "C" void sqlite3Fts3PorterTokenizerModule(
|
||||
sqlite3_tokenizer_module const**ppModule);
|
||||
|
||||
extern "C" void glodaRankFunc(sqlite3_context *pCtx,
|
||||
int nVal,
|
||||
sqlite3_value **apVal);
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsFts3Tokenizer,nsIFts3Tokenizer)
|
||||
|
||||
nsFts3Tokenizer::nsFts3Tokenizer()
|
||||
{
|
||||
}
|
||||
|
||||
nsFts3Tokenizer::~nsFts3Tokenizer()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsFts3Tokenizer::RegisterTokenizer(mozIStorageConnection *connection)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<mozIStorageStatement> selectStatement;
|
||||
|
||||
// -- register the tokenizer
|
||||
rv = connection->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT fts3_tokenizer(?1, ?2)"),
|
||||
getter_AddRefs(selectStatement));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
const sqlite3_tokenizer_module* module = nullptr;
|
||||
sqlite3Fts3PorterTokenizerModule(&module);
|
||||
if (!module)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
rv = selectStatement->BindUTF8StringParameter(
|
||||
0, NS_LITERAL_CSTRING("mozporter"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = selectStatement->BindBlobParameter(1,
|
||||
(uint8_t*)&module,
|
||||
sizeof(module));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool hasMore;
|
||||
rv = selectStatement->ExecuteStep(&hasMore);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// -- register the ranking function
|
||||
nsCOMPtr<mozIStorageFunction> func = new nsGlodaRankerFunction();
|
||||
NS_ENSURE_TRUE(func, NS_ERROR_OUT_OF_MEMORY);
|
||||
rv = connection->CreateFunction(
|
||||
NS_LITERAL_CSTRING("glodaRank"),
|
||||
-1, // variable argument support
|
||||
func
|
||||
);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return rv;
|
||||
}
|
||||
26
mailnews/extensions/fts3/src/nsFts3Tokenizer.h
Normal file
26
mailnews/extensions/fts3/src/nsFts3Tokenizer.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsFts3Tokenizer_h__
|
||||
#define nsFts3Tokenizer_h__
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIFts3Tokenizer.h"
|
||||
#include "fts3_tokenizer.h"
|
||||
|
||||
extern const sqlite3_tokenizer_module* getWindowsTokenizer();
|
||||
|
||||
class nsFts3Tokenizer final : public nsIFts3Tokenizer {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIFTS3TOKENIZER
|
||||
|
||||
nsFts3Tokenizer();
|
||||
|
||||
private:
|
||||
~nsFts3Tokenizer();
|
||||
};
|
||||
|
||||
#endif
|
||||
16
mailnews/extensions/fts3/src/nsFts3TokenizerCID.h
Normal file
16
mailnews/extensions/fts3/src/nsFts3TokenizerCID.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsFts3TokenizerCID_h__
|
||||
#define nsFts3TokenizerCID_h__
|
||||
|
||||
#define NS_FTS3TOKENIZER_CONTRACTID \
|
||||
"@mozilla.org/messenger/fts3tokenizer;1"
|
||||
#define NS_FTS3TOKENIZER_CID \
|
||||
{ /* a67d724d-0015-4e2e-8cad-b84775330924 */ \
|
||||
0xa67d724d, 0x0015, 0x4e2e, \
|
||||
{ 0x8c, 0xad, 0xb8, 0x47, 0x75, 0x33, 0x09, 0x24 }}
|
||||
|
||||
#endif /* nsFts3TokenizerCID_h__ */
|
||||
145
mailnews/extensions/fts3/src/nsGlodaRankerFunction.cpp
Normal file
145
mailnews/extensions/fts3/src/nsGlodaRankerFunction.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Thunderbird Global Database.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Andrew Sutherland <asutherland@asutherland.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsGlodaRankerFunction.h"
|
||||
#include "mozIStorageValueArray.h"
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsVariant.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
|
||||
#ifndef SQLITE_VERSION_NUMBER
|
||||
#error "We need SQLITE_VERSION_NUMBER defined!"
|
||||
#endif
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsGlodaRankerFunction, mozIStorageFunction)
|
||||
|
||||
nsGlodaRankerFunction::nsGlodaRankerFunction()
|
||||
{
|
||||
}
|
||||
|
||||
nsGlodaRankerFunction::~nsGlodaRankerFunction()
|
||||
{
|
||||
}
|
||||
|
||||
static uint32_t COLUMN_SATURATION[] = {10, 1, 1, 1, 1};
|
||||
|
||||
/**
|
||||
* Our ranking function basically just multiplies the weight of the column
|
||||
* against the number of (saturating) matches.
|
||||
*
|
||||
* The original code is a SQLite example ranking function, although somewhat
|
||||
* rather modified at this point. All SQLite code is public domain, so we are
|
||||
* subsuming it to MPL1.1/LGPL2/GPL2.
|
||||
*/
|
||||
NS_IMETHODIMP
|
||||
nsGlodaRankerFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
|
||||
nsIVariant **_result)
|
||||
{
|
||||
// all argument names are maintained from the original SQLite code.
|
||||
uint32_t nVal;
|
||||
nsresult rv = aArguments->GetNumEntries(&nVal);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
/* Check that the number of arguments passed to this function is correct.
|
||||
* If not, return an error. Set aArgsData to point to the array
|
||||
* of unsigned integer values returned by FTS3 function. Set nPhrase
|
||||
* to contain the number of reportable phrases in the users full-text
|
||||
* query, and nCol to the number of columns in the table.
|
||||
*/
|
||||
if (nVal < 1)
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
uint32_t lenArgsData;
|
||||
uint32_t *aArgsData = (uint32_t *)aArguments->AsSharedBlob(0, &lenArgsData);
|
||||
|
||||
uint32_t nPhrase = aArgsData[0];
|
||||
uint32_t nCol = aArgsData[1];
|
||||
if (nVal != (1 + nCol))
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
double score = 0.0;
|
||||
|
||||
// SQLite 3.6.22 has a different matchinfo layout than SQLite 3.6.23+
|
||||
#if SQLITE_VERSION_NUMBER <= 3006022
|
||||
/* Iterate through each phrase in the users query. */
|
||||
for (uint32_t iPhrase = 0; iPhrase < nPhrase; iPhrase++) {
|
||||
// in SQ
|
||||
for (uint32_t iCol = 0; iCol < nCol; iCol++) {
|
||||
uint32_t nHitCount = aArgsData[2 + (iPhrase+1)*nCol + iCol];
|
||||
double weight = aArguments->AsDouble(iCol+1);
|
||||
if (nHitCount > 0) {
|
||||
score += (nHitCount > COLUMN_SATURATION[iCol]) ?
|
||||
(COLUMN_SATURATION[iCol] * weight) :
|
||||
(nHitCount * weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Iterate through each phrase in the users query. */
|
||||
for (uint32_t iPhrase = 0; iPhrase < nPhrase; iPhrase++) {
|
||||
/* Now iterate through each column in the users query. For each column,
|
||||
** increment the relevancy score by:
|
||||
**
|
||||
** (<hit count> / <global hit count>) * <column weight>
|
||||
**
|
||||
** aPhraseinfo[] points to the start of the data for phrase iPhrase. So
|
||||
** the hit count and global hit counts for each column are found in
|
||||
** aPhraseinfo[iCol*3] and aPhraseinfo[iCol*3+1], respectively.
|
||||
*/
|
||||
uint32_t *aPhraseinfo = &aArgsData[2 + iPhrase*nCol*3];
|
||||
for (uint32_t iCol = 0; iCol < nCol; iCol++) {
|
||||
uint32_t nHitCount = aPhraseinfo[3 * iCol];
|
||||
double weight = aArguments->AsDouble(iCol+1);
|
||||
if (nHitCount > 0) {
|
||||
score += (nHitCount > COLUMN_SATURATION[iCol]) ?
|
||||
(COLUMN_SATURATION[iCol] * weight) :
|
||||
(nHitCount * weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
nsCOMPtr<nsIWritableVariant> result = new nsVariant();
|
||||
|
||||
rv = result->SetAsDouble(score);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
NS_ADDREF(*_result = result);
|
||||
return NS_OK;
|
||||
}
|
||||
25
mailnews/extensions/fts3/src/nsGlodaRankerFunction.h
Normal file
25
mailnews/extensions/fts3/src/nsGlodaRankerFunction.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef _nsGlodaRankerFunction_h_
|
||||
#define _nsGlodaRankerFunction_h_
|
||||
|
||||
#include "mozIStorageFunction.h"
|
||||
|
||||
/**
|
||||
* Basically a port of the example FTS3 ranking function to mozStorage's
|
||||
* view of the universe. This might get fancier at some point.
|
||||
*/
|
||||
class nsGlodaRankerFunction final : public mozIStorageFunction
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_MOZISTORAGEFUNCTION
|
||||
|
||||
nsGlodaRankerFunction();
|
||||
private:
|
||||
~nsGlodaRankerFunction();
|
||||
};
|
||||
|
||||
#endif // _nsGlodaRankerFunction_h_
|
||||
22
mailnews/extensions/mailviews/content/mailViews.dat
Normal file
22
mailnews/extensions/mailviews/content/mailViews.dat
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
version="8"
|
||||
logging="no"
|
||||
name="People I Know"
|
||||
enabled="yes"
|
||||
type="1"
|
||||
condition="AND (from,is in ab,moz-abmdbdirectory://abook.mab)"
|
||||
name="Recent Mail"
|
||||
enabled="yes"
|
||||
type="1"
|
||||
condition="AND (age in days,is less than,1)"
|
||||
name="Last 5 Days"
|
||||
enabled="yes"
|
||||
type="1"
|
||||
condition="AND (age in days,is less than,5)"
|
||||
name="Not Junk"
|
||||
enabled="yes"
|
||||
type="1"
|
||||
condition="AND (junk status,isn't,2)"
|
||||
name="Has Attachments"
|
||||
enabled="yes"
|
||||
type="1"
|
||||
condition="AND (has attachment status,is,true)"
|
||||
8
mailnews/extensions/mailviews/content/moz.build
Normal file
8
mailnews/extensions/mailviews/content/moz.build
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
FINAL_TARGET_FILES.defaults.messenger += [
|
||||
'mailViews.dat',
|
||||
]
|
||||
12
mailnews/extensions/mailviews/public/moz.build
Normal file
12
mailnews/extensions/mailviews/public/moz.build
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIMsgMailView.idl',
|
||||
'nsIMsgMailViewList.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'mailview'
|
||||
|
||||
35
mailnews/extensions/mailviews/public/nsIMsgMailView.idl
Normal file
35
mailnews/extensions/mailviews/public/nsIMsgMailView.idl
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
#include "nsISupports.idl"
|
||||
// Disable deprecation warnings generated by nsISupportsArray and associated
|
||||
// classes.
|
||||
%{C++
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning (disable : 4996)
|
||||
#endif
|
||||
%}
|
||||
interface nsISupportsArray;
|
||||
|
||||
interface nsIMsgSearchTerm;
|
||||
|
||||
[scriptable, uuid(28AC84DF-CBE5-430d-A5C0-4FA63B5424DF)]
|
||||
interface nsIMsgMailView : nsISupports {
|
||||
attribute wstring mailViewName;
|
||||
readonly attribute wstring prettyName; // localized pretty name
|
||||
|
||||
// the array of search terms
|
||||
attribute nsISupportsArray searchTerms;
|
||||
|
||||
// these two helper methods are required to allow searchTermsOverlay.js to
|
||||
// manipulate a mail view without knowing it is dealing with a mail view. nsIMsgFilter
|
||||
// and nsIMsgSearchSession have the same two methods....we should probably make an interface around them.
|
||||
void appendTerm(in nsIMsgSearchTerm term);
|
||||
nsIMsgSearchTerm createTerm();
|
||||
|
||||
};
|
||||
28
mailnews/extensions/mailviews/public/nsIMsgMailViewList.idl
Normal file
28
mailnews/extensions/mailviews/public/nsIMsgMailViewList.idl
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIMsgMailView.idl"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// A mail view list is a list of mail views a particular implementor provides
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef long nsMsgMailViewListFileAttribValue;
|
||||
|
||||
[scriptable, uuid(6DD798D7-9528-49e6-9447-3AAF14D2D36F)]
|
||||
interface nsIMsgMailViewList : nsISupports {
|
||||
|
||||
readonly attribute unsigned long mailViewCount;
|
||||
|
||||
nsIMsgMailView getMailViewAt(in unsigned long mailViewIndex);
|
||||
|
||||
void addMailView(in nsIMsgMailView mailView);
|
||||
void removeMailView(in nsIMsgMailView mailView);
|
||||
|
||||
nsIMsgMailView createMailView();
|
||||
|
||||
void save();
|
||||
};
|
||||
11
mailnews/extensions/mailviews/src/moz.build
Normal file
11
mailnews/extensions/mailviews/src/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SOURCES += [
|
||||
'nsMsgMailViewList.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'mail'
|
||||
|
||||
312
mailnews/extensions/mailviews/src/nsMsgMailViewList.cpp
Normal file
312
mailnews/extensions/mailviews/src/nsMsgMailViewList.cpp
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsMsgMailViewList.h"
|
||||
// Disable deprecation warnings generated by nsISupportsArray and associated
|
||||
// classes.
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning (disable : 4996)
|
||||
#endif
|
||||
#include "nsISupportsArray.h"
|
||||
#include "nsIFileChannel.h"
|
||||
#include "nsIMsgFilterService.h"
|
||||
#include "nsIMsgMailSession.h"
|
||||
#include "nsIMsgSearchTerm.h"
|
||||
#include "nsMsgBaseCID.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsIMsgFilter.h"
|
||||
|
||||
#define kDefaultViewPeopleIKnow "People I Know"
|
||||
#define kDefaultViewRecent "Recent Mail"
|
||||
#define kDefaultViewFiveDays "Last 5 Days"
|
||||
#define kDefaultViewNotJunk "Not Junk"
|
||||
#define kDefaultViewHasAttachments "Has Attachments"
|
||||
|
||||
nsMsgMailView::nsMsgMailView()
|
||||
{
|
||||
mViewSearchTerms = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF(nsMsgMailView)
|
||||
NS_IMPL_RELEASE(nsMsgMailView)
|
||||
NS_IMPL_QUERY_INTERFACE(nsMsgMailView, nsIMsgMailView)
|
||||
|
||||
nsMsgMailView::~nsMsgMailView()
|
||||
{
|
||||
if (mViewSearchTerms)
|
||||
mViewSearchTerms->Clear();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailView::GetMailViewName(char16_t ** aMailViewName)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMailViewName);
|
||||
|
||||
*aMailViewName = ToNewUnicode(mName);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailView::SetMailViewName(const char16_t * aMailViewName)
|
||||
{
|
||||
mName = aMailViewName;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailView::GetPrettyName(char16_t ** aMailViewName)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMailViewName);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
if (!mBundle)
|
||||
{
|
||||
nsCOMPtr<nsIStringBundleService> bundleService =
|
||||
mozilla::services::GetStringBundleService();
|
||||
NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
|
||||
bundleService->CreateBundle("chrome://messenger/locale/mailviews.properties",
|
||||
getter_AddRefs(mBundle));
|
||||
}
|
||||
|
||||
NS_ENSURE_TRUE(mBundle, NS_ERROR_FAILURE);
|
||||
|
||||
// see if mName has an associated pretty name inside our string bundle and if so, use that as the pretty name
|
||||
// otherwise just return mName
|
||||
if (mName.EqualsLiteral(kDefaultViewPeopleIKnow))
|
||||
rv = mBundle->GetStringFromName(u"mailViewPeopleIKnow", aMailViewName);
|
||||
else if (mName.EqualsLiteral(kDefaultViewRecent))
|
||||
rv = mBundle->GetStringFromName(u"mailViewRecentMail", aMailViewName);
|
||||
else if (mName.EqualsLiteral(kDefaultViewFiveDays))
|
||||
rv = mBundle->GetStringFromName(u"mailViewLastFiveDays", aMailViewName);
|
||||
else if (mName.EqualsLiteral(kDefaultViewNotJunk))
|
||||
rv = mBundle->GetStringFromName(u"mailViewNotJunk", aMailViewName);
|
||||
else if (mName.EqualsLiteral(kDefaultViewHasAttachments))
|
||||
rv = mBundle->GetStringFromName(u"mailViewHasAttachments", aMailViewName);
|
||||
else
|
||||
*aMailViewName = ToNewUnicode(mName);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailView::GetSearchTerms(nsISupportsArray ** aSearchTerms)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aSearchTerms);
|
||||
NS_IF_ADDREF(*aSearchTerms = mViewSearchTerms);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailView::SetSearchTerms(nsISupportsArray * aSearchTerms)
|
||||
{
|
||||
mViewSearchTerms = aSearchTerms;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailView::AppendTerm(nsIMsgSearchTerm * aTerm)
|
||||
{
|
||||
NS_ENSURE_TRUE(aTerm, NS_ERROR_NULL_POINTER);
|
||||
|
||||
return mViewSearchTerms->AppendElement(static_cast<nsISupports*>(aTerm));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailView::CreateTerm(nsIMsgSearchTerm **aResult)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aResult);
|
||||
nsCOMPtr<nsIMsgSearchTerm> searchTerm = do_CreateInstance("@mozilla.org/messenger/searchTerm;1");
|
||||
NS_IF_ADDREF(*aResult = searchTerm);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// nsMsgMailViewList implementation
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
nsMsgMailViewList::nsMsgMailViewList()
|
||||
{
|
||||
LoadMailViews();
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF(nsMsgMailViewList)
|
||||
NS_IMPL_RELEASE(nsMsgMailViewList)
|
||||
NS_IMPL_QUERY_INTERFACE(nsMsgMailViewList, nsIMsgMailViewList)
|
||||
|
||||
nsMsgMailViewList::~nsMsgMailViewList()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailViewList::GetMailViewCount(uint32_t * aCount)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aCount);
|
||||
|
||||
*aCount = m_mailViews.Length();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailViewList::GetMailViewAt(uint32_t aMailViewIndex, nsIMsgMailView ** aMailView)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMailView);
|
||||
|
||||
uint32_t mailViewCount = m_mailViews.Length();
|
||||
|
||||
NS_ENSURE_ARG(mailViewCount > aMailViewIndex);
|
||||
|
||||
NS_IF_ADDREF(*aMailView = m_mailViews[aMailViewIndex]);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailViewList::AddMailView(nsIMsgMailView * aMailView)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMailView);
|
||||
|
||||
m_mailViews.AppendElement(aMailView);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailViewList::RemoveMailView(nsIMsgMailView * aMailView)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMailView);
|
||||
|
||||
m_mailViews.RemoveElement(aMailView);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailViewList::CreateMailView(nsIMsgMailView ** aMailView)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMailView);
|
||||
|
||||
nsMsgMailView * mailView = new nsMsgMailView;
|
||||
NS_ENSURE_TRUE(mailView, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
NS_IF_ADDREF(*aMailView = mailView);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgMailViewList::Save()
|
||||
{
|
||||
// brute force...remove all the old filters in our filter list, then we'll re-add our current
|
||||
// list
|
||||
nsCOMPtr<nsIMsgFilter> msgFilter;
|
||||
uint32_t numFilters = 0;
|
||||
if (mFilterList)
|
||||
mFilterList->GetFilterCount(&numFilters);
|
||||
while (numFilters)
|
||||
{
|
||||
mFilterList->RemoveFilterAt(numFilters - 1);
|
||||
numFilters--;
|
||||
}
|
||||
|
||||
// now convert our mail view list into a filter list and save it
|
||||
ConvertMailViewListToFilterList();
|
||||
|
||||
// now save the filters to our file
|
||||
return mFilterList ? mFilterList->SaveToDefaultFile() : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult nsMsgMailViewList::ConvertMailViewListToFilterList()
|
||||
{
|
||||
uint32_t mailViewCount = m_mailViews.Length();
|
||||
nsCOMPtr<nsIMsgMailView> mailView;
|
||||
nsCOMPtr<nsIMsgFilter> newMailFilter;
|
||||
nsString mailViewName;
|
||||
for (uint32_t index = 0; index < mailViewCount; index++)
|
||||
{
|
||||
GetMailViewAt(index, getter_AddRefs(mailView));
|
||||
if (!mailView)
|
||||
continue;
|
||||
mailView->GetMailViewName(getter_Copies(mailViewName));
|
||||
mFilterList->CreateFilter(mailViewName, getter_AddRefs(newMailFilter));
|
||||
if (!newMailFilter)
|
||||
continue;
|
||||
|
||||
nsCOMPtr<nsISupportsArray> searchTerms;
|
||||
mailView->GetSearchTerms(getter_AddRefs(searchTerms));
|
||||
newMailFilter->SetSearchTerms(searchTerms);
|
||||
mFilterList->InsertFilterAt(index, newMailFilter);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsMsgMailViewList::LoadMailViews()
|
||||
{
|
||||
nsCOMPtr<nsIFile> file;
|
||||
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = file->AppendNative(nsDependentCString("mailViews.dat"));
|
||||
|
||||
// if the file doesn't exist, we should try to get it from the defaults directory and copy it over
|
||||
bool exists = false;
|
||||
file->Exists(&exists);
|
||||
if (!exists)
|
||||
{
|
||||
nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIFile> defaultMessagesFile;
|
||||
nsCOMPtr<nsIFile> profileDir;
|
||||
rv = mailSession->GetDataFilesDir("messenger", getter_AddRefs(defaultMessagesFile));
|
||||
rv = defaultMessagesFile->AppendNative(nsDependentCString("mailViews.dat"));
|
||||
|
||||
// get the profile directory
|
||||
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir));
|
||||
|
||||
// now copy the file over to the profile directory
|
||||
defaultMessagesFile->CopyToNative(profileDir, EmptyCString());
|
||||
}
|
||||
// this is kind of a hack but I think it will be an effective hack. The filter service already knows how to
|
||||
// take a nsIFile and parse the contents into filters which are very similar to mail views. Intead of
|
||||
// re-writing all of that dirty parsing code, let's just re-use it then convert the results into a data strcuture
|
||||
// we wish to give to our consumers.
|
||||
|
||||
nsCOMPtr<nsIMsgFilterService> filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
|
||||
nsCOMPtr<nsIMsgFilterList> mfilterList;
|
||||
|
||||
rv = filterService->OpenFilterList(file, nullptr, nullptr, getter_AddRefs(mFilterList));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return ConvertFilterListToMailViews();
|
||||
}
|
||||
/**
|
||||
* Converts the filter list into our mail view objects,
|
||||
* stripping out just the info we need.
|
||||
*/
|
||||
nsresult nsMsgMailViewList::ConvertFilterListToMailViews()
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
m_mailViews.Clear();
|
||||
|
||||
// iterate over each filter in the list
|
||||
uint32_t numFilters = 0;
|
||||
mFilterList->GetFilterCount(&numFilters);
|
||||
for (uint32_t index = 0; index < numFilters; index++)
|
||||
{
|
||||
nsCOMPtr<nsIMsgFilter> msgFilter;
|
||||
rv = mFilterList->GetFilterAt(index, getter_AddRefs(msgFilter));
|
||||
if (NS_FAILED(rv) || !msgFilter)
|
||||
continue;
|
||||
|
||||
// create a new nsIMsgMailView for this item
|
||||
nsCOMPtr<nsIMsgMailView> newMailView;
|
||||
rv = CreateMailView(getter_AddRefs(newMailView));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsString filterName;
|
||||
msgFilter->GetFilterName(filterName);
|
||||
newMailView->SetMailViewName(filterName.get());
|
||||
|
||||
nsCOMPtr<nsISupportsArray> filterSearchTerms;
|
||||
rv = msgFilter->GetSearchTerms(getter_AddRefs(filterSearchTerms));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = newMailView->SetSearchTerms(filterSearchTerms);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// now append this new mail view to our global list view
|
||||
m_mailViews.AppendElement(newMailView);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
61
mailnews/extensions/mailviews/src/nsMsgMailViewList.h
Normal file
61
mailnews/extensions/mailviews/src/nsMsgMailViewList.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
#ifndef _nsMsgMailViewList_H_
|
||||
#define _nsMsgMailViewList_H_
|
||||
|
||||
#include "nscore.h"
|
||||
#include "nsIMsgMailViewList.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCOMArray.h"
|
||||
// Disable deprecation warnings generated by nsISupportsArray and associated
|
||||
// classes.
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning (disable : 4996)
|
||||
#endif
|
||||
#include "nsISupportsArray.h"
|
||||
#include "nsIStringBundle.h"
|
||||
#include "nsStringGlue.h"
|
||||
#include "nsIMsgFilterList.h"
|
||||
|
||||
// a mail View is just a name and an array of search terms
|
||||
class nsMsgMailView : public nsIMsgMailView
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMSGMAILVIEW
|
||||
|
||||
nsMsgMailView();
|
||||
|
||||
protected:
|
||||
virtual ~nsMsgMailView();
|
||||
nsString mName;
|
||||
nsCOMPtr<nsIStringBundle> mBundle;
|
||||
nsCOMPtr<nsISupportsArray> mViewSearchTerms;
|
||||
};
|
||||
|
||||
|
||||
class nsMsgMailViewList : public nsIMsgMailViewList
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMSGMAILVIEWLIST
|
||||
|
||||
nsMsgMailViewList();
|
||||
|
||||
protected:
|
||||
virtual ~nsMsgMailViewList();
|
||||
nsresult LoadMailViews(); // reads in user defined mail views from our default file
|
||||
nsresult ConvertFilterListToMailViews();
|
||||
nsresult ConvertMailViewListToFilterList();
|
||||
|
||||
nsCOMArray<nsIMsgMailView> m_mailViews;
|
||||
nsCOMPtr<nsIMsgFilterList> mFilterList; // our internal filter list representation
|
||||
};
|
||||
|
||||
#endif
|
||||
17
mailnews/extensions/mailviews/src/nsMsgMailViewsCID.h
Normal file
17
mailnews/extensions/mailviews/src/nsMsgMailViewsCID.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsMsgMailViewsCID_h__
|
||||
#define nsMsgMailViewsCID_h__
|
||||
|
||||
#define NS_MSGMAILVIEWLIST_CONTRACTID \
|
||||
"@mozilla.org/messenger/mailviewlist;1"
|
||||
|
||||
#define NS_MSGMAILVIEWLIST_CID \
|
||||
{ /* A0258267-44FD-4886-A858-8192615178EC */ \
|
||||
0xa0258267, 0x44fd, 0x4886, \
|
||||
{ 0xa8, 0x58, 0x81, 0x92, 0x61, 0x51, 0x78, 0xec }}
|
||||
|
||||
#endif /* nsMsgMailViewsCID_h__*/
|
||||
155
mailnews/extensions/mdn/content/am-mdn.js
Normal file
155
mailnews/extensions/mdn/content/am-mdn.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var useCustomPrefs;
|
||||
var requestReceipt;
|
||||
var leaveInInbox;
|
||||
var moveToSent;
|
||||
var receiptSend;
|
||||
var neverReturn;
|
||||
var returnSome;
|
||||
var notInToCcPref;
|
||||
var notInToCcLabel;
|
||||
var outsideDomainPref;
|
||||
var outsideDomainLabel;
|
||||
var otherCasesPref;
|
||||
var otherCasesLabel;
|
||||
var receiptArriveLabel;
|
||||
var receiptRequestLabel;
|
||||
var gIdentity;
|
||||
var gIncomingServer;
|
||||
var gMdnPrefBranch;
|
||||
|
||||
function onInit()
|
||||
{
|
||||
useCustomPrefs = document.getElementById("identity.use_custom_prefs");
|
||||
requestReceipt = document.getElementById("identity.request_return_receipt_on");
|
||||
leaveInInbox = document.getElementById("leave_in_inbox");
|
||||
moveToSent = document.getElementById("move_to_sent");
|
||||
receiptSend = document.getElementById("server.mdn_report_enabled");
|
||||
neverReturn = document.getElementById("never_return");
|
||||
returnSome = document.getElementById("return_some");
|
||||
notInToCcPref = document.getElementById("server.mdn_not_in_to_cc");
|
||||
notInToCcLabel = document.getElementById("notInToCcLabel");
|
||||
outsideDomainPref = document.getElementById("server.mdn_outside_domain");
|
||||
outsideDomainLabel = document.getElementById("outsideDomainLabel");
|
||||
otherCasesPref = document.getElementById("server.mdn_other");
|
||||
otherCasesLabel = document.getElementById("otherCasesLabel");
|
||||
receiptArriveLabel = document.getElementById("receiptArriveLabel");
|
||||
receiptRequestLabel = document.getElementById("receiptRequestLabel");
|
||||
|
||||
EnableDisableCustomSettings();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function onSave()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
function EnableDisableCustomSettings() {
|
||||
if (useCustomPrefs && (useCustomPrefs.getAttribute("value") == "false")) {
|
||||
requestReceipt.setAttribute("disabled", "true");
|
||||
leaveInInbox.setAttribute("disabled", "true");
|
||||
moveToSent.setAttribute("disabled", "true");
|
||||
neverReturn.setAttribute("disabled", "true");
|
||||
returnSome.setAttribute("disabled", "true");
|
||||
receiptArriveLabel.setAttribute("disabled", "true");
|
||||
receiptRequestLabel.setAttribute("disabled", "true");
|
||||
}
|
||||
else {
|
||||
requestReceipt.removeAttribute("disabled");
|
||||
leaveInInbox.removeAttribute("disabled");
|
||||
moveToSent.removeAttribute("disabled");
|
||||
neverReturn.removeAttribute("disabled");
|
||||
returnSome.removeAttribute("disabled");
|
||||
receiptArriveLabel.removeAttribute("disabled");
|
||||
receiptRequestLabel.removeAttribute("disabled");
|
||||
}
|
||||
EnableDisableAllowedReceipts();
|
||||
// Lock id based prefs
|
||||
onLockPreference("mail.identity", gIdentity.key);
|
||||
// Lock server based prefs
|
||||
onLockPreference("mail.server", gIncomingServer.key);
|
||||
return true;
|
||||
}
|
||||
|
||||
function EnableDisableAllowedReceipts() {
|
||||
if (receiptSend) {
|
||||
if (!neverReturn.getAttribute("disabled") && (receiptSend.getAttribute("value") != "false")) {
|
||||
notInToCcPref.removeAttribute("disabled");
|
||||
notInToCcLabel.removeAttribute("disabled");
|
||||
outsideDomainPref.removeAttribute("disabled");
|
||||
outsideDomainLabel.removeAttribute("disabled");
|
||||
otherCasesPref.removeAttribute("disabled");
|
||||
otherCasesLabel.removeAttribute("disabled");
|
||||
}
|
||||
else {
|
||||
notInToCcPref.setAttribute("disabled", "true");
|
||||
notInToCcLabel.setAttribute("disabled", "true");
|
||||
outsideDomainPref.setAttribute("disabled", "true");
|
||||
outsideDomainLabel.setAttribute("disabled", "true");
|
||||
otherCasesPref.setAttribute("disabled", "true");
|
||||
otherCasesLabel.setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function onPreInit(account, accountValues)
|
||||
{
|
||||
gIdentity = account.defaultIdentity;
|
||||
gIncomingServer = account.incomingServer;
|
||||
}
|
||||
|
||||
// Disables xul elements that have associated preferences locked.
|
||||
function onLockPreference(initPrefString, keyString)
|
||||
{
|
||||
var finalPrefString;
|
||||
|
||||
var allPrefElements = [
|
||||
{ prefstring:"request_return_receipt_on", id:"identity.request_return_receipt_on"},
|
||||
{ prefstring:"select_custom_prefs", id:"identity.select_custom_prefs"},
|
||||
{ prefstring:"select_global_prefs", id:"identity.select_global_prefs"},
|
||||
{ prefstring:"incorporate_return_receipt", id:"server.incorporate_return_receipt"},
|
||||
{ prefstring:"never_return", id:"never_return"},
|
||||
{ prefstring:"return_some", id:"return_some"},
|
||||
{ prefstring:"mdn_not_in_to_cc", id:"server.mdn_not_in_to_cc"},
|
||||
{ prefstring:"mdn_outside_domain", id:"server.mdn_outside_domain"},
|
||||
{ prefstring:"mdn_other", id:"server.mdn_other"},
|
||||
];
|
||||
|
||||
finalPrefString = initPrefString + "." + keyString + ".";
|
||||
gMdnPrefBranch = Services.prefs.getBranch(finalPrefString);
|
||||
|
||||
disableIfLocked( allPrefElements );
|
||||
}
|
||||
|
||||
function disableIfLocked( prefstrArray )
|
||||
{
|
||||
for (var i=0; i<prefstrArray.length; i++) {
|
||||
var id = prefstrArray[i].id;
|
||||
var element = document.getElementById(id);
|
||||
if (gMdnPrefBranch.prefIsLocked(prefstrArray[i].prefstring)) {
|
||||
if (id == "server.incorporate_return_receipt")
|
||||
{
|
||||
document.getElementById("leave_in_inbox").setAttribute("disabled", "true");
|
||||
document.getElementById("move_to_sent").setAttribute("disabled", "true");
|
||||
}
|
||||
else
|
||||
element.setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens Preferences (Options) dialog on the pane and tab where
|
||||
* the global receipts settings can be found.
|
||||
*/
|
||||
function showGlobalReceipts() {
|
||||
openPrefsFromAccountManager("paneAdvanced", "generalTab",
|
||||
{subdialog: "showReturnReceipts"}, "receipts_pane");
|
||||
}
|
||||
136
mailnews/extensions/mdn/content/am-mdn.xul
Normal file
136
mailnews/extensions/mdn/content/am-mdn.xul
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-mdn.dtd">
|
||||
|
||||
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&pane.title;"
|
||||
onload="parent.onPanelLoaded('am-mdn.xul');">
|
||||
|
||||
<vbox flex="1" style="overflow: auto;">
|
||||
<stringbundle id="bundle_smime" src="chrome://messenger/locale/am-mdn.properties"/>
|
||||
<script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
|
||||
<script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
|
||||
<script type="application/javascript" src="chrome://messenger/content/am-mdn.js"/>
|
||||
|
||||
<dialogheader title="&pane.title;"/>
|
||||
|
||||
<groupbox>
|
||||
|
||||
<caption label="&pane.title;"/>
|
||||
|
||||
<hbox id="prefChoices" align="center" flex="1">
|
||||
<radiogroup id="identity.use_custom_prefs" wsm_persist="true" genericattr="true"
|
||||
preftype="bool" prefstring="mail.identity.%identitykey%.use_custom_prefs"
|
||||
oncommand="EnableDisableCustomSettings();" flex="1">
|
||||
<radio id="identity.select_global_prefs"
|
||||
value="false"
|
||||
label="&useGlobalPrefs.label;"
|
||||
accesskey="&useGlobalPrefs.accesskey;"/>
|
||||
<hbox flex="1">
|
||||
<spacer flex="1"/>
|
||||
<button id="globalReceiptsLink"
|
||||
label="&globalReceipts.label;"
|
||||
accesskey="&globalReceipts.accesskey;"
|
||||
oncommand="showGlobalReceipts();"/>
|
||||
</hbox>
|
||||
<radio id="identity.select_custom_prefs"
|
||||
value="true"
|
||||
label="&useCustomPrefs.label;"
|
||||
accesskey="&useCustomPrefs.accesskey;"/>
|
||||
</radiogroup>
|
||||
</hbox>
|
||||
|
||||
<vbox id="returnReceiptSettings" class="indent" align="start">
|
||||
<checkbox id="identity.request_return_receipt_on" label="&requestReceipt.label;"
|
||||
accesskey="&requestReceipt.accesskey;"
|
||||
wsm_persist="true" genericattr="true" iscontrolcontainer="true"
|
||||
preftype="bool" prefstring="mail.identity.%identitykey%.request_return_receipt_on"/>
|
||||
|
||||
<separator/>
|
||||
|
||||
<vbox id="receiptArrive">
|
||||
<label id="receiptArriveLabel" control="server.incorporate_return_receipt">&receiptArrive.label;</label>
|
||||
<radiogroup id="server.incorporate_return_receipt" wsm_persist="true" genericattr="true"
|
||||
preftype="int" prefstring="mail.server.%serverkey%.incorporate_return_receipt"
|
||||
class="indent">
|
||||
<radio id="leave_in_inbox" value="0" label="&leaveIt.label;"
|
||||
accesskey="&leaveIt.accesskey;"/>
|
||||
<radio id="move_to_sent" value="1" label="&moveToSent.label;"
|
||||
accesskey="&moveToSent.accesskey;"/>
|
||||
</radiogroup>
|
||||
</vbox>
|
||||
|
||||
<separator/>
|
||||
|
||||
<vbox id="receiptRequest">
|
||||
<label id="receiptRequestLabel" control="server.mdn_report_enabled">&requestMDN.label;</label>
|
||||
<radiogroup id="server.mdn_report_enabled" wsm_persist="true" genericattr="true"
|
||||
preftype="bool" prefstring="mail.server.%serverkey%.mdn_report_enabled"
|
||||
oncommand="EnableDisableAllowedReceipts();"
|
||||
class="indent">
|
||||
<radio id="never_return" value="false" label="&never.label;"
|
||||
accesskey="&never.accesskey;"/>
|
||||
<radio id="return_some" value="true" label="&returnSome.label;"
|
||||
accesskey="&returnSome.accesskey;"/>
|
||||
|
||||
<hbox id="receiptSendIf" class="indent">
|
||||
<grid>
|
||||
<columns><column/><column/></columns>
|
||||
<rows>
|
||||
<row align="center">
|
||||
<label id="notInToCcLabel" value="¬InToCc.label;"
|
||||
accesskey="¬InToCc.accesskey;" control="server.mdn_not_in_to_cc"/>
|
||||
<menulist id="server.mdn_not_in_to_cc" wsm_persist="true" genericattr="true"
|
||||
preftype="int" prefstring="mail.server.%serverkey%.mdn_not_in_to_cc">
|
||||
<menupopup>
|
||||
<menuitem value="0" label="&neverSend.label;"/>
|
||||
<menuitem value="1" label="&alwaysSend.label;"/>
|
||||
<menuitem value="2" label="&askMe.label;"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</row>
|
||||
<row align="center">
|
||||
<label id="outsideDomainLabel" value="&outsideDomain.label;"
|
||||
accesskey="&outsideDomain.accesskey;" control="server.mdn_outside_domain"/>
|
||||
<menulist id="server.mdn_outside_domain" wsm_persist="true" genericattr="true"
|
||||
preftype="int" prefstring="mail.server.%serverkey%.mdn_outside_domain">
|
||||
<menupopup>
|
||||
<menuitem value="0" label="&neverSend.label;"/>
|
||||
<menuitem value="1" label="&alwaysSend.label;"/>
|
||||
<menuitem value="2" label="&askMe.label;"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</row>
|
||||
<row align="center">
|
||||
<label id="otherCasesLabel" value="&otherCases.label;"
|
||||
accesskey="&otherCases.accesskey;" control="server.mdn_other"/>
|
||||
<menulist id="server.mdn_other" wsm_persist="true" genericattr="true"
|
||||
preftype="int" prefstring="mail.server.%serverkey%.mdn_other">
|
||||
<menupopup>
|
||||
<menuitem value="0" label="&neverSend.label;"/>
|
||||
<menuitem value="1" label="&alwaysSend.label;"/>
|
||||
<menuitem value="2" label="&askMe.label;"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</hbox>
|
||||
</radiogroup>
|
||||
|
||||
</vbox>
|
||||
|
||||
</vbox>
|
||||
|
||||
</groupbox>
|
||||
</vbox>
|
||||
|
||||
</page>
|
||||
23
mailnews/extensions/mdn/content/mdn.js
Normal file
23
mailnews/extensions/mdn/content/mdn.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* default prefs for mdn
|
||||
*/
|
||||
|
||||
pref("mail.identity.default.use_custom_prefs", false); // false: Use global true: Use custom
|
||||
|
||||
pref("mail.identity.default.request_return_receipt_on", false);
|
||||
|
||||
pref("mail.server.default.incorporate_return_receipt", 0); // 0: Inbox/filter 1: Sent folder
|
||||
|
||||
pref("mail.server.default.mdn_report_enabled", true); // false: Never return receipts true: Return some receipts
|
||||
|
||||
pref("mail.server.default.mdn_not_in_to_cc", 2); // 0: Never 1: Always 2: Ask me 3: Denial
|
||||
pref("mail.server.default.mdn_outside_domain", 2);
|
||||
pref("mail.server.default.mdn_other", 2);
|
||||
|
||||
pref("mail.identity.default.request_receipt_header_type", 0); // return receipt header type - 0: MDN-DNT 1: RRT 2: Both
|
||||
|
||||
pref("mail.server.default.mdn_report_enabled", true);
|
||||
7
mailnews/extensions/mdn/jar.mn
Normal file
7
mailnews/extensions/mdn/jar.mn
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
messenger.jar:
|
||||
content/messenger/am-mdn.xul (content/am-mdn.xul)
|
||||
content/messenger/am-mdn.js (content/am-mdn.js)
|
||||
12
mailnews/extensions/mdn/moz.build
Normal file
12
mailnews/extensions/mdn/moz.build
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ['src']
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
JS_PREFERENCE_FILES += [
|
||||
'content/mdn.js',
|
||||
]
|
||||
24
mailnews/extensions/mdn/src/mdn-service.js
Normal file
24
mailnews/extensions/mdn/src/mdn-service.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function MDNService() {}
|
||||
|
||||
MDNService.prototype = {
|
||||
name: "mdn",
|
||||
chromePackageName: "messenger",
|
||||
showPanel: function(server) {
|
||||
// don't show the panel for news, rss, im or local accounts
|
||||
return (server.type != "nntp" && server.type != "rss" &&
|
||||
server.type != "im" && server.type != "none");
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]),
|
||||
classID: Components.ID("{e007d92e-1dd1-11b2-a61e-dc962c9b8571}"),
|
||||
};
|
||||
|
||||
var components = [MDNService];
|
||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|
||||
3
mailnews/extensions/mdn/src/mdn-service.manifest
Normal file
3
mailnews/extensions/mdn/src/mdn-service.manifest
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
component {e007d92e-1dd1-11b2-a61e-dc962c9b8571} mdn-service.js
|
||||
contract @mozilla.org/accountmanager/extension;1?name=mdn {e007d92e-1dd1-11b2-a61e-dc962c9b8571}
|
||||
category mailnews-accountmanager-extensions mdn-account-manager-extension @mozilla.org/accountmanager/extension;1?name=mdn
|
||||
16
mailnews/extensions/mdn/src/moz.build
Normal file
16
mailnews/extensions/mdn/src/moz.build
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SOURCES += [
|
||||
'nsMsgMdnGenerator.cpp',
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'mdn-service.js',
|
||||
'mdn-service.manifest',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'mail'
|
||||
|
||||
22
mailnews/extensions/mdn/src/nsMsgMdnCID.h
Normal file
22
mailnews/extensions/mdn/src/nsMsgMdnCID.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsMsgMdnCID_h__
|
||||
#define nsMsgMdnCID_h__
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "nsIFactory.h"
|
||||
#include "nsIComponentManager.h"
|
||||
|
||||
#include "nsIMsgMdnGenerator.h"
|
||||
|
||||
#define NS_MSGMDNGENERATOR_CONTRACTID \
|
||||
"@mozilla.org/messenger-mdn/generator;1"
|
||||
#define NS_MSGMDNGENERATOR_CID \
|
||||
{ /* ec917b13-8f73-4d4d-9146-d7f7aafe9076 */ \
|
||||
0xec917b13, 0x8f73, 0x4d4d, \
|
||||
{ 0x91, 0x46, 0xd7, 0xf7, 0xaa, 0xfe, 0x90, 0x76 }}
|
||||
|
||||
#endif /* nsMsgMdnCID_h__ */
|
||||
1139
mailnews/extensions/mdn/src/nsMsgMdnGenerator.cpp
Normal file
1139
mailnews/extensions/mdn/src/nsMsgMdnGenerator.cpp
Normal file
File diff suppressed because it is too large
Load diff
90
mailnews/extensions/mdn/src/nsMsgMdnGenerator.h
Normal file
90
mailnews/extensions/mdn/src/nsMsgMdnGenerator.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef _nsMsgMdnGenerator_H_
|
||||
#define _nsMsgMdnGenerator_H_
|
||||
|
||||
#include "nsIMsgMdnGenerator.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIUrlListener.h"
|
||||
#include "nsIMsgIncomingServer.h"
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIMsgIdentity.h"
|
||||
#include "nsIMsgWindow.h"
|
||||
#include "nsIMimeHeaders.h"
|
||||
#include "nsStringGlue.h"
|
||||
#include "MailNewsTypes2.h"
|
||||
|
||||
#define eNeverSendOp ((int32_t) 0)
|
||||
#define eAutoSendOp ((int32_t) 1)
|
||||
#define eAskMeOp ((int32_t) 2)
|
||||
#define eDeniedOp ((int32_t) 3)
|
||||
|
||||
class nsMsgMdnGenerator : public nsIMsgMdnGenerator, public nsIUrlListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMSGMDNGENERATOR
|
||||
NS_DECL_NSIURLLISTENER
|
||||
|
||||
nsMsgMdnGenerator();
|
||||
|
||||
private:
|
||||
virtual ~nsMsgMdnGenerator();
|
||||
|
||||
// Sanity Check methods
|
||||
bool ProcessSendMode(); // must called prior ValidateReturnPath
|
||||
bool ValidateReturnPath();
|
||||
bool NotInToOrCc();
|
||||
bool MailAddrMatch(const char *addr1, const char *addr2);
|
||||
|
||||
nsresult StoreMDNSentFlag(nsIMsgFolder *folder, nsMsgKey key);
|
||||
nsresult ClearMDNNeededFlag(nsIMsgFolder *folder, nsMsgKey key);
|
||||
nsresult NoteMDNRequestHandled();
|
||||
|
||||
nsresult CreateMdnMsg();
|
||||
nsresult CreateFirstPart();
|
||||
nsresult CreateSecondPart();
|
||||
nsresult CreateThirdPart();
|
||||
nsresult SendMdnMsg();
|
||||
|
||||
// string bundle helper methods
|
||||
nsresult GetStringFromName(const char16_t *aName, char16_t **aResultString);
|
||||
nsresult FormatStringFromName(const char16_t *aName,
|
||||
const char16_t *aString,
|
||||
char16_t **aResultString);
|
||||
|
||||
// other helper methods
|
||||
nsresult InitAndProcess(bool *needToAskUser);
|
||||
nsresult OutputAllHeaders();
|
||||
nsresult WriteString(const char *str);
|
||||
|
||||
private:
|
||||
EDisposeType m_disposeType;
|
||||
nsCOMPtr<nsIMsgWindow> m_window;
|
||||
nsCOMPtr<nsIOutputStream> m_outputStream;
|
||||
nsCOMPtr<nsIFile> m_file;
|
||||
nsCOMPtr<nsIMsgIdentity> m_identity;
|
||||
nsMsgKey m_key;
|
||||
nsCString m_charset;
|
||||
nsCString m_email;
|
||||
nsCString m_mimeSeparator;
|
||||
nsCString m_messageId;
|
||||
nsCOMPtr<nsIMsgFolder> m_folder;
|
||||
nsCOMPtr<nsIMsgIncomingServer> m_server;
|
||||
nsCOMPtr<nsIMimeHeaders> m_headers;
|
||||
nsCString m_dntRrt;
|
||||
int32_t m_notInToCcOp;
|
||||
int32_t m_outsideDomainOp;
|
||||
int32_t m_otherOp;
|
||||
bool m_reallySendMdn;
|
||||
bool m_autoSend;
|
||||
bool m_autoAction;
|
||||
bool m_mdnEnabled;
|
||||
};
|
||||
|
||||
#endif // _nsMsgMdnGenerator_H_
|
||||
|
||||
19
mailnews/extensions/moz.build
Normal file
19
mailnews/extensions/moz.build
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# These extensions are not optional.
|
||||
DIRS += [
|
||||
'mdn',
|
||||
'mailviews/public',
|
||||
'mailviews/src',
|
||||
'mailviews/content',
|
||||
'bayesian-spam-filter',
|
||||
'offline-startup',
|
||||
'newsblog',
|
||||
'fts3/public',
|
||||
'fts3/src',
|
||||
'smime',
|
||||
]
|
||||
|
||||
620
mailnews/extensions/newsblog/content/Feed.js
Normal file
620
mailnews/extensions/newsblog/content/Feed.js
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Cache for all of the feeds currently being downloaded, indexed by URL,
|
||||
// so the load event listener can access the Feed objects after it finishes
|
||||
// downloading the feed.
|
||||
var FeedCache =
|
||||
{
|
||||
mFeeds: {},
|
||||
|
||||
putFeed: function (aFeed)
|
||||
{
|
||||
this.mFeeds[this.normalizeHost(aFeed.url)] = aFeed;
|
||||
},
|
||||
|
||||
getFeed: function (aUrl)
|
||||
{
|
||||
let index = this.normalizeHost(aUrl);
|
||||
if (index in this.mFeeds)
|
||||
return this.mFeeds[index];
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
removeFeed: function (aUrl)
|
||||
{
|
||||
let index = this.normalizeHost(aUrl);
|
||||
if (index in this.mFeeds)
|
||||
delete this.mFeeds[index];
|
||||
},
|
||||
|
||||
normalizeHost: function (aUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
let normalizedUrl = Services.io.newURI(aUrl, null, null);
|
||||
normalizedUrl.host = normalizedUrl.host.toLowerCase();
|
||||
return normalizedUrl.spec
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
return aUrl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function Feed(aResource, aRSSServer)
|
||||
{
|
||||
this.resource = aResource.QueryInterface(Ci.nsIRDFResource);
|
||||
this.server = aRSSServer;
|
||||
}
|
||||
|
||||
Feed.prototype =
|
||||
{
|
||||
description: null,
|
||||
author: null,
|
||||
request: null,
|
||||
server: null,
|
||||
downloadCallback: null,
|
||||
resource: null,
|
||||
items: new Array(),
|
||||
itemsStored: 0,
|
||||
mFolder: null,
|
||||
mInvalidFeed: false,
|
||||
mFeedType: null,
|
||||
mLastModified: null,
|
||||
|
||||
get folder()
|
||||
{
|
||||
return this.mFolder;
|
||||
},
|
||||
|
||||
set folder (aFolder)
|
||||
{
|
||||
this.mFolder = aFolder;
|
||||
},
|
||||
|
||||
get name()
|
||||
{
|
||||
// Used for the feed's title in Subcribe dialog and opml export.
|
||||
let name = this.title || this.description || this.url;
|
||||
return name.replace(/[\n\r\t]+/g, " ").replace(/[\x00-\x1F]+/g, "");
|
||||
},
|
||||
|
||||
get folderName()
|
||||
{
|
||||
if (this.mFolderName)
|
||||
return this.mFolderName;
|
||||
|
||||
// Get a unique sanitized name. Use title or description as a base;
|
||||
// these are mandatory by spec. Length of 80 is plenty.
|
||||
let folderName = (this.title || this.description || "").substr(0,80);
|
||||
let defaultName = FeedUtils.strings.GetStringFromName("ImportFeedsNew");
|
||||
return this.mFolderName = FeedUtils.getSanitizedFolderName(this.server.rootMsgFolder,
|
||||
folderName,
|
||||
defaultName,
|
||||
true);
|
||||
},
|
||||
|
||||
download: function(aParseItems, aCallback)
|
||||
{
|
||||
// May be null.
|
||||
this.downloadCallback = aCallback;
|
||||
|
||||
// Whether or not to parse items when downloading and parsing the feed.
|
||||
// Defaults to true, but setting to false is useful for obtaining
|
||||
// just the title of the feed when the user subscribes to it.
|
||||
this.parseItems = aParseItems == null ? true : aParseItems ? true : false;
|
||||
|
||||
// Before we do anything, make sure the url is an http url. This is just
|
||||
// a sanity check so we don't try opening mailto urls, imap urls, etc. that
|
||||
// the user may have tried to subscribe to as an rss feed.
|
||||
if (!FeedUtils.isValidScheme(this.url))
|
||||
{
|
||||
// Simulate an invalid feed error.
|
||||
FeedUtils.log.info("Feed.download: invalid protocol for - " + this.url);
|
||||
this.onParseError(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Before we try to download the feed, make sure we aren't already
|
||||
// processing the feed by looking up the url in our feed cache.
|
||||
if (FeedCache.getFeed(this.url))
|
||||
{
|
||||
if (this.downloadCallback)
|
||||
this.downloadCallback.downloaded(this, FeedUtils.kNewsBlogFeedIsBusy);
|
||||
// Return, the feed is already in use.
|
||||
return;
|
||||
}
|
||||
|
||||
if (Services.io.offline) {
|
||||
// If offline and don't want to go online, just add the feed subscription;
|
||||
// it can be verified later (the folder name will be the url if not adding
|
||||
// to an existing folder). Only for subscribe actions; passive biff and
|
||||
// active get new messages are handled prior to getting here.
|
||||
let win = Services.wm.getMostRecentWindow("mail:3pane");
|
||||
if (!win.MailOfflineMgr.getNewMail()) {
|
||||
this.storeNextItem();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance(Ci.nsIXMLHttpRequest);
|
||||
// Must set onProgress before calling open.
|
||||
this.request.onprogress = this.onProgress;
|
||||
this.request.open("GET", this.url, true);
|
||||
this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE |
|
||||
Ci.nsIRequest.INHIBIT_CACHING;
|
||||
|
||||
// Some servers, if sent If-Modified-Since, will send 304 if subsequently
|
||||
// not sent If-Modified-Since, as in the case of an unsubscribe and new
|
||||
// subscribe. Send start of century date to force a download; some servers
|
||||
// will 304 on older dates (such as epoch 1970).
|
||||
let lastModified = this.lastModified || "Sat, 01 Jan 2000 00:00:00 GMT";
|
||||
this.request.setRequestHeader("If-Modified-Since", lastModified);
|
||||
|
||||
// Only order what you're going to eat...
|
||||
this.request.responseType = "document";
|
||||
this.request.overrideMimeType("text/xml");
|
||||
this.request.setRequestHeader("Accept", FeedUtils.REQUEST_ACCEPT);
|
||||
this.request.timeout = FeedUtils.REQUEST_TIMEOUT;
|
||||
this.request.onload = this.onDownloaded;
|
||||
this.request.onerror = this.onDownloadError;
|
||||
this.request.ontimeout = this.onDownloadError;
|
||||
FeedCache.putFeed(this);
|
||||
this.request.send(null);
|
||||
},
|
||||
|
||||
onDownloaded: function(aEvent)
|
||||
{
|
||||
let request = aEvent.target;
|
||||
let isHttp = request.channel.originalURI.scheme.startsWith("http");
|
||||
let url = request.channel.originalURI.spec;
|
||||
if (isHttp && (request.status < 200 || request.status >= 300))
|
||||
{
|
||||
Feed.prototype.onDownloadError(aEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
FeedUtils.log.debug("Feed.onDownloaded: got a download - " + url);
|
||||
let feed = FeedCache.getFeed(url);
|
||||
if (!feed)
|
||||
throw new Error("Feed.onDownloaded: error - couldn't retrieve feed " +
|
||||
"from cache");
|
||||
|
||||
// If the server sends a Last-Modified header, store the property on the
|
||||
// feed so we can use it when making future requests, to avoid downloading
|
||||
// and parsing feeds that have not changed. Don't update if merely checking
|
||||
// the url, as for subscribe move/copy, as a subsequent refresh may get a 304.
|
||||
// Save the response and persist it only upon successful completion of the
|
||||
// refresh cycle (i.e. not if the request is cancelled).
|
||||
let lastModifiedHeader = request.getResponseHeader("Last-Modified");
|
||||
feed.mLastModified = (lastModifiedHeader && feed.parseItems) ?
|
||||
lastModifiedHeader : null;
|
||||
|
||||
// The download callback is called asynchronously when parse() is done.
|
||||
feed.parse();
|
||||
},
|
||||
|
||||
onProgress: function(aEvent)
|
||||
{
|
||||
let request = aEvent.target;
|
||||
let url = request.channel.originalURI.spec;
|
||||
let feed = FeedCache.getFeed(url);
|
||||
|
||||
if (feed.downloadCallback)
|
||||
feed.downloadCallback.onProgress(feed, aEvent.loaded, aEvent.total,
|
||||
aEvent.lengthComputable);
|
||||
},
|
||||
|
||||
onDownloadError: function(aEvent)
|
||||
{
|
||||
let request = aEvent.target;
|
||||
let url = request.channel.originalURI.spec;
|
||||
let feed = FeedCache.getFeed(url);
|
||||
if (feed.downloadCallback)
|
||||
{
|
||||
// Generic network or 'not found' error initially.
|
||||
let error = FeedUtils.kNewsBlogRequestFailure;
|
||||
|
||||
if (request.status == 304) {
|
||||
// If the http status code is 304, the feed has not been modified
|
||||
// since we last downloaded it and does not need to be parsed.
|
||||
error = FeedUtils.kNewsBlogNoNewItems;
|
||||
}
|
||||
else {
|
||||
let [errType, errName] = FeedUtils.createTCPErrorFromFailedXHR(request);
|
||||
FeedUtils.log.info("Feed.onDownloaded: request errType:errName:statusCode - " +
|
||||
errType + ":" + errName + ":" + request.status);
|
||||
if (errType == "SecurityCertificate")
|
||||
// This is the code for nsINSSErrorsService.ERROR_CLASS_BAD_CERT
|
||||
// overrideable security certificate errors.
|
||||
error = FeedUtils.kNewsBlogBadCertError;
|
||||
|
||||
if (request.status == 401 || request.status == 403)
|
||||
// Unauthorized or Forbidden.
|
||||
error = FeedUtils.kNewsBlogNoAuthError;
|
||||
}
|
||||
|
||||
feed.downloadCallback.downloaded(feed, error);
|
||||
}
|
||||
|
||||
FeedCache.removeFeed(url);
|
||||
},
|
||||
|
||||
onParseError: function(aFeed)
|
||||
{
|
||||
if (!aFeed)
|
||||
return;
|
||||
|
||||
aFeed.mInvalidFeed = true;
|
||||
if (aFeed.downloadCallback)
|
||||
aFeed.downloadCallback.downloaded(aFeed, FeedUtils.kNewsBlogInvalidFeed);
|
||||
|
||||
FeedCache.removeFeed(aFeed.url);
|
||||
},
|
||||
|
||||
onUrlChange: function(aFeed, aOldUrl)
|
||||
{
|
||||
if (!aFeed)
|
||||
return;
|
||||
|
||||
// Simulate a cancel after a url update; next cycle will check the new url.
|
||||
aFeed.mInvalidFeed = true;
|
||||
if (aFeed.downloadCallback)
|
||||
aFeed.downloadCallback.downloaded(aFeed, FeedUtils.kNewsBlogCancel);
|
||||
|
||||
FeedCache.removeFeed(aOldUrl);
|
||||
},
|
||||
|
||||
get url()
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
let url = ds.GetTarget(this.resource, FeedUtils.DC_IDENTIFIER, true);
|
||||
if (url)
|
||||
url = url.QueryInterface(Ci.nsIRDFLiteral).Value;
|
||||
else
|
||||
url = this.resource.ValueUTF8;
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
get title()
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
let title = ds.GetTarget(this.resource, FeedUtils.DC_TITLE, true);
|
||||
if (title)
|
||||
title = title.QueryInterface(Ci.nsIRDFLiteral).Value;
|
||||
|
||||
return title;
|
||||
},
|
||||
|
||||
set title (aNewTitle)
|
||||
{
|
||||
if (!aNewTitle)
|
||||
return;
|
||||
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
aNewTitle = FeedUtils.rdf.GetLiteral(aNewTitle);
|
||||
let old_title = ds.GetTarget(this.resource, FeedUtils.DC_TITLE, true);
|
||||
if (old_title)
|
||||
ds.Change(this.resource, FeedUtils.DC_TITLE, old_title, aNewTitle);
|
||||
else
|
||||
ds.Assert(this.resource, FeedUtils.DC_TITLE, aNewTitle, true);
|
||||
},
|
||||
|
||||
get lastModified()
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
let lastModified = ds.GetTarget(this.resource,
|
||||
FeedUtils.DC_LASTMODIFIED,
|
||||
true);
|
||||
if (lastModified)
|
||||
lastModified = lastModified.QueryInterface(Ci.nsIRDFLiteral).Value;
|
||||
|
||||
return lastModified;
|
||||
},
|
||||
|
||||
set lastModified(aLastModified)
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
aLastModified = FeedUtils.rdf.GetLiteral(aLastModified);
|
||||
let old_lastmodified = ds.GetTarget(this.resource,
|
||||
FeedUtils.DC_LASTMODIFIED,
|
||||
true);
|
||||
if (old_lastmodified)
|
||||
ds.Change(this.resource, FeedUtils.DC_LASTMODIFIED,
|
||||
old_lastmodified, aLastModified);
|
||||
else
|
||||
ds.Assert(this.resource, FeedUtils.DC_LASTMODIFIED, aLastModified, true);
|
||||
},
|
||||
|
||||
get quickMode ()
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
let quickMode = ds.GetTarget(this.resource, FeedUtils.FZ_QUICKMODE, true);
|
||||
if (quickMode)
|
||||
{
|
||||
quickMode = quickMode.QueryInterface(Ci.nsIRDFLiteral);
|
||||
quickMode = quickMode.Value == "true";
|
||||
}
|
||||
|
||||
return quickMode;
|
||||
},
|
||||
|
||||
set quickMode (aNewQuickMode)
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
aNewQuickMode = FeedUtils.rdf.GetLiteral(aNewQuickMode);
|
||||
let old_quickMode = ds.GetTarget(this.resource,
|
||||
FeedUtils.FZ_QUICKMODE,
|
||||
true);
|
||||
if (old_quickMode)
|
||||
ds.Change(this.resource, FeedUtils.FZ_QUICKMODE,
|
||||
old_quickMode, aNewQuickMode);
|
||||
else
|
||||
ds.Assert(this.resource, FeedUtils.FZ_QUICKMODE,
|
||||
aNewQuickMode, true);
|
||||
},
|
||||
|
||||
get options ()
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
let options = ds.GetTarget(this.resource, FeedUtils.FZ_OPTIONS, true);
|
||||
if (options)
|
||||
return JSON.parse(options.QueryInterface(Ci.nsIRDFLiteral).Value);
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
set options (aOptions)
|
||||
{
|
||||
let newOptions = aOptions ? FeedUtils.newOptions(aOptions) :
|
||||
FeedUtils._optionsDefault;
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
newOptions = FeedUtils.rdf.GetLiteral(JSON.stringify(newOptions));
|
||||
let oldOptions = ds.GetTarget(this.resource, FeedUtils.FZ_OPTIONS, true);
|
||||
if (oldOptions)
|
||||
ds.Change(this.resource, FeedUtils.FZ_OPTIONS, oldOptions, newOptions);
|
||||
else
|
||||
ds.Assert(this.resource, FeedUtils.FZ_OPTIONS, newOptions, true);
|
||||
},
|
||||
|
||||
categoryPrefs: function ()
|
||||
{
|
||||
let categoryPrefsAcct = FeedUtils.getOptionsAcct(this.server).category;
|
||||
if (!this.options)
|
||||
return categoryPrefsAcct;
|
||||
|
||||
return this.options.category;
|
||||
},
|
||||
|
||||
get link ()
|
||||
{
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
let link = ds.GetTarget(this.resource, FeedUtils.RSS_LINK, true);
|
||||
if (link)
|
||||
link = link.QueryInterface(Ci.nsIRDFLiteral).Value;
|
||||
|
||||
return link;
|
||||
},
|
||||
|
||||
set link (aNewLink)
|
||||
{
|
||||
if (!aNewLink)
|
||||
return;
|
||||
|
||||
let ds = FeedUtils.getSubscriptionsDS(this.server);
|
||||
aNewLink = FeedUtils.rdf.GetLiteral(aNewLink);
|
||||
let old_link = ds.GetTarget(this.resource, FeedUtils.RSS_LINK, true);
|
||||
if (old_link)
|
||||
ds.Change(this.resource, FeedUtils.RSS_LINK, old_link, aNewLink);
|
||||
else
|
||||
ds.Assert(this.resource, FeedUtils.RSS_LINK, aNewLink, true);
|
||||
},
|
||||
|
||||
parse: function()
|
||||
{
|
||||
// Create a feed parser which will parse the feed.
|
||||
let parser = new FeedParser();
|
||||
this.itemsToStore = parser.parseFeed(this, this.request.responseXML);
|
||||
parser = null;
|
||||
|
||||
if (this.mInvalidFeed)
|
||||
{
|
||||
this.request = null;
|
||||
this.mInvalidFeed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// storeNextItem() will iterate through the parsed items, storing each one.
|
||||
this.itemsToStoreIndex = 0;
|
||||
this.itemsStored = 0;
|
||||
this.storeNextItem();
|
||||
},
|
||||
|
||||
invalidateItems: function ()
|
||||
{
|
||||
let ds = FeedUtils.getItemsDS(this.server);
|
||||
FeedUtils.log.debug("Feed.invalidateItems: for url - " + this.url);
|
||||
let items = ds.GetSources(FeedUtils.FZ_FEED, this.resource, true);
|
||||
let item;
|
||||
|
||||
while (items.hasMoreElements())
|
||||
{
|
||||
item = items.getNext();
|
||||
item = item.QueryInterface(Ci.nsIRDFResource);
|
||||
FeedUtils.log.trace("Feed.invalidateItems: item - " + item.Value);
|
||||
let valid = ds.GetTarget(item, FeedUtils.FZ_VALID, true);
|
||||
if (valid)
|
||||
ds.Unassert(item, FeedUtils.FZ_VALID, valid, true);
|
||||
}
|
||||
},
|
||||
|
||||
removeInvalidItems: function(aDeleteFeed)
|
||||
{
|
||||
let ds = FeedUtils.getItemsDS(this.server);
|
||||
FeedUtils.log.debug("Feed.removeInvalidItems: for url - " + this.url);
|
||||
let items = ds.GetSources(FeedUtils.FZ_FEED, this.resource, true);
|
||||
let item;
|
||||
let currentTime = new Date().getTime();
|
||||
while (items.hasMoreElements())
|
||||
{
|
||||
item = items.getNext();
|
||||
item = item.QueryInterface(Ci.nsIRDFResource);
|
||||
|
||||
if (ds.HasAssertion(item, FeedUtils.FZ_VALID,
|
||||
FeedUtils.RDF_LITERAL_TRUE, true))
|
||||
continue;
|
||||
|
||||
let lastSeenTime = ds.GetTarget(item, FeedUtils.FZ_LAST_SEEN_TIMESTAMP, true);
|
||||
if (lastSeenTime)
|
||||
lastSeenTime = parseInt(lastSeenTime.QueryInterface(Ci.nsIRDFLiteral).Value)
|
||||
else
|
||||
lastSeenTime = 0;
|
||||
|
||||
if ((currentTime - lastSeenTime) < FeedUtils.INVALID_ITEM_PURGE_DELAY &&
|
||||
!aDeleteFeed)
|
||||
// Don't immediately purge items in active feeds; do so for deleted feeds.
|
||||
continue;
|
||||
|
||||
FeedUtils.log.trace("Feed.removeInvalidItems: item - " + item.Value);
|
||||
ds.Unassert(item, FeedUtils.FZ_FEED, this.resource, true);
|
||||
if (ds.hasArcOut(item, FeedUtils.FZ_FEED))
|
||||
FeedUtils.log.debug("Feed.removeInvalidItems: " + item.Value +
|
||||
" is from more than one feed; only the reference to" +
|
||||
" this feed removed");
|
||||
else
|
||||
FeedUtils.removeAssertions(ds, item);
|
||||
}
|
||||
},
|
||||
|
||||
createFolder: function()
|
||||
{
|
||||
if (this.folder)
|
||||
return;
|
||||
|
||||
try {
|
||||
this.folder = this.server.rootMsgFolder
|
||||
.QueryInterface(Ci.nsIMsgLocalMailFolder)
|
||||
.createLocalSubfolder(this.folderName);
|
||||
}
|
||||
catch (ex) {
|
||||
// An error creating.
|
||||
FeedUtils.log.info("Feed.createFolder: error creating folder - '"+
|
||||
this.folderName+"' in parent folder "+
|
||||
this.server.rootMsgFolder.filePath.path + " -- "+ex);
|
||||
// But its remnants are still there, clean up.
|
||||
let xfolder = this.server.rootMsgFolder.getChildNamed(this.folderName);
|
||||
this.server.rootMsgFolder.propagateDelete(xfolder, true, null);
|
||||
}
|
||||
},
|
||||
|
||||
// Gets the next item from itemsToStore and forces that item to be stored
|
||||
// to the folder. If more items are left to be stored, fires a timer for
|
||||
// the next one, otherwise triggers a download done notification to the UI.
|
||||
storeNextItem: function()
|
||||
{
|
||||
if (FeedUtils.CANCEL_REQUESTED)
|
||||
{
|
||||
FeedUtils.CANCEL_REQUESTED = false;
|
||||
this.cleanupParsingState(this, FeedUtils.kNewsBlogCancel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.itemsToStore || !this.itemsToStore.length)
|
||||
{
|
||||
let code = FeedUtils.kNewsBlogSuccess;
|
||||
this.createFolder();
|
||||
if (!this.folder)
|
||||
code = FeedUtils.kNewsBlogFileError;
|
||||
this.cleanupParsingState(this, code);
|
||||
return;
|
||||
}
|
||||
|
||||
let item = this.itemsToStore[this.itemsToStoreIndex];
|
||||
|
||||
if (item.store())
|
||||
this.itemsStored++;
|
||||
|
||||
if (!this.folder)
|
||||
{
|
||||
this.cleanupParsingState(this, FeedUtils.kNewsBlogFileError);
|
||||
return;
|
||||
}
|
||||
|
||||
this.itemsToStoreIndex++;
|
||||
|
||||
// If the listener is tracking progress for each item, report it here.
|
||||
if (item.feed.downloadCallback && item.feed.downloadCallback.onFeedItemStored)
|
||||
item.feed.downloadCallback.onFeedItemStored(item.feed,
|
||||
this.itemsToStoreIndex,
|
||||
this.itemsToStore.length);
|
||||
|
||||
// Eventually we'll report individual progress here.
|
||||
|
||||
if (this.itemsToStoreIndex < this.itemsToStore.length)
|
||||
{
|
||||
if (!this.storeItemsTimer)
|
||||
this.storeItemsTimer = Cc["@mozilla.org/timer;1"].
|
||||
createInstance(Ci.nsITimer);
|
||||
this.storeItemsTimer.initWithCallback(this, 50, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have just finished downloading one or more feed items into the
|
||||
// destination folder; if the folder is still listed as having new
|
||||
// messages in it, then we should set the biff state on the folder so the
|
||||
// right RDF UI changes happen in the folder pane to indicate new mail.
|
||||
if (item.feed.folder.hasNewMessages)
|
||||
{
|
||||
item.feed.folder.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NewMail;
|
||||
// Run the bayesian spam filter, if enabled.
|
||||
item.feed.folder.callFilterPlugins(null);
|
||||
}
|
||||
|
||||
this.cleanupParsingState(this, FeedUtils.kNewsBlogSuccess);
|
||||
}
|
||||
},
|
||||
|
||||
cleanupParsingState: function(aFeed, aCode)
|
||||
{
|
||||
// Now that we are done parsing the feed, remove the feed from the cache.
|
||||
FeedCache.removeFeed(aFeed.url);
|
||||
|
||||
if (aFeed.parseItems)
|
||||
{
|
||||
// Do this only if we're in parse/store mode.
|
||||
aFeed.removeInvalidItems(false);
|
||||
|
||||
if (aCode == FeedUtils.kNewsBlogSuccess && aFeed.mLastModified)
|
||||
aFeed.lastModified = aFeed.mLastModified;
|
||||
|
||||
// Flush any feed item changes to disk.
|
||||
let ds = FeedUtils.getItemsDS(aFeed.server);
|
||||
ds.Flush();
|
||||
FeedUtils.log.debug("Feed.cleanupParsingState: items stored - " + this.itemsStored);
|
||||
}
|
||||
|
||||
// Force the xml http request to go away. This helps reduce some nasty
|
||||
// assertions on shut down.
|
||||
this.request = null;
|
||||
this.itemsToStore = "";
|
||||
this.itemsToStoreIndex = 0;
|
||||
this.itemsStored = 0;
|
||||
this.storeItemsTimer = null;
|
||||
|
||||
if (aFeed.downloadCallback)
|
||||
aFeed.downloadCallback.downloaded(aFeed, aCode);
|
||||
},
|
||||
|
||||
// nsITimerCallback
|
||||
notify: function(aTimer)
|
||||
{
|
||||
this.storeNextItem();
|
||||
}
|
||||
};
|
||||
490
mailnews/extensions/newsblog/content/FeedItem.js
Normal file
490
mailnews/extensions/newsblog/content/FeedItem.js
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
function FeedItem()
|
||||
{
|
||||
this.mDate = FeedUtils.getValidRFC5322Date();
|
||||
this.mUnicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
this.mParserUtils = Cc["@mozilla.org/parserutils;1"].
|
||||
getService(Ci.nsIParserUtils);
|
||||
}
|
||||
|
||||
FeedItem.prototype =
|
||||
{
|
||||
// Only for IETF Atom.
|
||||
xmlContentBase: null,
|
||||
id: null,
|
||||
feed: null,
|
||||
description: null,
|
||||
content: null,
|
||||
enclosures: [],
|
||||
title: null,
|
||||
author: "anonymous",
|
||||
inReplyTo: "",
|
||||
keywords: [],
|
||||
mURL: null,
|
||||
characterSet: "UTF-8",
|
||||
|
||||
ENCLOSURE_BOUNDARY_PREFIX: "--------------", // 14 dashes
|
||||
ENCLOSURE_HEADER_BOUNDARY_PREFIX: "------------", // 12 dashes
|
||||
MESSAGE_TEMPLATE: '\n' +
|
||||
'<html>\n' +
|
||||
' <head>\n' +
|
||||
' <title>%TITLE%</title>\n' +
|
||||
' <base href="%BASE%">\n' +
|
||||
' </head>\n' +
|
||||
' <body id="msgFeedSummaryBody" selected="false">\n' +
|
||||
' %CONTENT%\n' +
|
||||
' </body>\n' +
|
||||
'</html>\n',
|
||||
|
||||
get url()
|
||||
{
|
||||
return this.mURL;
|
||||
},
|
||||
|
||||
set url(aVal)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.mURL = Services.io.newURI(aVal, null, null).spec;
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
// The url as published or constructed can be a non url. It's used as a
|
||||
// feeditem identifier in feeditems.rdf, as a messageId, and as an href
|
||||
// and for the content-base header. Save as is; ensure not null.
|
||||
this.mURL = aVal ? aVal : "";
|
||||
}
|
||||
},
|
||||
|
||||
get date()
|
||||
{
|
||||
return this.mDate;
|
||||
},
|
||||
|
||||
set date (aVal)
|
||||
{
|
||||
this.mDate = aVal;
|
||||
},
|
||||
|
||||
get identity ()
|
||||
{
|
||||
return this.feed.name + ": " + this.title + " (" + this.id + ")"
|
||||
},
|
||||
|
||||
normalizeMessageID: function(messageID)
|
||||
{
|
||||
// Escape occurrences of message ID meta characters <, >, and @.
|
||||
messageID.replace(/</g, "%3C");
|
||||
messageID.replace(/>/g, "%3E");
|
||||
messageID.replace(/@/g, "%40");
|
||||
messageID = "<" + messageID.trim() + "@" + "localhost.localdomain" + ">";
|
||||
|
||||
FeedUtils.log.trace("FeedItem.normalizeMessageID: messageID - " + messageID);
|
||||
return messageID;
|
||||
},
|
||||
|
||||
get itemUniqueURI()
|
||||
{
|
||||
return this.createURN(this.id);
|
||||
},
|
||||
|
||||
get contentBase()
|
||||
{
|
||||
if(this.xmlContentBase)
|
||||
return this.xmlContentBase
|
||||
else
|
||||
return this.mURL;
|
||||
},
|
||||
|
||||
store: function()
|
||||
{
|
||||
// this.title and this.content contain HTML.
|
||||
// this.mUrl and this.contentBase contain plain text.
|
||||
|
||||
let stored = false;
|
||||
let resource = this.findStoredResource();
|
||||
if (!this.feed.folder)
|
||||
return stored;
|
||||
|
||||
if (resource == null)
|
||||
{
|
||||
resource = FeedUtils.rdf.GetResource(this.itemUniqueURI);
|
||||
if (!this.content)
|
||||
{
|
||||
FeedUtils.log.trace("FeedItem.store: " + this.identity +
|
||||
" no content; storing description or title");
|
||||
this.content = this.description || this.title;
|
||||
}
|
||||
|
||||
let content = this.MESSAGE_TEMPLATE;
|
||||
content = content.replace(/%TITLE%/, this.title);
|
||||
content = content.replace(/%BASE%/, this.htmlEscape(this.contentBase));
|
||||
content = content.replace(/%CONTENT%/, this.content);
|
||||
this.content = content;
|
||||
this.writeToFolder();
|
||||
this.markStored(resource);
|
||||
stored = true;
|
||||
}
|
||||
this.markValid(resource);
|
||||
return stored;
|
||||
},
|
||||
|
||||
findStoredResource: function()
|
||||
{
|
||||
// Checks to see if the item has already been stored in its feed's
|
||||
// message folder.
|
||||
FeedUtils.log.trace("FeedItem.findStoredResource: checking if stored - " +
|
||||
this.identity);
|
||||
|
||||
let server = this.feed.server;
|
||||
let folder = this.feed.folder;
|
||||
|
||||
if (!folder)
|
||||
{
|
||||
FeedUtils.log.debug("FeedItem.findStoredResource: folder '" +
|
||||
this.feed.folderName +
|
||||
"' doesn't exist; creating as child of " +
|
||||
server.rootMsgFolder.prettyName + "\n");
|
||||
this.feed.createFolder();
|
||||
return null;
|
||||
}
|
||||
|
||||
let ds = FeedUtils.getItemsDS(server);
|
||||
let itemURI = this.itemUniqueURI;
|
||||
let itemResource = FeedUtils.rdf.GetResource(itemURI);
|
||||
|
||||
let downloaded = ds.GetTarget(itemResource, FeedUtils.FZ_STORED, true);
|
||||
|
||||
if (!downloaded ||
|
||||
downloaded.QueryInterface(Ci.nsIRDFLiteral).Value == "false")
|
||||
{
|
||||
FeedUtils.log.trace("FeedItem.findStoredResource: not stored");
|
||||
return null;
|
||||
}
|
||||
|
||||
FeedUtils.log.trace("FeedItem.findStoredResource: already stored");
|
||||
return itemResource;
|
||||
},
|
||||
|
||||
markValid: function(resource)
|
||||
{
|
||||
let ds = FeedUtils.getItemsDS(this.feed.server);
|
||||
|
||||
let newTimeStamp = FeedUtils.rdf.GetLiteral(new Date().getTime());
|
||||
let currentTimeStamp = ds.GetTarget(resource,
|
||||
FeedUtils.FZ_LAST_SEEN_TIMESTAMP,
|
||||
true);
|
||||
if (currentTimeStamp)
|
||||
ds.Change(resource, FeedUtils.FZ_LAST_SEEN_TIMESTAMP,
|
||||
currentTimeStamp, newTimeStamp);
|
||||
else
|
||||
ds.Assert(resource, FeedUtils.FZ_LAST_SEEN_TIMESTAMP,
|
||||
newTimeStamp, true);
|
||||
|
||||
if (!ds.HasAssertion(resource, FeedUtils.FZ_FEED,
|
||||
FeedUtils.rdf.GetResource(this.feed.url), true))
|
||||
ds.Assert(resource, FeedUtils.FZ_FEED,
|
||||
FeedUtils.rdf.GetResource(this.feed.url), true);
|
||||
|
||||
if (ds.hasArcOut(resource, FeedUtils.FZ_VALID))
|
||||
{
|
||||
let currentValue = ds.GetTarget(resource, FeedUtils.FZ_VALID, true);
|
||||
ds.Change(resource, FeedUtils.FZ_VALID,
|
||||
currentValue, FeedUtils.RDF_LITERAL_TRUE);
|
||||
}
|
||||
else
|
||||
ds.Assert(resource, FeedUtils.FZ_VALID, FeedUtils.RDF_LITERAL_TRUE, true);
|
||||
},
|
||||
|
||||
markStored: function(resource)
|
||||
{
|
||||
let ds = FeedUtils.getItemsDS(this.feed.server);
|
||||
|
||||
if (!ds.HasAssertion(resource, FeedUtils.FZ_FEED,
|
||||
FeedUtils.rdf.GetResource(this.feed.url), true))
|
||||
ds.Assert(resource, FeedUtils.FZ_FEED,
|
||||
FeedUtils.rdf.GetResource(this.feed.url), true);
|
||||
|
||||
let currentValue;
|
||||
if (ds.hasArcOut(resource, FeedUtils.FZ_STORED))
|
||||
{
|
||||
currentValue = ds.GetTarget(resource, FeedUtils.FZ_STORED, true);
|
||||
ds.Change(resource, FeedUtils.FZ_STORED,
|
||||
currentValue, FeedUtils.RDF_LITERAL_TRUE);
|
||||
}
|
||||
else
|
||||
ds.Assert(resource, FeedUtils.FZ_STORED,
|
||||
FeedUtils.RDF_LITERAL_TRUE, true);
|
||||
},
|
||||
|
||||
mimeEncodeSubject: function(aSubject, aCharset)
|
||||
{
|
||||
// This routine sometimes throws exceptions for mis-encoded data so
|
||||
// wrap it with a try catch for now.
|
||||
let newSubject;
|
||||
try
|
||||
{
|
||||
newSubject = mailServices.mimeConverter.encodeMimePartIIStr_UTF8(aSubject,
|
||||
false,
|
||||
aCharset, 9, 72);
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
newSubject = aSubject;
|
||||
}
|
||||
|
||||
return newSubject;
|
||||
},
|
||||
|
||||
writeToFolder: function()
|
||||
{
|
||||
FeedUtils.log.trace("FeedItem.writeToFolder: " + this.identity +
|
||||
" writing to message folder " + this.feed.name);
|
||||
// Convert the title to UTF-16 before performing our HTML entity
|
||||
// replacement reg expressions.
|
||||
let title = this.title;
|
||||
|
||||
// The subject may contain HTML entities. Convert these to their unencoded
|
||||
// state. i.e. & becomes '&'.
|
||||
title = this.mParserUtils.convertToPlainText(
|
||||
title,
|
||||
Ci.nsIDocumentEncoder.OutputSelectionOnly |
|
||||
Ci.nsIDocumentEncoder.OutputAbsoluteLinks,
|
||||
0);
|
||||
|
||||
// Compress white space in the subject to make it look better. Trim
|
||||
// leading/trailing spaces to prevent mbox header folding issue at just
|
||||
// the right subject length.
|
||||
title = title.replace(/[\t\r\n]+/g, " ").trim();
|
||||
|
||||
this.title = this.mimeEncodeSubject(title, this.characterSet);
|
||||
|
||||
// If the date looks like it's in W3C-DTF format, convert it into
|
||||
// an IETF standard date. Otherwise assume it's in IETF format.
|
||||
if (this.mDate.search(/^\d\d\d\d/) != -1)
|
||||
this.mDate = new Date(this.mDate).toUTCString();
|
||||
|
||||
// If there is an inreplyto value, create the headers.
|
||||
let inreplytoHdrsStr = this.inReplyTo ?
|
||||
("References: " + this.inReplyTo + "\n" +
|
||||
"In-Reply-To: " + this.inReplyTo + "\n") : "";
|
||||
|
||||
// If there are keywords (categories), create the headers. In the case of
|
||||
// a longer than RFC5322 recommended line length, create multiple folded
|
||||
// lines (easier to parse than multiple Keywords headers).
|
||||
let keywordsStr = "";
|
||||
if (this.keywords.length)
|
||||
{
|
||||
let HEADER = "Keywords: ";
|
||||
let MAXLEN = 78;
|
||||
keywordsStr = HEADER;
|
||||
let keyword;
|
||||
let keywords = [].concat(this.keywords);
|
||||
let lines = [];
|
||||
while (keywords.length)
|
||||
{
|
||||
keyword = keywords.shift();
|
||||
if (keywordsStr.length + keyword.length > MAXLEN)
|
||||
{
|
||||
lines.push(keywordsStr)
|
||||
keywordsStr = " ".repeat(HEADER.length);
|
||||
}
|
||||
keywordsStr += keyword + ",";
|
||||
}
|
||||
keywordsStr = keywordsStr.replace(/,$/,"\n");
|
||||
lines.push(keywordsStr)
|
||||
keywordsStr = lines.join("\n");
|
||||
}
|
||||
|
||||
// Escape occurrences of "From " at the beginning of lines of
|
||||
// content per the mbox standard, since "From " denotes a new
|
||||
// message, and add a line break so we know the last line has one.
|
||||
this.content = this.content.replace(/([\r\n]+)(>*From )/g, "$1>$2");
|
||||
this.content += "\n";
|
||||
|
||||
// The opening line of the message, mandated by standards to start
|
||||
// with "From ". It's useful to construct this separately because
|
||||
// we not only need to write it into the message, we also need to
|
||||
// use it to calculate the offset of the X-Mozilla-Status lines from
|
||||
// the front of the message for the statusOffset property of the
|
||||
// DB header object.
|
||||
let openingLine = 'From - ' + this.mDate + '\n';
|
||||
|
||||
let source =
|
||||
openingLine +
|
||||
'X-Mozilla-Status: 0000\n' +
|
||||
'X-Mozilla-Status2: 00000000\n' +
|
||||
'X-Mozilla-Keys: ' + " ".repeat(80) + '\n' +
|
||||
'Received: by localhost; ' + FeedUtils.getValidRFC5322Date() + '\n' +
|
||||
'Date: ' + this.mDate + '\n' +
|
||||
'Message-Id: ' + this.normalizeMessageID(this.id) + '\n' +
|
||||
'From: ' + this.author + '\n' +
|
||||
'MIME-Version: 1.0\n' +
|
||||
'Subject: ' + this.title + '\n' +
|
||||
inreplytoHdrsStr +
|
||||
keywordsStr +
|
||||
'Content-Transfer-Encoding: 8bit\n' +
|
||||
'Content-Base: ' + this.mURL + '\n';
|
||||
|
||||
if (this.enclosures.length)
|
||||
{
|
||||
let boundaryID = source.length;
|
||||
source += 'Content-Type: multipart/mixed; boundary="' +
|
||||
this.ENCLOSURE_HEADER_BOUNDARY_PREFIX + boundaryID + '"' + '\n\n' +
|
||||
'This is a multi-part message in MIME format.\n' +
|
||||
this.ENCLOSURE_BOUNDARY_PREFIX + boundaryID + '\n' +
|
||||
'Content-Type: text/html; charset=' + this.characterSet + '\n' +
|
||||
'Content-Transfer-Encoding: 8bit\n' +
|
||||
this.content;
|
||||
|
||||
this.enclosures.forEach(function(enclosure) {
|
||||
source += enclosure.convertToAttachment(boundaryID);
|
||||
});
|
||||
|
||||
source += this.ENCLOSURE_BOUNDARY_PREFIX + boundaryID + '--' + '\n\n\n';
|
||||
}
|
||||
else
|
||||
source += 'Content-Type: text/html; charset=' + this.characterSet + '\n' +
|
||||
this.content;
|
||||
|
||||
FeedUtils.log.trace("FeedItem.writeToFolder: " + this.identity +
|
||||
" is " + source.length + " characters long");
|
||||
|
||||
// Get the folder and database storing the feed's messages and headers.
|
||||
let folder = this.feed.folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
|
||||
let msgFolder = folder.QueryInterface(Ci.nsIMsgFolder);
|
||||
msgFolder.gettingNewMessages = true;
|
||||
// Source is a unicode string, we want to save a char * string in
|
||||
// the original charset. So convert back.
|
||||
this.mUnicodeConverter.charset = this.characterSet;
|
||||
let msgDBHdr = folder.addMessage(this.mUnicodeConverter.ConvertFromUnicode(source));
|
||||
msgDBHdr.OrFlags(Ci.nsMsgMessageFlags.FeedMsg);
|
||||
msgFolder.gettingNewMessages = false;
|
||||
this.tagItem(msgDBHdr, this.keywords);
|
||||
},
|
||||
|
||||
/**
|
||||
* Autotag messages.
|
||||
*
|
||||
* @param nsIMsgDBHdr aMsgDBHdr - message to tag
|
||||
* @param array aKeywords - keywords (tags)
|
||||
*/
|
||||
tagItem: function(aMsgDBHdr, aKeywords)
|
||||
{
|
||||
let categoryPrefs = this.feed.categoryPrefs();
|
||||
if (!aKeywords.length || !categoryPrefs.enabled)
|
||||
return;
|
||||
|
||||
let msgArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
msgArray.appendElement(aMsgDBHdr, false);
|
||||
|
||||
let prefix = categoryPrefs.prefixEnabled ? categoryPrefs.prefix : "";
|
||||
let rtl = Services.prefs.getIntPref("bidi.direction") == 2;
|
||||
|
||||
let keys = [];
|
||||
for (let keyword of aKeywords)
|
||||
{
|
||||
keyword = rtl ? keyword + prefix : prefix + keyword;
|
||||
let keyForTag = MailServices.tags.getKeyForTag(keyword);
|
||||
if (!keyForTag)
|
||||
{
|
||||
// Add the tag if it doesn't exist.
|
||||
MailServices.tags.addTag(keyword, "", FeedUtils.AUTOTAG);
|
||||
keyForTag = MailServices.tags.getKeyForTag(keyword);
|
||||
}
|
||||
|
||||
// Add the tag key to the keys array.
|
||||
keys.push(keyForTag);
|
||||
}
|
||||
|
||||
if (keys.length)
|
||||
// Add the keys to the message.
|
||||
aMsgDBHdr.folder.addKeywordsToMessages(msgArray, keys.join(" "));
|
||||
},
|
||||
|
||||
htmlEscape: function(s)
|
||||
{
|
||||
s = s.replace(/&/g, "&");
|
||||
s = s.replace(/>/g, ">");
|
||||
s = s.replace(/</g, "<");
|
||||
s = s.replace(/'/g, "'");
|
||||
s = s.replace(/"/g, """);
|
||||
return s;
|
||||
},
|
||||
|
||||
createURN: function(aName)
|
||||
{
|
||||
// Returns name as a URN in the 'feeditem' namespace. The returned URN is
|
||||
// (or is intended to be) RFC2141 compliant.
|
||||
// The builtin encodeURI provides nearly the exact encoding functionality
|
||||
// required by the RFC. The exceptions are that NULL characters should not
|
||||
// appear, and that #, /, ?, &, and ~ should be escaped.
|
||||
// NULL characters are removed before encoding.
|
||||
|
||||
let name = aName.replace(/\0/g, "");
|
||||
let encoded = encodeURI(name);
|
||||
encoded = encoded.replace(/\#/g, "%23");
|
||||
encoded = encoded.replace(/\//g, "%2f");
|
||||
encoded = encoded.replace(/\?/g, "%3f");
|
||||
encoded = encoded.replace(/\&/g, "%26");
|
||||
encoded = encoded.replace(/\~/g, "%7e");
|
||||
|
||||
return FeedUtils.FZ_ITEM_NS + encoded;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A feed enclosure is to RSS what an attachment is for e-mail. We make
|
||||
// enclosures look like attachments in the UI.
|
||||
function FeedEnclosure(aURL, aContentType, aLength, aTitle)
|
||||
{
|
||||
this.mURL = aURL;
|
||||
// Store a reasonable mimetype if content-type is not present.
|
||||
this.mContentType = aContentType || "application/unknown";
|
||||
this.mLength = aLength;
|
||||
this.mTitle = aTitle;
|
||||
|
||||
// Generate a fileName from the URL.
|
||||
if (this.mURL)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.mFileName = Services.io.newURI(this.mURL, null, null).
|
||||
QueryInterface(Ci.nsIURL).
|
||||
fileName;
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
this.mFileName = this.mURL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FeedEnclosure.prototype =
|
||||
{
|
||||
mURL: "",
|
||||
mContentType: "",
|
||||
mLength: 0,
|
||||
mFileName: "",
|
||||
mTitle: "",
|
||||
ENCLOSURE_BOUNDARY_PREFIX: "--------------", // 14 dashes
|
||||
|
||||
// Returns a string that looks like an e-mail attachment which represents
|
||||
// the enclosure.
|
||||
convertToAttachment: function(aBoundaryID)
|
||||
{
|
||||
return '\n' +
|
||||
this.ENCLOSURE_BOUNDARY_PREFIX + aBoundaryID + '\n' +
|
||||
'Content-Type: ' + this.mContentType +
|
||||
'; name="' + (this.mTitle || this.mFileName) +
|
||||
(this.mLength ? '"; size=' + this.mLength : '"') + '\n' +
|
||||
'X-Mozilla-External-Attachment-URL: ' + this.mURL + '\n' +
|
||||
'Content-Disposition: attachment; filename="' + this.mFileName + '"\n\n' +
|
||||
FeedUtils.strings.GetStringFromName("externalAttachmentMsg") + '\n';
|
||||
}
|
||||
};
|
||||
1608
mailnews/extensions/newsblog/content/FeedUtils.jsm
Normal file
1608
mailnews/extensions/newsblog/content/FeedUtils.jsm
Normal file
File diff suppressed because it is too large
Load diff
63
mailnews/extensions/newsblog/content/am-newsblog.js
Normal file
63
mailnews/extensions/newsblog/content/am-newsblog.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource:///modules/FeedUtils.jsm");
|
||||
|
||||
var gServer, autotagEnable, autotagUsePrefix, autotagPrefix;
|
||||
|
||||
function onInit(aPageId, aServerId)
|
||||
{
|
||||
var accountName = document.getElementById("server.prettyName");
|
||||
var title = document.getElementById("am-newsblog-title");
|
||||
var defaultTitle = title.getAttribute("defaultTitle");
|
||||
|
||||
var titleValue;
|
||||
if (accountName.value)
|
||||
titleValue = defaultTitle + " - <" + accountName.value + ">";
|
||||
else
|
||||
titleValue = defaultTitle;
|
||||
|
||||
title.setAttribute("title", titleValue);
|
||||
document.title = titleValue;
|
||||
|
||||
onCheckItem("server.biffMinutes", ["server.doBiff"]);
|
||||
|
||||
autotagEnable = document.getElementById("autotagEnable");
|
||||
autotagUsePrefix = document.getElementById("autotagUsePrefix");
|
||||
autotagPrefix = document.getElementById("autotagPrefix");
|
||||
|
||||
let categoryPrefsAcct = FeedUtils.getOptionsAcct(gServer).category;
|
||||
autotagEnable.checked = categoryPrefsAcct.enabled;
|
||||
autotagUsePrefix.disabled = !autotagEnable.checked;
|
||||
autotagUsePrefix.checked = categoryPrefsAcct.prefixEnabled;
|
||||
autotagPrefix.disabled = autotagUsePrefix.disabled || !autotagUsePrefix.checked;
|
||||
autotagPrefix.value = categoryPrefsAcct.prefix;
|
||||
}
|
||||
|
||||
function onPreInit(account, accountValues)
|
||||
{
|
||||
gServer = account.incomingServer;
|
||||
}
|
||||
|
||||
function setCategoryPrefs(aNode)
|
||||
{
|
||||
let options = FeedUtils.getOptionsAcct(gServer);
|
||||
switch (aNode.id) {
|
||||
case "autotagEnable":
|
||||
options.category.enabled = aNode.checked;
|
||||
autotagUsePrefix.disabled = !aNode.checked;
|
||||
autotagPrefix.disabled = !aNode.checked || !autotagUsePrefix.checked;
|
||||
break;
|
||||
case "autotagUsePrefix":
|
||||
options.category.prefixEnabled = aNode.checked;
|
||||
autotagPrefix.disabled = aNode.disabled || !aNode.checked;
|
||||
break;
|
||||
case "autotagPrefix":
|
||||
options.category.prefix = aNode.value;
|
||||
break;
|
||||
}
|
||||
|
||||
FeedUtils.setOptionsAcct(gServer, options)
|
||||
}
|
||||
155
mailnews/extensions/newsblog/content/am-newsblog.xul
Normal file
155
mailnews/extensions/newsblog/content/am-newsblog.xul
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger-newsblog/skin/feed-subscriptions.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE page [
|
||||
<!ENTITY % newsblogDTD SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd" >
|
||||
%newsblogDTD;
|
||||
<!ENTITY % feedDTD SYSTEM "chrome://messenger-newsblog/locale/feed-subscriptions.dtd" >
|
||||
%feedDTD;
|
||||
<!ENTITY % accountNoIdentDTD SYSTEM "chrome://messenger/locale/am-serverwithnoidentities.dtd" >
|
||||
%accountNoIdentDTD;
|
||||
<!ENTITY % accountServerTopDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">
|
||||
%accountServerTopDTD;
|
||||
]>
|
||||
|
||||
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
class="color-dialog"
|
||||
title="&accountTitle.label;"
|
||||
onload="parent.onPanelLoaded('am-newsblog.xul');">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/AccountManager.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger-newsblog/content/am-newsblog.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger-newsblog/content/newsblogOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/amUtils.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/am-prefs.js"/>
|
||||
|
||||
<vbox flex="1" style="overflow: auto;">
|
||||
|
||||
<dialogheader id="am-newsblog-title" defaultTitle="&accountTitle.label;"/>
|
||||
|
||||
<description class="secDesc">&accountSettingsDesc.label;</description>
|
||||
|
||||
<hbox align="center">
|
||||
<label value="&accountName.label;"
|
||||
accesskey="&accountName.accesskey;"
|
||||
control="server.prettyName"/>
|
||||
<textbox id="server.prettyName"
|
||||
wsm_persist="true"
|
||||
size="30"
|
||||
prefstring="mail.server.%serverkey%.name"/>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&serverSettings.label;"/>
|
||||
|
||||
<checkbox id="server.loginAtStartUp"
|
||||
wsm_persist="true"
|
||||
label="&loginAtStartup.label;"
|
||||
accesskey="&loginAtStartup.accesskey;"
|
||||
prefattribute="value"
|
||||
prefstring="mail.server.%serverkey%.login_at_startup"/>
|
||||
|
||||
<hbox align="center">
|
||||
<checkbox id="server.doBiff"
|
||||
wsm_persist="true"
|
||||
label="&biffStart.label;"
|
||||
accesskey="&biffStart.accesskey;"
|
||||
oncommand="onCheckItem('server.biffMinutes', [this.id]);"
|
||||
prefattribute="value"
|
||||
prefstring="mail.server.%serverkey%.check_new_mail"/>
|
||||
<textbox id="server.biffMinutes"
|
||||
wsm_persist="true"
|
||||
type="number"
|
||||
size="3"
|
||||
min="1"
|
||||
increment="1"
|
||||
preftype="int"
|
||||
prefstring="mail.server.%serverkey%.check_time"
|
||||
aria-labelledby="server.doBiff server.biffMinutes biffEnd"/>
|
||||
<label id="biffEnd"
|
||||
value="&biffEnd.label;"
|
||||
control="server.biffMinutes"/>
|
||||
</hbox>
|
||||
|
||||
<checkbox id="server.quickMode"
|
||||
wsm_persist="true"
|
||||
genericattr="true"
|
||||
label="&useQuickMode.label;"
|
||||
accesskey="&useQuickMode.accesskey;"
|
||||
preftype="bool"
|
||||
prefattribute="value"
|
||||
prefstring="mail.server.%serverkey%.quickMode"/>
|
||||
|
||||
<checkbox id="autotagEnable"
|
||||
accesskey="&autotagEnable.accesskey;"
|
||||
label="&autotagEnable.label;"
|
||||
oncommand="setCategoryPrefs(this)"/>
|
||||
<hbox>
|
||||
<checkbox id="autotagUsePrefix"
|
||||
class="indent"
|
||||
accesskey="&autotagUsePrefix.accesskey;"
|
||||
label="&autotagUsePrefix.label;"
|
||||
oncommand="setCategoryPrefs(this)"/>
|
||||
<textbox id="autotagPrefix"
|
||||
placeholder="&autoTagPrefix.placeholder;"
|
||||
clickSelectsAll="true"
|
||||
onchange="setCategoryPrefs(this)"/>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<groupbox>
|
||||
<caption label="&messageStorage.label;"/>
|
||||
|
||||
<checkbox id="server.emptyTrashOnExit"
|
||||
wsm_persist="true"
|
||||
label="&emptyTrashOnExit.label;"
|
||||
accesskey="&emptyTrashOnExit.accesskey;"
|
||||
prefattribute="value"
|
||||
prefstring="mail.server.%serverkey%.empty_trash_on_exit"/>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<vbox>
|
||||
<label value="&localPath.label;" control="server.localPath"/>
|
||||
<hbox align="center">
|
||||
<textbox readonly="true"
|
||||
wsm_persist="true"
|
||||
flex="1"
|
||||
id="server.localPath"
|
||||
datatype="nsIFile"
|
||||
prefstring="mail.server.%serverkey%.directory"
|
||||
class="uri-element"/>
|
||||
<button id="browseForLocalFolder"
|
||||
label="&browseFolder.label;"
|
||||
filepickertitle="&localFolderPicker.label;"
|
||||
accesskey="&browseFolder.accesskey;"
|
||||
oncommand="BrowseForLocalFolders();"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
</groupbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<hbox align="center">
|
||||
<spacer flex="1"/>
|
||||
<button label="&manageSubscriptions.label;"
|
||||
accesskey="&manageSubscriptions.accesskey;"
|
||||
oncommand="openSubscriptionsDialog(gServer.rootFolder);"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</page>
|
||||
1034
mailnews/extensions/newsblog/content/feed-parser.js
Normal file
1034
mailnews/extensions/newsblog/content/feed-parser.js
Normal file
File diff suppressed because it is too large
Load diff
2703
mailnews/extensions/newsblog/content/feed-subscriptions.js
Normal file
2703
mailnews/extensions/newsblog/content/feed-subscriptions.js
Normal file
File diff suppressed because it is too large
Load diff
235
mailnews/extensions/newsblog/content/feed-subscriptions.xul
Normal file
235
mailnews/extensions/newsblog/content/feed-subscriptions.xul
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger-newsblog/skin/feed-subscriptions.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % feedDTD SYSTEM "chrome://messenger-newsblog/locale/feed-subscriptions.dtd">
|
||||
%feedDTD;
|
||||
<!ENTITY % certDTD SYSTEM "chrome://pippki/locale/certManager.dtd">
|
||||
%certDTD;
|
||||
]>
|
||||
|
||||
<window id="subscriptionsDialog"
|
||||
flex="1"
|
||||
title="&feedSubscriptions.label;"
|
||||
windowtype="Mail:News-BlogSubscriptions"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:nc="http://home.netscape.com/NC-rdf#"
|
||||
persist="width height screenX screenY sizemode"
|
||||
onload="FeedSubscriptions.onLoad();"
|
||||
onclose="return FeedSubscriptions.onClose();"
|
||||
onkeypress="FeedSubscriptions.onKeyPress(event);"
|
||||
onmousedown="FeedSubscriptions.onMouseDown(event);">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/specialTabs.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger-newsblog/content/feed-subscriptions.js"/>
|
||||
|
||||
<keyset id="extensionsKeys">
|
||||
<key id="key_close"
|
||||
key="&cmd.close.commandKey;"
|
||||
modifiers="accel"
|
||||
oncommand="window.close();"/>
|
||||
<key id="key_close2"
|
||||
keycode="VK_ESCAPE"
|
||||
oncommand="window.close();"/>
|
||||
</keyset>
|
||||
|
||||
<stringbundle id="bundle_newsblog"
|
||||
src="chrome://messenger-newsblog/locale/newsblog.properties"/>
|
||||
<stringbundle id="bundle_brand"
|
||||
src="chrome://branding/locale/brand.properties"/>
|
||||
|
||||
<vbox flex="1" id="contentPane">
|
||||
<hbox align="right">
|
||||
<label id="learnMore"
|
||||
class="text-link"
|
||||
crop="end"
|
||||
value="&learnMore.label;"
|
||||
href="https://support.mozilla.org/kb/how-subscribe-news-feeds-and-blogs"/>
|
||||
</hbox>
|
||||
|
||||
<tree id="rssSubscriptionsList"
|
||||
treelines="true"
|
||||
flex="1"
|
||||
hidecolumnpicker="true"
|
||||
onselect="FeedSubscriptions.onSelect();"
|
||||
seltype="single">
|
||||
<treecols>
|
||||
<treecol id="folderNameCol"
|
||||
flex="2"
|
||||
primary="true"
|
||||
hideheader="true"/>
|
||||
</treecols>
|
||||
<treechildren id="subscriptionChildren"
|
||||
ondragstart="FeedSubscriptions.onDragStart(event);"
|
||||
ondragover="FeedSubscriptions.onDragOver(event);"/>
|
||||
</tree>
|
||||
|
||||
<hbox id="rssFeedInfoBox">
|
||||
<vbox flex="1">
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
<column/>
|
||||
<column flex="1"/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row>
|
||||
<hbox align="right" valign="middle">
|
||||
<label id="nameLabel"
|
||||
accesskey="&feedTitle.accesskey;"
|
||||
control="nameValue"
|
||||
value="&feedTitle.label;"/>
|
||||
</hbox>
|
||||
<textbox id="nameValue"
|
||||
clickSelectsAll="true"/>
|
||||
</row>
|
||||
<row>
|
||||
<hbox align="right" valign="middle">
|
||||
<label id="locationLabel"
|
||||
accesskey="&feedLocation.accesskey;"
|
||||
control="locationValue"
|
||||
value="&feedLocation.label;"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<textbox id="locationValue"
|
||||
flex="1"
|
||||
class="uri-element"
|
||||
placeholder="&feedLocation.placeholder;"
|
||||
clickSelectsAll="true"
|
||||
onfocus="FeedSubscriptions.setSummaryFocus();"
|
||||
onblur="FeedSubscriptions.setSummaryFocus();"/>
|
||||
<hbox align="center">
|
||||
<label id="locationValidate"
|
||||
collapsed="true"
|
||||
class="text-link"
|
||||
crop="end"
|
||||
value="&locationValidate.label;"
|
||||
onclick="FeedSubscriptions.checkValidation(event);"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
<hbox align="right" valign="middle">
|
||||
<label id="feedFolderLabel"
|
||||
value="&feedFolder.label;"
|
||||
accesskey="&feedFolder.accesskey;"
|
||||
control="selectFolder"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<menulist id="selectFolder"
|
||||
flex="1"
|
||||
class="folderMenuItem"
|
||||
hidden="true">
|
||||
<menupopup id="selectFolderPopup"
|
||||
class="menulist-menupopup"
|
||||
type="folder"
|
||||
mode="feeds"
|
||||
showFileHereLabel="true"
|
||||
showAccountsFileHere="true"
|
||||
oncommand="FeedSubscriptions.setNewFolder(event)"/>
|
||||
</menulist>
|
||||
<textbox id="selectFolderValue"
|
||||
flex="1"
|
||||
readonly="true"
|
||||
onkeypress="FeedSubscriptions.onClickSelectFolderValue(event)"
|
||||
onclick="FeedSubscriptions.onClickSelectFolderValue(event)"/>
|
||||
</hbox>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<checkbox id="quickMode"
|
||||
accesskey="&quickMode.accesskey;"
|
||||
label="&quickMode.label;"
|
||||
oncommand="FeedSubscriptions.setSummary(this.checked)"/>
|
||||
<checkbox id="autotagEnable"
|
||||
accesskey="&autotagEnable.accesskey;"
|
||||
label="&autotagEnable.label;"
|
||||
oncommand="FeedSubscriptions.setCategoryPrefs(this)"/>
|
||||
<hbox>
|
||||
<checkbox id="autotagUsePrefix"
|
||||
class="indent"
|
||||
accesskey="&autotagUsePrefix.accesskey;"
|
||||
label="&autotagUsePrefix.label;"
|
||||
oncommand="FeedSubscriptions.setCategoryPrefs(this)"/>
|
||||
<textbox id="autotagPrefix"
|
||||
placeholder="&autoTagPrefix.placeholder;"
|
||||
clickSelectsAll="true"/>
|
||||
</hbox>
|
||||
<separator class="thin"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<hbox id="statusContainerBox"
|
||||
align="center"
|
||||
valign="middle">
|
||||
<vbox flex="1">
|
||||
<description id="statusText"/>
|
||||
</vbox>
|
||||
<spacer flex="1"/>
|
||||
<label id="validationText"
|
||||
collapsed="true"
|
||||
class="text-link"
|
||||
crop="end"
|
||||
value="&validateText.label;"
|
||||
onclick="FeedSubscriptions.checkValidation(event);"/>
|
||||
<button id="addCertException"
|
||||
collapsed="true"
|
||||
label="&certmgr.addException.label;"
|
||||
accesskey="&certmgr.addException.accesskey;"
|
||||
oncommand="FeedSubscriptions.addCertExceptionDialog();"/>
|
||||
<progressmeter id="progressMeter"
|
||||
collapsed="true"
|
||||
mode="determined"
|
||||
value="0"/>
|
||||
</hbox>
|
||||
|
||||
<hbox align="end">
|
||||
<hbox class="actionButtons" flex="1">
|
||||
<button id="addFeed"
|
||||
label="&button.addFeed.label;"
|
||||
accesskey="&button.addFeed.accesskey;"
|
||||
oncommand="FeedSubscriptions.addFeed();"/>
|
||||
|
||||
<button id="editFeed"
|
||||
disabled="true"
|
||||
label="&button.updateFeed.label;"
|
||||
accesskey="&button.updateFeed.accesskey;"
|
||||
oncommand="FeedSubscriptions.editFeed();"/>
|
||||
|
||||
<button id="removeFeed"
|
||||
disabled="true"
|
||||
label="&button.removeFeed.label;"
|
||||
accesskey="&button.removeFeed.accesskey;"
|
||||
oncommand="FeedSubscriptions.removeFeed(true);"/>
|
||||
|
||||
<button id="importOPML"
|
||||
label="&button.importOPML.label;"
|
||||
accesskey="&button.importOPML.accesskey;"
|
||||
oncommand="FeedSubscriptions.importOPML();"/>
|
||||
|
||||
<button id="exportOPML"
|
||||
label="&button.exportOPML.label;"
|
||||
accesskey="&button.exportOPML.accesskey;"
|
||||
tooltiptext="&button.exportOPML.tooltip;"
|
||||
oncommand="FeedSubscriptions.exportOPML(event);"/>
|
||||
|
||||
<spacer flex="1"/>
|
||||
|
||||
<button id="close"
|
||||
label="&button.close.label;"
|
||||
icon="close"
|
||||
oncommand="if (FeedSubscriptions.onClose()) window.close();"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</window>
|
||||
45
mailnews/extensions/newsblog/content/feedAccountWizard.js
Normal file
45
mailnews/extensions/newsblog/content/feedAccountWizard.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource:///modules/FeedUtils.jsm");
|
||||
|
||||
/* Feed account standalone wizard functions */
|
||||
var FeedAccountWizard = {
|
||||
accountName: "",
|
||||
|
||||
accountSetupPageInit: function() {
|
||||
this.accountSetupPageValidate();
|
||||
},
|
||||
|
||||
accountSetupPageValidate: function() {
|
||||
this.accountName = document.getElementById("prettyName").value.trim();
|
||||
document.documentElement.canAdvance = this.accountName;
|
||||
},
|
||||
|
||||
accountSetupPageUnload: function() {
|
||||
return;
|
||||
},
|
||||
|
||||
donePageInit: function() {
|
||||
document.getElementById("account.name.text").value = this.accountName;
|
||||
},
|
||||
|
||||
onCancel: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
onFinish: function() {
|
||||
let account = FeedUtils.createRssAccount(this.accountName);
|
||||
if ("gFolderTreeView" in window.opener.top)
|
||||
// Opened from 3pane File->New or Appmenu New Message, or
|
||||
// Account Central link.
|
||||
window.opener.top.gFolderTreeView.selectFolder(account.incomingServer.rootMsgFolder);
|
||||
else if ("selectServer" in window.opener)
|
||||
// Opened from Account Settings.
|
||||
window.opener.selectServer(account.incomingServer);
|
||||
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
79
mailnews/extensions/newsblog/content/feedAccountWizard.xul
Normal file
79
mailnews/extensions/newsblog/content/feedAccountWizard.xul
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/accountWizard.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE wizard [
|
||||
<!ENTITY % accountDTD SYSTEM "chrome://messenger/locale/AccountWizard.dtd">
|
||||
%accountDTD;
|
||||
<!ENTITY % newsblogDTD SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd" >
|
||||
%newsblogDTD;
|
||||
<!ENTITY % imDTD SYSTEM "chrome://messenger/locale/imAccountWizard.dtd" >
|
||||
%imDTD;
|
||||
]>
|
||||
|
||||
<wizard id="FeedAccountWizard"
|
||||
title="&feedWindowTitle.label;"
|
||||
onwizardcancel="return FeedAccountWizard.onCancel();"
|
||||
onwizardfinish="return FeedAccountWizard.onFinish();"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger-newsblog/content/feedAccountWizard.js"/>
|
||||
|
||||
<!-- Account setup page : User gets a choice to enter a name for the account -->
|
||||
<!-- Defaults : Feed account name -> default string -->
|
||||
<wizardpage id="accountsetuppage"
|
||||
pageid="accountsetuppage"
|
||||
label="&accnameTitle.label;"
|
||||
onpageshow="return FeedAccountWizard.accountSetupPageInit();"
|
||||
onpageadvanced="return FeedAccountWizard.accountSetupPageUnload();">
|
||||
<vbox flex="1">
|
||||
<description>&accnameDesc.label;</description>
|
||||
<separator class="thin"/>
|
||||
<hbox align="center">
|
||||
<label class="label"
|
||||
value="&accnameLabel.label;"
|
||||
accesskey="&accnameLabel.accesskey;"
|
||||
control="prettyName"/>
|
||||
<textbox id="prettyName"
|
||||
flex="1"
|
||||
value="&feeds.accountName;"
|
||||
oninput="FeedAccountWizard.accountSetupPageValidate();"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</wizardpage>
|
||||
|
||||
<!-- Done page : Summarizes information collected to create a feed account -->
|
||||
<wizardpage id="done"
|
||||
pageid="done"
|
||||
label="&accountSummaryTitle.label;"
|
||||
onpageshow="return FeedAccountWizard.donePageInit();">
|
||||
<vbox flex="1">
|
||||
<description>&accountSummaryInfo.label;</description>
|
||||
<separator class="thin"/>
|
||||
<grid>
|
||||
<columns>
|
||||
<column/>
|
||||
<column flex="1"/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row id="account.name"
|
||||
align="center">
|
||||
<label id="account.name.label"
|
||||
class="label"
|
||||
flex="1"
|
||||
value="&accnameLabel.label;"/>
|
||||
<label id="account.name.text"
|
||||
class="label"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<separator/>
|
||||
<spacer flex="1"/>
|
||||
</vbox>
|
||||
</wizardpage>
|
||||
|
||||
</wizard>
|
||||
363
mailnews/extensions/newsblog/content/newsblogOverlay.js
Normal file
363
mailnews/extensions/newsblog/content/newsblogOverlay.js
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource:///modules/gloda/mimemsg.js");
|
||||
Components.utils.import("resource:///modules/mailServices.js");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// This global is for SeaMonkey compatibility.
|
||||
var gShowFeedSummary;
|
||||
|
||||
var FeedMessageHandler = {
|
||||
gShowSummary: true,
|
||||
gToggle: false,
|
||||
kSelectOverrideWebPage: 0,
|
||||
kSelectOverrideSummary: 1,
|
||||
kSelectFeedDefault: 2,
|
||||
kOpenWebPage: 0,
|
||||
kOpenSummary: 1,
|
||||
kOpenToggleInMessagePane: 2,
|
||||
kOpenLoadInBrowser: 3,
|
||||
|
||||
/**
|
||||
* How to load message on threadpane select.
|
||||
*/
|
||||
get onSelectPref() {
|
||||
return Services.prefs.getIntPref("rss.show.summary");
|
||||
},
|
||||
|
||||
set onSelectPref(val) {
|
||||
Services.prefs.setIntPref("rss.show.summary", val);
|
||||
ReloadMessage();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load web page on threadpane select.
|
||||
*/
|
||||
get loadWebPageOnSelectPref() {
|
||||
return Services.prefs.getIntPref("rss.message.loadWebPageOnSelect") ? true : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* How to load message on open (enter/dbl click in threadpane, contextmenu).
|
||||
*/
|
||||
get onOpenPref() {
|
||||
return Services.prefs.getIntPref("rss.show.content-base");
|
||||
},
|
||||
|
||||
set onOpenPref(val) {
|
||||
Services.prefs.setIntPref("rss.show.content-base", val);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if a message is a feed message. Prior to Tb15, a message had to
|
||||
* be in an rss acount type folder. In Tb15 and later, a flag is set on the
|
||||
* message itself upon initial store; the message can be moved to any folder.
|
||||
*
|
||||
* @param nsIMsgDBHdr aMsgHdr - the message.
|
||||
*
|
||||
* @return true if message is a feed, false if not.
|
||||
*/
|
||||
isFeedMessage: function(aMsgHdr) {
|
||||
return (aMsgHdr instanceof Components.interfaces.nsIMsgDBHdr) &&
|
||||
((aMsgHdr.flags & Components.interfaces.nsMsgMessageFlags.FeedMsg) ||
|
||||
(aMsgHdr.folder && aMsgHdr.folder.server.type == "rss"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether to show a feed message summary or load a web page in the
|
||||
* message pane.
|
||||
*
|
||||
* @param nsIMsgDBHdr aMsgHdr - the message.
|
||||
* @param bool aToggle - true if in toggle mode, false otherwise.
|
||||
*
|
||||
* @return true if summary is to be displayed, false if web page.
|
||||
*/
|
||||
shouldShowSummary: function(aMsgHdr, aToggle) {
|
||||
// Not a feed message, always show summary (the message).
|
||||
if (!this.isFeedMessage(aMsgHdr))
|
||||
return true;
|
||||
|
||||
// Notified of a summary reload when toggling, reset toggle and return.
|
||||
if (!aToggle && this.gToggle)
|
||||
return !(this.gToggle = false);
|
||||
|
||||
let showSummary = true;
|
||||
this.gToggle = aToggle;
|
||||
|
||||
// Thunderbird 2 rss messages with 'Show article summary' not selected,
|
||||
// ie message body constructed to show web page in an iframe, can't show
|
||||
// a summary - notify user.
|
||||
let browser = getBrowser();
|
||||
let contentDoc = browser ? browser.contentDocument : null;
|
||||
let rssIframe = contentDoc ? contentDoc.getElementById("_mailrssiframe") : null;
|
||||
if (rssIframe) {
|
||||
if (this.gToggle || this.onSelectPref == this.kSelectOverrideSummary)
|
||||
this.gToggle = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aToggle)
|
||||
// Toggle mode, flip value.
|
||||
return gShowFeedSummary = this.gShowSummary = !this.gShowSummary;
|
||||
|
||||
let wintype = document.documentElement.getAttribute("windowtype");
|
||||
let tabMail = document.getElementById("tabmail");
|
||||
let messageTab = tabMail && tabMail.currentTabInfo.mode.type == "message";
|
||||
let messageWindow = wintype == "mail:messageWindow";
|
||||
|
||||
switch (this.onSelectPref) {
|
||||
case this.kSelectOverrideWebPage:
|
||||
showSummary = false;
|
||||
break;
|
||||
case this.kSelectOverrideSummary:
|
||||
showSummary = true
|
||||
break;
|
||||
case this.kSelectFeedDefault:
|
||||
// Get quickmode per feed folder pref from feeds.rdf. If the feed
|
||||
// message is not in a feed account folder (hence the folder is not in
|
||||
// the feeds database), or FZ_QUICKMODE property is not found (possible
|
||||
// in pre renovation urls), err on the side of showing the summary.
|
||||
// For the former, toggle or global override is necessary; for the
|
||||
// latter, a show summary checkbox toggle in Subscribe dialog will set
|
||||
// one on the path to bliss.
|
||||
let folder = aMsgHdr.folder, targetRes;
|
||||
try {
|
||||
targetRes = FeedUtils.getParentTargetForChildResource(
|
||||
folder.URI, FeedUtils.FZ_QUICKMODE, folder.server);
|
||||
}
|
||||
catch (ex) {
|
||||
// Not in a feed account folder or other error.
|
||||
FeedUtils.log.info("FeedMessageHandler.shouldShowSummary: could not " +
|
||||
"get summary pref for this folder");
|
||||
}
|
||||
|
||||
showSummary = targetRes && targetRes.QueryInterface(Ci.nsIRDFLiteral).
|
||||
Value == "false" ? false : true;
|
||||
break;
|
||||
}
|
||||
|
||||
gShowFeedSummary = this.gShowSummary = showSummary;
|
||||
|
||||
if (messageWindow || messageTab) {
|
||||
// Message opened in either standalone window or tab, due to either
|
||||
// message open pref (we are here only if the pref is 0 or 1) or
|
||||
// contextmenu open.
|
||||
switch (this.onOpenPref) {
|
||||
case this.kOpenToggleInMessagePane:
|
||||
// Opened by contextmenu, use the value derived above.
|
||||
// XXX: allow a toggle via crtl?
|
||||
break;
|
||||
case this.kOpenWebPage:
|
||||
showSummary = false;
|
||||
break;
|
||||
case this.kOpenSummary:
|
||||
showSummary = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto load web page in browser on select, per pref; shouldShowSummary() is
|
||||
// always called first to 1)test if feed, 2)get summary pref, so do it here.
|
||||
if (this.loadWebPageOnSelectPref)
|
||||
setTimeout(FeedMessageHandler.loadWebPage, 20, aMsgHdr, {browser:true});
|
||||
|
||||
return showSummary;
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a web page for feed messages. Use MsgHdrToMimeMessage() to get
|
||||
* the content-base url from the message headers. We cannot rely on
|
||||
* currentHeaderData; it has not yet been streamed at our entry point in
|
||||
* displayMessageChanged(), and in the case of a collapsed message pane it
|
||||
* is not streamed.
|
||||
*
|
||||
* @param nsIMsgDBHdr aMessageHdr - the message.
|
||||
* @param {obj} aWhere - name value=true pair, where name is in:
|
||||
* 'messagepane', 'browser', 'tab', 'window'.
|
||||
*/
|
||||
loadWebPage: function(aMessageHdr, aWhere) {
|
||||
MsgHdrToMimeMessage(aMessageHdr, null, function(aMsgHdr, aMimeMsg) {
|
||||
if (aMimeMsg && aMimeMsg.headers["content-base"] &&
|
||||
aMimeMsg.headers["content-base"][0]) {
|
||||
let url = aMimeMsg.headers["content-base"], uri;
|
||||
try {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
url = converter.ConvertToUnicode(url);
|
||||
uri = Services.io.newURI(url, null, null);
|
||||
url = uri.spec;
|
||||
}
|
||||
catch (ex) {
|
||||
FeedUtils.log.info("FeedMessageHandler.loadWebPage: " +
|
||||
"invalid Content-Base header url - " + url);
|
||||
return;
|
||||
}
|
||||
if (aWhere.browser)
|
||||
Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
|
||||
.getService(Components.interfaces.nsIExternalProtocolService)
|
||||
.loadURI(uri);
|
||||
else if (aWhere.messagepane) {
|
||||
let loadFlag = getBrowser().webNavigation.LOAD_FLAGS_NONE;
|
||||
getBrowser().webNavigation.loadURI(url, loadFlag, null, null, null);
|
||||
}
|
||||
else if (aWhere.tab)
|
||||
openContentTab(url, "tab", "^");
|
||||
else if (aWhere.window)
|
||||
openContentTab(url, "window", "^");
|
||||
}
|
||||
else
|
||||
FeedUtils.log.info("FeedMessageHandler.loadWebPage: could not get " +
|
||||
"Content-Base header url for this message");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Display summary or load web page for feed messages. Caller should already
|
||||
* know if the message is a feed message.
|
||||
*
|
||||
* @param nsIMsgDBHdr aMsgHdr - the message.
|
||||
* @param bool aShowSummary - true if summary is to be displayed, false if
|
||||
* web page.
|
||||
*/
|
||||
setContent: function(aMsgHdr, aShowSummary) {
|
||||
if (aShowSummary) {
|
||||
// Only here if toggling to summary in 3pane.
|
||||
if (this.gToggle && gDBView && GetNumSelectedMessages() == 1)
|
||||
ReloadMessage();
|
||||
}
|
||||
else {
|
||||
let browser = getBrowser();
|
||||
if (browser && browser.contentDocument && browser.contentDocument.body)
|
||||
browser.contentDocument.body.hidden = true;
|
||||
// If in a non rss folder, hide possible remote content bar on a web
|
||||
// page load, as it doesn't apply.
|
||||
if ("msgNotificationBar" in window)
|
||||
gMessageNotificationBar.clearMsgNotifications();
|
||||
|
||||
this.loadWebPage(aMsgHdr, {messagepane:true});
|
||||
this.gToggle = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openSubscriptionsDialog(aFolder)
|
||||
{
|
||||
// Check for an existing feed subscriptions window and focus it.
|
||||
let subscriptionsWindow =
|
||||
Services.wm.getMostRecentWindow("Mail:News-BlogSubscriptions");
|
||||
|
||||
if (subscriptionsWindow)
|
||||
{
|
||||
if (aFolder)
|
||||
{
|
||||
subscriptionsWindow.FeedSubscriptions.selectFolder(aFolder);
|
||||
subscriptionsWindow.FeedSubscriptions.mView.treeBox.ensureRowIsVisible(
|
||||
subscriptionsWindow.FeedSubscriptions.mView.selection.currentIndex);
|
||||
}
|
||||
|
||||
subscriptionsWindow.focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
window.openDialog("chrome://messenger-newsblog/content/feed-subscriptions.xul",
|
||||
"", "centerscreen,chrome,dialog=no,resizable",
|
||||
{ folder: aFolder});
|
||||
}
|
||||
}
|
||||
|
||||
// Special case attempts to reply/forward/edit as new RSS articles. For
|
||||
// messages stored prior to Tb15, we are here only if the message's folder's
|
||||
// account server is rss and feed messages moved to other types will have their
|
||||
// summaries loaded, as viewing web pages only happened in an rss account.
|
||||
// The user may choose whether to load a summary or web page link by ensuring
|
||||
// the current feed message is being viewed as either a summary or web page.
|
||||
function openComposeWindowForRSSArticle(aMsgComposeWindow, aMsgHdr, aMessageUri,
|
||||
aType, aFormat, aIdentity, aMsgWindow)
|
||||
{
|
||||
// Ensure right content is handled for web pages in window/tab.
|
||||
let tabmail = document.getElementById("tabmail");
|
||||
let is3pane = tabmail && tabmail.selectedTab && tabmail.selectedTab.mode ?
|
||||
tabmail.selectedTab.mode.type == "folder" : false;
|
||||
let showingwebpage = ("FeedMessageHandler" in window) && !is3pane &&
|
||||
FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenWebPage;
|
||||
|
||||
if (gShowFeedSummary && !showingwebpage)
|
||||
{
|
||||
// The user is viewing the summary.
|
||||
MailServices.compose.OpenComposeWindow(aMsgComposeWindow, aMsgHdr, aMessageUri,
|
||||
aType, aFormat, aIdentity, aMsgWindow);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set up the compose message and get the feed message's web page link.
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let msgHdr = aMsgHdr;
|
||||
let type = aType;
|
||||
let msgComposeType = Ci.nsIMsgCompType;
|
||||
let subject = msgHdr.mime2DecodedSubject;
|
||||
let fwdPrefix = Services.prefs.getCharPref("mail.forward_subject_prefix");
|
||||
fwdPrefix = fwdPrefix ? fwdPrefix + ": " : "";
|
||||
|
||||
let params = Cc["@mozilla.org/messengercompose/composeparams;1"]
|
||||
.createInstance(Ci.nsIMsgComposeParams);
|
||||
|
||||
let composeFields = Cc["@mozilla.org/messengercompose/composefields;1"]
|
||||
.createInstance(Ci.nsIMsgCompFields);
|
||||
|
||||
if (type == msgComposeType.Reply ||
|
||||
type == msgComposeType.ReplyAll ||
|
||||
type == msgComposeType.ReplyToSender ||
|
||||
type == msgComposeType.ReplyToGroup ||
|
||||
type == msgComposeType.ReplyToSenderAndGroup)
|
||||
{
|
||||
subject = "Re: " + subject;
|
||||
}
|
||||
else if (type == msgComposeType.ForwardInline ||
|
||||
type == msgComposeType.ForwardAsAttachment)
|
||||
{
|
||||
subject = fwdPrefix + subject;
|
||||
}
|
||||
|
||||
params.composeFields = composeFields;
|
||||
params.composeFields.subject = subject;
|
||||
params.composeFields.characterSet = msgHdr.Charset;
|
||||
params.composeFields.body = "";
|
||||
params.bodyIsLink = false;
|
||||
params.identity = aIdentity;
|
||||
|
||||
try
|
||||
{
|
||||
// The feed's web page url is stored in the Content-Base header.
|
||||
MsgHdrToMimeMessage(msgHdr, null, function(aMsgHdr, aMimeMsg) {
|
||||
if (aMimeMsg && aMimeMsg.headers["content-base"] &&
|
||||
aMimeMsg.headers["content-base"][0])
|
||||
{
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let url = converter.ConvertToUnicode(aMimeMsg.headers["content-base"]);
|
||||
params.composeFields.body = url;
|
||||
params.bodyIsLink = true;
|
||||
MailServices.compose.OpenComposeWindowWithParams(null, params);
|
||||
}
|
||||
else
|
||||
// No content-base url, use the summary.
|
||||
MailServices.compose.OpenComposeWindow(aMsgComposeWindow, aMsgHdr, aMessageUri,
|
||||
aType, aFormat, aIdentity, aMsgWindow);
|
||||
|
||||
}, false, {saneBodySize: true});
|
||||
}
|
||||
catch (ex)
|
||||
{
|
||||
// Error getting header, use the summary.
|
||||
MailServices.compose.OpenComposeWindow(aMsgComposeWindow, aMsgHdr, aMessageUri,
|
||||
aType, aFormat, aIdentity, aMsgWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
mailnews/extensions/newsblog/jar.mn
Normal file
16
mailnews/extensions/newsblog/jar.mn
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
newsblog.jar:
|
||||
% content messenger-newsblog %content/messenger-newsblog/
|
||||
content/messenger-newsblog/newsblogOverlay.js (content/newsblogOverlay.js)
|
||||
content/messenger-newsblog/Feed.js (content/Feed.js)
|
||||
content/messenger-newsblog/FeedItem.js (content/FeedItem.js)
|
||||
content/messenger-newsblog/feed-parser.js (content/feed-parser.js)
|
||||
* content/messenger-newsblog/feed-subscriptions.js (content/feed-subscriptions.js)
|
||||
content/messenger-newsblog/feed-subscriptions.xul (content/feed-subscriptions.xul)
|
||||
content/messenger-newsblog/am-newsblog.js (content/am-newsblog.js)
|
||||
content/messenger-newsblog/am-newsblog.xul (content/am-newsblog.xul)
|
||||
content/messenger-newsblog/feedAccountWizard.js (content/feedAccountWizard.js)
|
||||
content/messenger-newsblog/feedAccountWizard.xul (content/feedAccountWizard.xul)
|
||||
99
mailnews/extensions/newsblog/js/newsblog.js
Normal file
99
mailnews/extensions/newsblog/js/newsblog.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource:///modules/FeedUtils.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var nsNewsBlogFeedDownloader =
|
||||
{
|
||||
downloadFeed: function(aFolder, aUrlListener, aIsBiff, aMsgWindow)
|
||||
{
|
||||
FeedUtils.downloadFeed(aFolder, aUrlListener, aIsBiff, aMsgWindow);
|
||||
},
|
||||
|
||||
subscribeToFeed: function(aUrl, aFolder, aMsgWindow)
|
||||
{
|
||||
FeedUtils.subscribeToFeed(aUrl, aFolder, aMsgWindow);
|
||||
},
|
||||
|
||||
updateSubscriptionsDS: function(aFolder, aOrigFolder, aAction)
|
||||
{
|
||||
FeedUtils.updateSubscriptionsDS(aFolder, aOrigFolder, aAction);
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID)
|
||||
{
|
||||
if (aIID.equals(Ci.nsINewsBlogFeedDownloader) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
return this;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
var nsNewsBlogAcctMgrExtension =
|
||||
{
|
||||
name: "newsblog",
|
||||
chromePackageName: "messenger-newsblog",
|
||||
showPanel: function (server)
|
||||
{
|
||||
return false;
|
||||
},
|
||||
QueryInterface: function(aIID)
|
||||
{
|
||||
if (aIID.equals(Ci.nsIMsgAccountManagerExtension) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
return this;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
function FeedDownloader() {}
|
||||
|
||||
FeedDownloader.prototype =
|
||||
{
|
||||
classID: Components.ID("{5c124537-adca-4456-b2b5-641ab687d1f6}"),
|
||||
_xpcom_factory:
|
||||
{
|
||||
createInstance: function (aOuter, aIID)
|
||||
{
|
||||
if (aOuter != null)
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
if (!aIID.equals(Ci.nsINewsBlogFeedDownloader) &&
|
||||
!aIID.equals(Ci.nsISupports))
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
|
||||
// return the singleton
|
||||
return nsNewsBlogFeedDownloader.QueryInterface(aIID);
|
||||
}
|
||||
} // factory
|
||||
}; // feed downloader
|
||||
|
||||
function AcctMgrExtension() {}
|
||||
|
||||
AcctMgrExtension.prototype =
|
||||
{
|
||||
classID: Components.ID("{E109C05F-D304-4ca5-8C44-6DE1BFAF1F74}"),
|
||||
_xpcom_factory:
|
||||
{
|
||||
createInstance: function (aOuter, aIID)
|
||||
{
|
||||
if (aOuter != null)
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
if (!aIID.equals(Ci.nsIMsgAccountManagerExtension) &&
|
||||
!aIID.equals(Ci.nsISupports))
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
|
||||
// return the singleton
|
||||
return nsNewsBlogAcctMgrExtension.QueryInterface(aIID);
|
||||
}
|
||||
} // factory
|
||||
}; // account manager extension
|
||||
|
||||
var components = [FeedDownloader, AcctMgrExtension];
|
||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|
||||
5
mailnews/extensions/newsblog/js/newsblog.manifest
Normal file
5
mailnews/extensions/newsblog/js/newsblog.manifest
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
component {5c124537-adca-4456-b2b5-641ab687d1f6} newsblog.js
|
||||
contract @mozilla.org/newsblog-feed-downloader;1 {5c124537-adca-4456-b2b5-641ab687d1f6}
|
||||
component {E109C05F-D304-4ca5-8C44-6DE1BFAF1F74} newsblog.js
|
||||
contract @mozilla.org/accountmanager/extension;1?name=newsblog {E109C05F-D304-4ca5-8C44-6DE1BFAF1F74}
|
||||
category mailnews-accountmanager-extensions newsblog @mozilla.org/accountmanager/extension;1?name=newsblog
|
||||
18
mailnews/extensions/newsblog/moz.build
Normal file
18
mailnews/extensions/newsblog/moz.build
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'js/newsblog.js',
|
||||
'js/newsblog.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'content/FeedUtils.jsm',
|
||||
]
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
FINAL_TARGET_FILES.isp += [
|
||||
'rss.rdf',
|
||||
]
|
||||
43
mailnews/extensions/newsblog/rss.rdf
Normal file
43
mailnews/extensions/newsblog/rss.rdf
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!DOCTYPE RDF SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd">
|
||||
<RDF:RDF
|
||||
xmlns:NC="http://home.netscape.com/NC-rdf#"
|
||||
xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<RDF:Description about="NC:ispinfo">
|
||||
<NC:providers>
|
||||
<NC:nsIMsgAccount about="newsblog">
|
||||
|
||||
<!-- server info -->
|
||||
<NC:incomingServer>
|
||||
<NC:nsIMsgIncomingServer>
|
||||
<NC:hostName>Feeds</NC:hostName>
|
||||
<NC:type>rss</NC:type>
|
||||
<NC:biffMinutes>100</NC:biffMinutes>
|
||||
<NC:username>nobody</NC:username>
|
||||
</NC:nsIMsgIncomingServer>
|
||||
</NC:incomingServer>
|
||||
|
||||
<!-- identity defaults -->
|
||||
<NC:identity>
|
||||
<NC:nsIMsgIdentity>
|
||||
</NC:nsIMsgIdentity>
|
||||
</NC:identity>
|
||||
|
||||
<NC:wizardAutoGenerateUniqueHostname>true</NC:wizardAutoGenerateUniqueHostname>
|
||||
<NC:wizardHideIncoming>true</NC:wizardHideIncoming>
|
||||
<NC:wizardAccountName>&feeds.accountName;</NC:wizardAccountName>
|
||||
<NC:wizardSkipPanels>identitypage,incomingpage,outgoingpage</NC:wizardSkipPanels>
|
||||
<NC:wizardShortName>&feeds.wizardShortName;</NC:wizardShortName>
|
||||
<NC:wizardLongName>&feeds.wizardLongName;</NC:wizardLongName>
|
||||
<NC:wizardLongNameAccesskey>&feeds.wizardLongName.accesskey;</NC:wizardLongNameAccesskey>
|
||||
<NC:wizardShow>true</NC:wizardShow>
|
||||
<NC:emailProviderName>RSS</NC:emailProviderName>
|
||||
<NC:showServerDetailsOnWizardSummary>false</NC:showServerDetailsOnWizardSummary>
|
||||
</NC:nsIMsgAccount>
|
||||
</NC:providers>
|
||||
</RDF:Description>
|
||||
</RDF:RDF>
|
||||
170
mailnews/extensions/offline-startup/js/offlineStartup.js
Normal file
170
mailnews/extensions/offline-startup/js/offlineStartup.js
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var kDebug = false;
|
||||
var kOfflineStartupPref = "offline.startup_state";
|
||||
var kRememberLastState = 0;
|
||||
var kAskForOnlineState = 1;
|
||||
var kAlwaysOnline = 2;
|
||||
var kAlwaysOffline = 3;
|
||||
var kAutomatic = 4;
|
||||
var gStartingUp = true;
|
||||
var gOfflineStartupMode; //0 = remember last state, 1 = ask me, 2 == online, 3 == offline, 4 = automatic
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// nsOfflineStartup : nsIObserver
|
||||
//
|
||||
// Check if the user has set the pref to be prompted for
|
||||
// online/offline startup mode. If so, prompt the user. Also,
|
||||
// check if the user wants to remember their offline state
|
||||
// the next time they start up.
|
||||
// If the user shutdown offline, and is now starting up in online
|
||||
// mode, we will set the boolean pref "mailnews.playback_offline" to true.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var nsOfflineStartup =
|
||||
{
|
||||
onProfileStartup: function()
|
||||
{
|
||||
debug("onProfileStartup");
|
||||
|
||||
if (gStartingUp)
|
||||
{
|
||||
gStartingUp = false;
|
||||
// if checked, the "work offline" checkbox overrides
|
||||
if (Services.io.offline && !Services.io.manageOfflineStatus)
|
||||
{
|
||||
debug("already offline!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var manageOfflineStatus = Services.prefs.getBoolPref("offline.autoDetect");
|
||||
gOfflineStartupMode = Services.prefs.getIntPref(kOfflineStartupPref);
|
||||
let wasOffline = !Services.prefs.getBoolPref("network.online");
|
||||
|
||||
if (gOfflineStartupMode == kAutomatic)
|
||||
{
|
||||
// Offline state should be managed automatically
|
||||
// so do nothing specific at startup.
|
||||
}
|
||||
else if (gOfflineStartupMode == kAlwaysOffline)
|
||||
{
|
||||
Services.io.manageOfflineStatus = false;
|
||||
Services.io.offline = true;
|
||||
}
|
||||
else if (gOfflineStartupMode == kAlwaysOnline)
|
||||
{
|
||||
Services.io.manageOfflineStatus = manageOfflineStatus;
|
||||
if (wasOffline)
|
||||
Services.prefs.setBoolPref("mailnews.playback_offline", true);
|
||||
// If we're managing the offline status, don't force online here... it may
|
||||
// be the network really is offline.
|
||||
if (!manageOfflineStatus)
|
||||
Services.io.offline = false;
|
||||
}
|
||||
else if (gOfflineStartupMode == kRememberLastState)
|
||||
{
|
||||
Services.io.manageOfflineStatus = manageOfflineStatus && !wasOffline;
|
||||
// If we are meant to be online, and managing the offline status
|
||||
// then don't force it - it may be the network really is offline.
|
||||
if (!manageOfflineStatus || wasOffline)
|
||||
Services.io.offline = wasOffline;
|
||||
}
|
||||
else if (gOfflineStartupMode == kAskForOnlineState)
|
||||
{
|
||||
var bundle = Services.strings.createBundle("chrome://messenger/locale/offlineStartup.properties");
|
||||
var title = bundle.GetStringFromName("title");
|
||||
var desc = bundle.GetStringFromName("desc");
|
||||
var button0Text = bundle.GetStringFromName("workOnline");
|
||||
var button1Text = bundle.GetStringFromName("workOffline");
|
||||
var checkVal = {value:0};
|
||||
|
||||
var result = Services.prompt.confirmEx(null, title, desc,
|
||||
(Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING) +
|
||||
(Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING),
|
||||
button0Text, button1Text, null, null, checkVal);
|
||||
debug ("result = " + result + "\n");
|
||||
Services.io.manageOfflineStatus = manageOfflineStatus && result != 1;
|
||||
Services.io.offline = result == 1;
|
||||
if (result != 1 && wasOffline)
|
||||
Services.prefs.setBoolPref("mailnews.playback_offline", true);
|
||||
}
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData)
|
||||
{
|
||||
debug("observe: " + aTopic);
|
||||
|
||||
if (aTopic == "profile-change-net-teardown")
|
||||
{
|
||||
debug("remembering offline state");
|
||||
Services.prefs.setBoolPref("network.online", !Services.io.offline);
|
||||
}
|
||||
else if (aTopic == "app-startup")
|
||||
{
|
||||
Services.obs.addObserver(this, "profile-after-change", false);
|
||||
Services.obs.addObserver(this, "profile-change-net-teardown", false);
|
||||
}
|
||||
else if (aTopic == "profile-after-change")
|
||||
{
|
||||
this.onProfileStartup();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
QueryInterface: function(aIID)
|
||||
{
|
||||
if (aIID.equals(Components.interfaces.nsIObserver) ||
|
||||
aIID.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
function nsOfflineStartupModule()
|
||||
{
|
||||
}
|
||||
|
||||
nsOfflineStartupModule.prototype =
|
||||
{
|
||||
classID: Components.ID("3028a3c8-2165-42a4-b878-398da5d32736"),
|
||||
_xpcom_factory:
|
||||
{
|
||||
createInstance: function(aOuter, aIID)
|
||||
{
|
||||
if (aOuter != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
|
||||
// return the singleton
|
||||
return nsOfflineStartup.QueryInterface(aIID);
|
||||
},
|
||||
|
||||
lockFactory: function(aLock)
|
||||
{
|
||||
// quieten warnings
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Debug helper
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
if (!kDebug)
|
||||
debug = function(m) {};
|
||||
else
|
||||
debug = function(m) {dump("\t *** nsOfflineStartup: " + m + "\n");};
|
||||
|
||||
var components = [nsOfflineStartupModule];
|
||||
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
component {3028a3c8-2165-42a4-b878-398da5d32736} offlineStartup.js
|
||||
contract @mozilla.org/offline-startup;1 {3028a3c8-2165-42a4-b878-398da5d32736}
|
||||
category app-startup Offline-startup @mozilla.org/offline-startup;1
|
||||
10
mailnews/extensions/offline-startup/moz.build
Normal file
10
mailnews/extensions/offline-startup/moz.build
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'js/offlineStartup.js',
|
||||
'js/offlineStartup.manifest',
|
||||
]
|
||||
|
||||
478
mailnews/extensions/smime/content/am-smime.js
Normal file
478
mailnews/extensions/smime/content/am-smime.js
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var nsIX509CertDB = Components.interfaces.nsIX509CertDB;
|
||||
var nsX509CertDBContractID = "@mozilla.org/security/x509certdb;1";
|
||||
var nsIX509Cert = Components.interfaces.nsIX509Cert;
|
||||
|
||||
var email_recipient_cert_usage = 5;
|
||||
var email_signing_cert_usage = 4;
|
||||
|
||||
var gIdentity;
|
||||
var gPref = null;
|
||||
var gEncryptionCertName = null;
|
||||
var gHiddenEncryptionPolicy = null;
|
||||
var gEncryptionChoices = null;
|
||||
var gSignCertName = null;
|
||||
var gSignMessages = null;
|
||||
var gEncryptAlways = null;
|
||||
var gNeverEncrypt = null;
|
||||
var gBundle = null;
|
||||
var gBrandBundle;
|
||||
var gSmimePrefbranch;
|
||||
var gEncryptionChoicesLocked;
|
||||
var gSigningChoicesLocked;
|
||||
var kEncryptionCertPref = "identity.encryption_cert_name";
|
||||
var kSigningCertPref = "identity.signing_cert_name";
|
||||
|
||||
function onInit()
|
||||
{
|
||||
smimeInitializeFields();
|
||||
}
|
||||
|
||||
function smimeInitializeFields()
|
||||
{
|
||||
// initialize all of our elements based on the current identity values....
|
||||
gEncryptionCertName = document.getElementById(kEncryptionCertPref);
|
||||
gHiddenEncryptionPolicy = document.getElementById("identity.encryptionpolicy");
|
||||
gEncryptionChoices = document.getElementById("encryptionChoices");
|
||||
gSignCertName = document.getElementById(kSigningCertPref);
|
||||
gSignMessages = document.getElementById("identity.sign_mail");
|
||||
gEncryptAlways = document.getElementById("encrypt_mail_always");
|
||||
gNeverEncrypt = document.getElementById("encrypt_mail_never");
|
||||
gBundle = document.getElementById("bundle_smime");
|
||||
gBrandBundle = document.getElementById("bundle_brand");
|
||||
|
||||
gEncryptionChoicesLocked = false;
|
||||
gSigningChoicesLocked = false;
|
||||
|
||||
if (!gIdentity) {
|
||||
// The user is going to create a new identity.
|
||||
// Set everything to default values.
|
||||
// Do not take over the values from gAccount.defaultIdentity
|
||||
// as the new identity is going to have a different mail address.
|
||||
|
||||
gEncryptionCertName.value = "";
|
||||
gEncryptionCertName.nickname = "";
|
||||
gEncryptionCertName.dbKey = "";
|
||||
gSignCertName.value = "";
|
||||
gSignCertName.nickname = "";
|
||||
gSignCertName.dbKey = "";
|
||||
|
||||
gEncryptAlways.setAttribute("disabled", true);
|
||||
gNeverEncrypt.setAttribute("disabled", true);
|
||||
gSignMessages.setAttribute("disabled", true);
|
||||
|
||||
gSignMessages.checked = false;
|
||||
gEncryptionChoices.value = 0;
|
||||
}
|
||||
else {
|
||||
var certdb = Components.classes[nsX509CertDBContractID].getService(nsIX509CertDB);
|
||||
var x509cert = null;
|
||||
|
||||
gEncryptionCertName.value = gIdentity.getUnicharAttribute("encryption_cert_name");
|
||||
gEncryptionCertName.dbKey = gIdentity.getCharAttribute("encryption_cert_dbkey");
|
||||
// If we succeed in looking up the certificate by the dbkey pref, then
|
||||
// append the serial number " [...]" to the display value, and remember the
|
||||
// nickname in a separate property.
|
||||
try {
|
||||
if (certdb && gEncryptionCertName.dbKey &&
|
||||
(x509cert = certdb.findCertByDBKey(gEncryptionCertName.dbKey))) {
|
||||
gEncryptionCertName.value = x509cert.nickname + " [" + x509cert.serialNumber + "]";
|
||||
gEncryptionCertName.nickname = x509cert.nickname;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
gEncryptionChoices.value = gIdentity.getIntAttribute("encryptionpolicy");
|
||||
|
||||
if (!gEncryptionCertName.value) {
|
||||
gEncryptAlways.setAttribute("disabled", true);
|
||||
gNeverEncrypt.setAttribute("disabled", true);
|
||||
}
|
||||
else {
|
||||
enableEncryptionControls(true);
|
||||
}
|
||||
|
||||
gSignCertName.value = gIdentity.getUnicharAttribute("signing_cert_name");
|
||||
gSignCertName.dbKey = gIdentity.getCharAttribute("signing_cert_dbkey");
|
||||
x509cert = null;
|
||||
// same procedure as with gEncryptionCertName (see above)
|
||||
try {
|
||||
if (certdb && gSignCertName.dbKey &&
|
||||
(x509cert = certdb.findCertByDBKey(gSignCertName.dbKey))) {
|
||||
gSignCertName.value = x509cert.nickname + " [" + x509cert.serialNumber + "]";
|
||||
gSignCertName.nickname = x509cert.nickname;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
gSignMessages.checked = gIdentity.getBoolAttribute("sign_mail");
|
||||
if (!gSignCertName.value)
|
||||
{
|
||||
gSignMessages.setAttribute("disabled", true);
|
||||
}
|
||||
else {
|
||||
enableSigningControls(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Always start with enabling signing and encryption cert select buttons.
|
||||
// This will keep the visibility of buttons in a sane state as user
|
||||
// jumps from security panel of one account to another.
|
||||
enableCertSelectButtons();
|
||||
|
||||
// Disable all locked elements on the panel
|
||||
if (gIdentity)
|
||||
onLockPreference();
|
||||
}
|
||||
|
||||
function onPreInit(account, accountValues)
|
||||
{
|
||||
gIdentity = account.defaultIdentity;
|
||||
}
|
||||
|
||||
function onSave()
|
||||
{
|
||||
smimeSave();
|
||||
}
|
||||
|
||||
function smimeSave()
|
||||
{
|
||||
// find out which radio for the encryption radio group is selected and set that on our hidden encryptionChoice pref....
|
||||
var newValue = gEncryptionChoices.value;
|
||||
gHiddenEncryptionPolicy.setAttribute('value', newValue);
|
||||
gIdentity.setIntAttribute("encryptionpolicy", newValue);
|
||||
gIdentity.setUnicharAttribute("encryption_cert_name",
|
||||
gEncryptionCertName.nickname || gEncryptionCertName.value);
|
||||
gIdentity.setCharAttribute("encryption_cert_dbkey", gEncryptionCertName.dbKey);
|
||||
|
||||
gIdentity.setBoolAttribute("sign_mail", gSignMessages.checked);
|
||||
gIdentity.setUnicharAttribute("signing_cert_name",
|
||||
gSignCertName.nickname || gSignCertName.value);
|
||||
gIdentity.setCharAttribute("signing_cert_dbkey", gSignCertName.dbKey);
|
||||
}
|
||||
|
||||
function smimeOnAcceptEditor()
|
||||
{
|
||||
try {
|
||||
if (!onOk())
|
||||
return false;
|
||||
}
|
||||
catch (ex) {}
|
||||
|
||||
smimeSave();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function onLockPreference()
|
||||
{
|
||||
var initPrefString = "mail.identity";
|
||||
var finalPrefString;
|
||||
|
||||
var allPrefElements = [
|
||||
{ prefstring:"signingCertSelectButton", id:"signingCertSelectButton"},
|
||||
{ prefstring:"encryptionCertSelectButton", id:"encryptionCertSelectButton"},
|
||||
{ prefstring:"sign_mail", id:"identity.sign_mail"},
|
||||
{ prefstring:"encryptionpolicy", id:"encryptionChoices"}
|
||||
];
|
||||
|
||||
finalPrefString = initPrefString + "." + gIdentity.key + ".";
|
||||
gSmimePrefbranch = Services.prefs.getBranch(finalPrefString);
|
||||
|
||||
disableIfLocked( allPrefElements );
|
||||
}
|
||||
|
||||
|
||||
// Does the work of disabling an element given the array which contains xul id/prefstring pairs.
|
||||
// Also saves the id/locked state in an array so that other areas of the code can avoid
|
||||
// stomping on the disabled state indiscriminately.
|
||||
function disableIfLocked( prefstrArray )
|
||||
{
|
||||
var i;
|
||||
for (i=0; i<prefstrArray.length; i++) {
|
||||
var id = prefstrArray[i].id;
|
||||
var element = document.getElementById(id);
|
||||
if (gSmimePrefbranch.prefIsLocked(prefstrArray[i].prefstring)) {
|
||||
// If encryption choices radio group is locked, make sure the individual
|
||||
// choices in the group are locked. Set a global (gEncryptionChoicesLocked)
|
||||
// indicating the status so that locking can be maintained further.
|
||||
if (id == "encryptionChoices") {
|
||||
document.getElementById("encrypt_mail_never").setAttribute("disabled", "true");
|
||||
document.getElementById("encrypt_mail_always").setAttribute("disabled", "true");
|
||||
gEncryptionChoicesLocked = true;
|
||||
}
|
||||
// If option to sign mail is locked (with true/false set in config file), disable
|
||||
// the corresponding checkbox and set a global (gSigningChoicesLocked) in order to
|
||||
// honor the locking as user changes other elements on the panel.
|
||||
if (id == "identity.sign_mail") {
|
||||
document.getElementById("identity.sign_mail").setAttribute("disabled", "true");
|
||||
gSigningChoicesLocked = true;
|
||||
}
|
||||
else {
|
||||
element.setAttribute("disabled", "true");
|
||||
if (id == "signingCertSelectButton") {
|
||||
document.getElementById("signingCertClearButton").setAttribute("disabled", "true");
|
||||
}
|
||||
else if (id == "encryptionCertSelectButton") {
|
||||
document.getElementById("encryptionCertClearButton").setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function alertUser(message)
|
||||
{
|
||||
Services.prompt.alert(window,
|
||||
gBrandBundle.getString("brandShortName"),
|
||||
message);
|
||||
}
|
||||
|
||||
function askUser(message)
|
||||
{
|
||||
let button = Services.prompt.confirmEx(
|
||||
window,
|
||||
gBrandBundle.getString("brandShortName"),
|
||||
message,
|
||||
Services.prompt.STD_YES_NO_BUTTONS,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{});
|
||||
// confirmEx returns button index:
|
||||
return (button == 0);
|
||||
}
|
||||
|
||||
function checkOtherCert(cert, pref, usage, msgNeedCertWantSame, msgWantSame, msgNeedCertWantToSelect, enabler)
|
||||
{
|
||||
var otherCertInfo = document.getElementById(pref);
|
||||
if (!otherCertInfo)
|
||||
return;
|
||||
|
||||
if (otherCertInfo.dbKey == cert.dbKey)
|
||||
// all is fine, same cert is now selected for both purposes
|
||||
return;
|
||||
|
||||
var certdb = Components.classes[nsX509CertDBContractID].getService(nsIX509CertDB);
|
||||
if (!certdb)
|
||||
return;
|
||||
|
||||
if (email_recipient_cert_usage == usage) {
|
||||
matchingOtherCert = certdb.findEmailEncryptionCert(cert.nickname);
|
||||
}
|
||||
else if (email_signing_cert_usage == usage) {
|
||||
matchingOtherCert = certdb.findEmailSigningCert(cert.nickname);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
var userWantsSameCert = false;
|
||||
|
||||
if (!otherCertInfo.value.length) {
|
||||
if (matchingOtherCert && (matchingOtherCert.dbKey == cert.dbKey)) {
|
||||
userWantsSameCert = askUser(gBundle.getString(msgNeedCertWantSame));
|
||||
}
|
||||
else {
|
||||
if (askUser(gBundle.getString(msgNeedCertWantToSelect))) {
|
||||
smimeSelectCert(pref);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (matchingOtherCert && (matchingOtherCert.dbKey == cert.dbKey)) {
|
||||
userWantsSameCert = askUser(gBundle.getString(msgWantSame));
|
||||
}
|
||||
}
|
||||
|
||||
if (userWantsSameCert) {
|
||||
otherCertInfo.value = cert.nickname + " [" + cert.serialNumber + "]";
|
||||
otherCertInfo.nickname = cert.nickname;
|
||||
otherCertInfo.dbKey = cert.dbKey;
|
||||
enabler(true);
|
||||
}
|
||||
}
|
||||
|
||||
function smimeSelectCert(smime_cert)
|
||||
{
|
||||
var certInfo = document.getElementById(smime_cert);
|
||||
if (!certInfo)
|
||||
return;
|
||||
|
||||
var picker = Components.classes["@mozilla.org/user_cert_picker;1"]
|
||||
.createInstance(Components.interfaces.nsIUserCertPicker);
|
||||
var canceled = new Object;
|
||||
var x509cert = 0;
|
||||
var certUsage;
|
||||
var selectEncryptionCert;
|
||||
|
||||
if (smime_cert == kEncryptionCertPref) {
|
||||
selectEncryptionCert = true;
|
||||
certUsage = email_recipient_cert_usage;
|
||||
} else if (smime_cert == kSigningCertPref) {
|
||||
selectEncryptionCert = false;
|
||||
certUsage = email_signing_cert_usage;
|
||||
}
|
||||
|
||||
try {
|
||||
x509cert = picker.pickByUsage(window,
|
||||
certInfo.value,
|
||||
certUsage, // this is from enum SECCertUsage
|
||||
false, true,
|
||||
gIdentity.email,
|
||||
canceled);
|
||||
} catch(e) {
|
||||
canceled.value = false;
|
||||
x509cert = null;
|
||||
}
|
||||
|
||||
if (!canceled.value) {
|
||||
if (!x509cert) {
|
||||
if (gIdentity.email) {
|
||||
alertUser(gBundle.getFormattedString(selectEncryptionCert ?
|
||||
"NoEncryptionCertForThisAddress" :
|
||||
"NoSigningCertForThisAddress",
|
||||
[ gIdentity.email ]));
|
||||
} else {
|
||||
alertUser(gBundle.getString(selectEncryptionCert ?
|
||||
"NoEncryptionCert" : "NoSigningCert"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
certInfo.removeAttribute("disabled");
|
||||
certInfo.value = x509cert.nickname + " [" + x509cert.serialNumber + "]";
|
||||
certInfo.nickname = x509cert.nickname;
|
||||
certInfo.dbKey = x509cert.dbKey;
|
||||
|
||||
if (selectEncryptionCert) {
|
||||
enableEncryptionControls(true);
|
||||
|
||||
checkOtherCert(x509cert,
|
||||
kSigningCertPref, email_signing_cert_usage,
|
||||
"signing_needCertWantSame",
|
||||
"signing_wantSame",
|
||||
"signing_needCertWantToSelect",
|
||||
enableSigningControls);
|
||||
} else {
|
||||
enableSigningControls(true);
|
||||
|
||||
checkOtherCert(x509cert,
|
||||
kEncryptionCertPref, email_recipient_cert_usage,
|
||||
"encryption_needCertWantSame",
|
||||
"encryption_wantSame",
|
||||
"encryption_needCertWantToSelect",
|
||||
enableEncryptionControls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enableCertSelectButtons();
|
||||
}
|
||||
|
||||
function enableEncryptionControls(do_enable)
|
||||
{
|
||||
if (gEncryptionChoicesLocked)
|
||||
return;
|
||||
|
||||
if (do_enable) {
|
||||
gEncryptAlways.removeAttribute("disabled");
|
||||
gNeverEncrypt.removeAttribute("disabled");
|
||||
gEncryptionCertName.removeAttribute("disabled");
|
||||
}
|
||||
else {
|
||||
gEncryptAlways.setAttribute("disabled", "true");
|
||||
gNeverEncrypt.setAttribute("disabled", "true");
|
||||
gEncryptionCertName.setAttribute("disabled", "true");
|
||||
gEncryptionChoices.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function enableSigningControls(do_enable)
|
||||
{
|
||||
if (gSigningChoicesLocked)
|
||||
return;
|
||||
|
||||
if (do_enable) {
|
||||
gSignMessages.removeAttribute("disabled");
|
||||
gSignCertName.removeAttribute("disabled");
|
||||
}
|
||||
else {
|
||||
gSignMessages.setAttribute("disabled", "true");
|
||||
gSignCertName.setAttribute("disabled", "true");
|
||||
gSignMessages.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
function enableCertSelectButtons()
|
||||
{
|
||||
document.getElementById("signingCertSelectButton").removeAttribute("disabled");
|
||||
|
||||
if (document.getElementById('identity.signing_cert_name').value.length)
|
||||
document.getElementById("signingCertClearButton").removeAttribute("disabled");
|
||||
else
|
||||
document.getElementById("signingCertClearButton").setAttribute("disabled", "true");
|
||||
|
||||
document.getElementById("encryptionCertSelectButton").removeAttribute("disabled");
|
||||
|
||||
if (document.getElementById('identity.encryption_cert_name').value.length)
|
||||
document.getElementById("encryptionCertClearButton").removeAttribute("disabled");
|
||||
else
|
||||
document.getElementById("encryptionCertClearButton").setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
function smimeClearCert(smime_cert)
|
||||
{
|
||||
var certInfo = document.getElementById(smime_cert);
|
||||
if (!certInfo)
|
||||
return;
|
||||
|
||||
certInfo.setAttribute("disabled", "true");
|
||||
certInfo.value = "";
|
||||
certInfo.nickname = "";
|
||||
certInfo.dbKey = "";
|
||||
|
||||
if (smime_cert == kEncryptionCertPref) {
|
||||
enableEncryptionControls(false);
|
||||
} else if (smime_cert == kSigningCertPref) {
|
||||
enableSigningControls(false);
|
||||
}
|
||||
|
||||
enableCertSelectButtons();
|
||||
}
|
||||
|
||||
function openCertManager()
|
||||
{
|
||||
// Check for an existing certManager window and focus it; it's not
|
||||
// application modal.
|
||||
let lastCertManager = Services.wm.getMostRecentWindow("mozilla:certmanager");
|
||||
if (lastCertManager)
|
||||
lastCertManager.focus();
|
||||
else
|
||||
window.openDialog("chrome://pippki/content/certManager.xul", "",
|
||||
"centerscreen,resizable=yes,dialog=no");
|
||||
}
|
||||
|
||||
function openDeviceManager()
|
||||
{
|
||||
// Check for an existing deviceManager window and focus it; it's not
|
||||
// application modal.
|
||||
let lastCertManager = Services.wm.getMostRecentWindow("mozilla:devicemanager");
|
||||
if (lastCertManager)
|
||||
lastCertManager.focus();
|
||||
else
|
||||
window.openDialog("chrome://pippki/content/device_manager.xul", "",
|
||||
"centerscreen,resizable=yes,dialog=no");
|
||||
}
|
||||
|
||||
function smimeOnLoadEditor()
|
||||
{
|
||||
smimeInitializeFields();
|
||||
|
||||
document.documentElement.setAttribute("ondialogaccept",
|
||||
"return smimeOnAcceptEditor();");
|
||||
}
|
||||
|
||||
26
mailnews/extensions/smime/content/am-smime.xul
Normal file
26
mailnews/extensions/smime/content/am-smime.xul
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
|
||||
|
||||
<?xul-overlay href="chrome://messenger/content/am-smimeOverlay.xul"?>
|
||||
|
||||
<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-smime.dtd">
|
||||
|
||||
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
class="color-dialog"
|
||||
onload="parent.onPanelLoaded('am-smime.xul');"
|
||||
ondialogaccept="smimeOnAcceptEditor();">
|
||||
|
||||
<vbox flex="1" style="overflow: auto;">
|
||||
<script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
|
||||
<script type="application/javascript" src="chrome://messenger/content/am-smime.js"/>
|
||||
|
||||
<dialogheader title="&securityTitle.label;"/>
|
||||
|
||||
<vbox flex="1" id="smimeEditing"/>
|
||||
</vbox>
|
||||
|
||||
</page>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/accountManage.css"
|
||||
type="text/css"?>
|
||||
|
||||
<?xul-overlay href="chrome://messenger/content/am-smimeOverlay.xul"?>
|
||||
|
||||
<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-smime.dtd">
|
||||
|
||||
<!--
|
||||
This is the overlay that adds the SMIME configurator
|
||||
to the identity editor of the account manager
|
||||
-->
|
||||
<overlay id="smimeAmIdEditOverlay"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/AccountManager.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/am-smime.js"/>
|
||||
|
||||
<tabs id="identitySettings">
|
||||
<tab label="&securityTab.label;"/>
|
||||
</tabs>
|
||||
|
||||
<tabpanels id="identityTabsPanels">
|
||||
<vbox flex="1" name="smimeEditingContent" id="smimeEditing"/>
|
||||
</tabpanels>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
window.addEventListener("load", smimeOnLoadEditor, false);
|
||||
]]>
|
||||
</script>
|
||||
</overlay>
|
||||
102
mailnews/extensions/smime/content/am-smimeOverlay.xul
Normal file
102
mailnews/extensions/smime/content/am-smimeOverlay.xul
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/accountManage.css"
|
||||
type="text/css"?>
|
||||
|
||||
<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-smime.dtd">
|
||||
|
||||
<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<vbox id="smimeEditing">
|
||||
|
||||
<stringbundleset>
|
||||
<stringbundle id="bundle_smime" src="chrome://messenger/locale/am-smime.properties"/>
|
||||
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
|
||||
</stringbundleset>
|
||||
|
||||
<label hidden="true" wsm_persist="true" id="identity.encryptionpolicy"/>
|
||||
|
||||
<description>&securityHeading.label;</description>
|
||||
|
||||
<groupbox id="signing.titlebox">
|
||||
<caption label="&signingGroupTitle.label;"/>
|
||||
|
||||
<label value="&signingCert.message;" control="identity.signing_cert_name"
|
||||
prefstring="mail.identity.%identitykey%.encryptionpolicy"/>
|
||||
|
||||
<hbox align="center">
|
||||
<textbox id="identity.signing_cert_name" wsm_persist="true" flex="1"
|
||||
prefstring="mail.identity.%identitykey%.signing_cert_name"
|
||||
readonly="true" disabled="true"/>
|
||||
|
||||
<button id="signingCertSelectButton"
|
||||
label="&digitalSign.certificate.button;"
|
||||
accesskey="&digitalSign.certificate.accesskey;"
|
||||
oncommand="smimeSelectCert('identity.signing_cert_name')"/>
|
||||
|
||||
<button id="signingCertClearButton"
|
||||
label="&digitalSign.certificate_clear.button;"
|
||||
accesskey="&digitalSign.certificate_clear.accesskey;"
|
||||
oncommand="smimeClearCert('identity.signing_cert_name')"/>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<checkbox id="identity.sign_mail" wsm_persist="true"
|
||||
prefstring="mail.identity.%identitykey%.sign_mail"
|
||||
label="&signMessage.label;" accesskey="&signMessage.accesskey;"/>
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="encryption.titlebox">
|
||||
<caption label="&encryptionGroupTitle.label;"/>
|
||||
|
||||
<label value="&encryptionCert.message;"
|
||||
control="identity.encryption_cert_name"/>
|
||||
|
||||
<hbox align="center">
|
||||
<textbox id="identity.encryption_cert_name" wsm_persist="true" flex="1"
|
||||
prefstring="mail.identity.%identitykey%.encryption_cert_name"
|
||||
readonly="true" disabled="true"/>
|
||||
|
||||
<button id="encryptionCertSelectButton"
|
||||
label="&encryption.certificate.button;"
|
||||
accesskey="&encryption.certificate.accesskey;"
|
||||
oncommand="smimeSelectCert('identity.encryption_cert_name')"/>
|
||||
|
||||
<button id="encryptionCertClearButton"
|
||||
label="&encryption.certificate_clear.button;"
|
||||
accesskey="&encryption.certificate_clear.accesskey;"
|
||||
oncommand="smimeClearCert('identity.encryption_cert_name')"/>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<label value="&encryptionChoiceLabel.label;" control="encryptionChoices"/>
|
||||
|
||||
<radiogroup id="encryptionChoices">
|
||||
<radio id="encrypt_mail_never" wsm_persist="true" value="0"
|
||||
label="&neverEncrypt.label;"
|
||||
accesskey="&neverEncrypt.accesskey;"/>
|
||||
|
||||
<radio id="encrypt_mail_always" wsm_persist="true" value="2"
|
||||
label="&alwaysEncryptMessage.label;"
|
||||
accesskey="&alwaysEncryptMessage.accesskey;"/>
|
||||
</radiogroup>
|
||||
</groupbox>
|
||||
|
||||
<!-- Certificate manager -->
|
||||
<groupbox id="smimeCertificateManager" orient="horizontal">
|
||||
<caption label="&certificates.label;"/>
|
||||
<button id="openCertManagerButton" oncommand="openCertManager();"
|
||||
label="&manageCerts2.label;" accesskey="&manageCerts2.accesskey;"
|
||||
prefstring="security.disable_button.openCertManager"/>
|
||||
<button id="openDeviceManagerButton" oncommand="openDeviceManager();"
|
||||
label="&manageDevices.label;" accesskey="&manageDevices.accesskey;"
|
||||
prefstring="security.disable_button.openDeviceManager"/>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
</overlay>
|
||||
265
mailnews/extensions/smime/content/certFetchingStatus.js
Normal file
265
mailnews/extensions/smime/content/certFetchingStatus.js
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* We expect the following arguments:
|
||||
- pref name of LDAP directory to fetch from
|
||||
- array with email addresses
|
||||
|
||||
Display modal dialog with message and stop button.
|
||||
In onload, kick off binding to LDAP.
|
||||
When bound, kick off the searches.
|
||||
On finding certificates, import into permanent cert database.
|
||||
When all searches are finished, close the dialog.
|
||||
*/
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var nsIX509CertDB = Components.interfaces.nsIX509CertDB;
|
||||
var nsX509CertDB = "@mozilla.org/security/x509certdb;1";
|
||||
var CertAttribute = "usercertificate;binary";
|
||||
|
||||
var gEmailAddresses;
|
||||
var gDirectoryPref;
|
||||
var gLdapServerURL;
|
||||
var gLdapConnection;
|
||||
var gCertDB;
|
||||
var gLdapOperation;
|
||||
var gLogin;
|
||||
|
||||
function onLoad()
|
||||
{
|
||||
gDirectoryPref = window.arguments[0];
|
||||
gEmailAddresses = window.arguments[1];
|
||||
|
||||
if (!gEmailAddresses.length)
|
||||
{
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(search, 1);
|
||||
}
|
||||
|
||||
function search()
|
||||
{
|
||||
// get the login to authenticate as, if there is one
|
||||
try {
|
||||
gLogin = Services.prefs.getComplexValue(gDirectoryPref + ".auth.dn", Components.interfaces.nsISupportsString).data;
|
||||
} catch (ex) {
|
||||
// if we don't have this pref, no big deal
|
||||
}
|
||||
|
||||
try {
|
||||
let url = Services.prefs.getCharPref(gDirectoryPref + ".uri");
|
||||
|
||||
gLdapServerURL = Services.io
|
||||
.newURI(url, null, null).QueryInterface(Components.interfaces.nsILDAPURL);
|
||||
|
||||
gLdapConnection = Components.classes["@mozilla.org/network/ldap-connection;1"]
|
||||
.createInstance().QueryInterface(Components.interfaces.nsILDAPConnection);
|
||||
|
||||
gLdapConnection.init(gLdapServerURL, gLogin, new boundListener(),
|
||||
null, Components.interfaces.nsILDAPConnection.VERSION3);
|
||||
|
||||
} catch (ex) {
|
||||
dump(ex);
|
||||
dump(" exception creating ldap connection\n");
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
function stopFetching()
|
||||
{
|
||||
if (gLdapOperation) {
|
||||
try {
|
||||
gLdapOperation.abandon();
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function importCert(ber_value)
|
||||
{
|
||||
if (!gCertDB) {
|
||||
gCertDB = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
|
||||
}
|
||||
|
||||
var cert_length = new Object();
|
||||
var cert_bytes = ber_value.get(cert_length);
|
||||
|
||||
if (cert_bytes) {
|
||||
gCertDB.importEmailCertificate(cert_bytes, cert_length.value, null);
|
||||
}
|
||||
}
|
||||
|
||||
function getLDAPOperation()
|
||||
{
|
||||
gLdapOperation = Components.classes["@mozilla.org/network/ldap-operation;1"]
|
||||
.createInstance().QueryInterface(Components.interfaces.nsILDAPOperation);
|
||||
|
||||
gLdapOperation.init(gLdapConnection,
|
||||
new ldapMessageListener(),
|
||||
null);
|
||||
}
|
||||
|
||||
function getPassword()
|
||||
{
|
||||
// we only need a password if we are using credentials
|
||||
if (gLogin)
|
||||
{
|
||||
let authPrompter = Services.ww.getNewAuthPrompter(window.QueryInterface(Components.interfaces.nsIDOMWindow));
|
||||
let strBundle = document.getElementById('bundle_ldap');
|
||||
let password = { value: "" };
|
||||
|
||||
// nsLDAPAutocompleteSession uses asciiHost instead of host for the prompt text, I think we should be
|
||||
// consistent.
|
||||
if (authPrompter.promptPassword(strBundle.getString("authPromptTitle"),
|
||||
strBundle.getFormattedString("authPromptText", [gLdapServerURL.asciiHost]),
|
||||
gLdapServerURL.spec,
|
||||
authPrompter.SAVE_PASSWORD_PERMANENTLY,
|
||||
password))
|
||||
return password.value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function kickOffBind()
|
||||
{
|
||||
try {
|
||||
getLDAPOperation();
|
||||
gLdapOperation.simpleBind(getPassword());
|
||||
}
|
||||
catch (e) {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
function kickOffSearch()
|
||||
{
|
||||
try {
|
||||
var prefix1 = "";
|
||||
var suffix1 = "";
|
||||
|
||||
var urlFilter = gLdapServerURL.filter;
|
||||
|
||||
if (urlFilter != null && urlFilter.length > 0 && urlFilter != "(objectclass=*)") {
|
||||
if (urlFilter.startsWith('(')) {
|
||||
prefix1 = "(&" + urlFilter;
|
||||
}
|
||||
else {
|
||||
prefix1 = "(&(" + urlFilter + ")";
|
||||
}
|
||||
suffix1 = ")";
|
||||
}
|
||||
|
||||
var prefix2 = "";
|
||||
var suffix2 = "";
|
||||
|
||||
if (gEmailAddresses.length > 1) {
|
||||
prefix2 = "(|";
|
||||
suffix2 = ")";
|
||||
}
|
||||
|
||||
var mailFilter = "";
|
||||
|
||||
for (var i = 0; i < gEmailAddresses.length; ++i) {
|
||||
mailFilter += "(mail=" + gEmailAddresses[i] + ")";
|
||||
}
|
||||
|
||||
var filter = prefix1 + prefix2 + mailFilter + suffix2 + suffix1;
|
||||
|
||||
var wanted_attributes = CertAttribute;
|
||||
|
||||
// Max search results =>
|
||||
// Double number of email addresses, because each person might have
|
||||
// multiple certificates listed. We expect at most two certificates,
|
||||
// one for signing, one for encrypting.
|
||||
// Maybe that number should be larger, to allow for deployments,
|
||||
// where even more certs can be stored per user???
|
||||
|
||||
var maxEntriesWanted = gEmailAddresses.length * 2;
|
||||
|
||||
getLDAPOperation();
|
||||
gLdapOperation.searchExt(gLdapServerURL.dn, gLdapServerURL.scope,
|
||||
filter, wanted_attributes, 0, maxEntriesWanted);
|
||||
}
|
||||
catch (e) {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function boundListener() {
|
||||
}
|
||||
|
||||
boundListener.prototype.QueryInterface =
|
||||
function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISupports) ||
|
||||
iid.equals(Components.interfaces.nsILDAPMessageListener))
|
||||
return this;
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
boundListener.prototype.onLDAPMessage =
|
||||
function(aMessage) {
|
||||
}
|
||||
|
||||
boundListener.prototype.onLDAPInit =
|
||||
function(aConn, aStatus) {
|
||||
kickOffBind();
|
||||
}
|
||||
|
||||
|
||||
function ldapMessageListener() {
|
||||
}
|
||||
|
||||
ldapMessageListener.prototype.QueryInterface =
|
||||
function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISupports) ||
|
||||
iid.equals(Components.interfaces.nsILDAPMessageListener))
|
||||
return this;
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
ldapMessageListener.prototype.onLDAPMessage =
|
||||
function(aMessage) {
|
||||
if (Components.interfaces.nsILDAPMessage.RES_SEARCH_RESULT == aMessage.type) {
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Components.interfaces.nsILDAPMessage.RES_BIND == aMessage.type) {
|
||||
if (Components.interfaces.nsILDAPErrors.SUCCESS != aMessage.errorCode) {
|
||||
window.close();
|
||||
}
|
||||
else {
|
||||
kickOffSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Components.interfaces.nsILDAPMessage.RES_SEARCH_ENTRY == aMessage.type) {
|
||||
var outSize = new Object();
|
||||
try {
|
||||
var outBinValues = aMessage.getBinaryValues(CertAttribute, outSize);
|
||||
|
||||
var i;
|
||||
for (i=0; i < outSize.value; ++i) {
|
||||
importCert(outBinValues[i]);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ldapMessageListener.prototype.onLDAPInit =
|
||||
function(aConn, aStatus) {
|
||||
}
|
||||
24
mailnews/extensions/smime/content/certFetchingStatus.xul
Normal file
24
mailnews/extensions/smime/content/certFetchingStatus.xul
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger/skin/smime/certFetchingStatus.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/certFetchingStatus.dtd">
|
||||
|
||||
<dialog id="certFetchingStatus" title="&title.label;"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
style="width: 50em;"
|
||||
buttons="cancel"
|
||||
buttonlabelcancel="&stop.label;"
|
||||
ondialogcancel="return stopFetching();"
|
||||
onload="onLoad();">
|
||||
|
||||
<stringbundle id="bundle_ldap" src="chrome://mozldap/locale/ldap.properties"/>
|
||||
<script type="application/javascript" src="chrome://messenger-smime/content/certFetchingStatus.js"/>
|
||||
|
||||
<description>&info.message;</description>
|
||||
|
||||
</dialog>
|
||||
73
mailnews/extensions/smime/content/certpicker.js
Normal file
73
mailnews/extensions/smime/content/certpicker.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
|
||||
|
||||
var dialogParams;
|
||||
var itemCount = 0;
|
||||
|
||||
function onLoad()
|
||||
{
|
||||
dialogParams = window.arguments[0].QueryInterface(nsIDialogParamBlock);
|
||||
|
||||
var selectElement = document.getElementById("nicknames");
|
||||
itemCount = dialogParams.GetInt(0);
|
||||
|
||||
var selIndex = dialogParams.GetInt(1);
|
||||
if (selIndex < 0) {
|
||||
selIndex = 0;
|
||||
}
|
||||
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
let menuItemNode = document.createElement("menuitem");
|
||||
let nick = dialogParams.GetString(i);
|
||||
menuItemNode.setAttribute("value", i);
|
||||
menuItemNode.setAttribute("label", nick); // This is displayed.
|
||||
selectElement.firstChild.appendChild(menuItemNode);
|
||||
|
||||
if (selIndex == i) {
|
||||
selectElement.selectedItem = menuItemNode;
|
||||
}
|
||||
}
|
||||
|
||||
dialogParams.SetInt(0, 0); // Set cancel return value.
|
||||
setDetails();
|
||||
}
|
||||
|
||||
function setDetails()
|
||||
{
|
||||
let selItem = document.getElementById("nicknames").value;
|
||||
if (selItem.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = parseInt(selItem);
|
||||
let details = dialogParams.GetString(index + itemCount);
|
||||
document.getElementById("details").value = details;
|
||||
}
|
||||
|
||||
function onCertSelected()
|
||||
{
|
||||
setDetails();
|
||||
}
|
||||
|
||||
function doOK()
|
||||
{
|
||||
// Signal that the user accepted.
|
||||
dialogParams.SetInt(0, 1);
|
||||
|
||||
// Signal the index of the selected cert in the list of cert nicknames
|
||||
// provided.
|
||||
let index = parseInt(document.getElementById("nicknames").value);
|
||||
dialogParams.SetInt(1, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
function doCancel()
|
||||
{
|
||||
dialogParams.SetInt(0, 0); // Signal that the user cancelled.
|
||||
return true;
|
||||
}
|
||||
38
mailnews/extensions/smime/content/certpicker.xul
Normal file
38
mailnews/extensions/smime/content/certpicker.xul
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
|
||||
<!DOCTYPE dialog [
|
||||
<!ENTITY % amSMIMEDTD SYSTEM "chrome://messenger/locale/am-smime.dtd" >
|
||||
%amSMIMEDTD;
|
||||
]>
|
||||
|
||||
<dialog id="certPicker" title="&certPicker.title;"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
style="width: 50em;"
|
||||
buttons="accept,cancel"
|
||||
ondialogaccept="return doOK();"
|
||||
ondialogcancel="return doCancel();"
|
||||
onload="onLoad();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://messenger/content/certpicker.js"/>
|
||||
|
||||
<hbox align="center">
|
||||
<broadcaster id="certSelected" oncommand="onCertSelected();"/>
|
||||
<label id="pickerInfo" value="&certPicker.info;"/>
|
||||
<!-- The items in this menulist must never be sorted,
|
||||
but remain in the order filled by the application
|
||||
-->
|
||||
<menulist id="nicknames" observes="certSelected">
|
||||
<menupopup/>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<separator class="thin"/>
|
||||
<label value="&certPicker.detailsLabel;"/>
|
||||
<textbox readonly="true" id="details" multiline="true"
|
||||
style="height: 12em;" flex="1"/>
|
||||
</dialog>
|
||||
357
mailnews/extensions/smime/content/msgCompSMIMEOverlay.js
Normal file
357
mailnews/extensions/smime/content/msgCompSMIMEOverlay.js
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Account encryption policy values:
|
||||
// const kEncryptionPolicy_Never = 0;
|
||||
// 'IfPossible' was used by ns4.
|
||||
// const kEncryptionPolicy_IfPossible = 1;
|
||||
var kEncryptionPolicy_Always = 2;
|
||||
|
||||
var gEncryptedURIService =
|
||||
Components.classes["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"]
|
||||
.getService(Components.interfaces.nsIEncryptedSMIMEURIsService);
|
||||
|
||||
var gNextSecurityButtonCommand = "";
|
||||
var gSMFields = null;
|
||||
var gEncryptOptionChanged;
|
||||
var gSignOptionChanged;
|
||||
|
||||
function onComposerLoad()
|
||||
{
|
||||
// Are we already set up ? Or are the required fields missing ?
|
||||
if (gSMFields || !gMsgCompose || !gMsgCompose.compFields)
|
||||
return;
|
||||
|
||||
gMsgCompose.compFields.securityInfo = null;
|
||||
|
||||
gSMFields = Components.classes["@mozilla.org/messenger-smime/composefields;1"]
|
||||
.createInstance(Components.interfaces.nsIMsgSMIMECompFields);
|
||||
if (!gSMFields)
|
||||
return;
|
||||
|
||||
gMsgCompose.compFields.securityInfo = gSMFields;
|
||||
|
||||
// Set up the intial security state.
|
||||
gSMFields.requireEncryptMessage =
|
||||
gCurrentIdentity.getIntAttribute("encryptionpolicy") == kEncryptionPolicy_Always;
|
||||
if (!gSMFields.requireEncryptMessage &&
|
||||
gEncryptedURIService &&
|
||||
gEncryptedURIService.isEncrypted(gMsgCompose.originalMsgURI))
|
||||
{
|
||||
// Override encryption setting if original is known as encrypted.
|
||||
gSMFields.requireEncryptMessage = true;
|
||||
}
|
||||
if (gSMFields.requireEncryptMessage)
|
||||
setEncryptionUI();
|
||||
else
|
||||
setNoEncryptionUI();
|
||||
|
||||
gSMFields.signMessage = gCurrentIdentity.getBoolAttribute("sign_mail");
|
||||
if (gSMFields.signMessage)
|
||||
setSignatureUI();
|
||||
else
|
||||
setNoSignatureUI();
|
||||
}
|
||||
|
||||
addEventListener("load", smimeComposeOnLoad, {capture: false, once: true});
|
||||
|
||||
// this function gets called multiple times
|
||||
function smimeComposeOnLoad()
|
||||
{
|
||||
onComposerLoad();
|
||||
|
||||
top.controllers.appendController(SecurityController);
|
||||
|
||||
addEventListener("compose-from-changed", onComposerFromChanged, true);
|
||||
addEventListener("compose-send-message", onComposerSendMessage, true);
|
||||
|
||||
addEventListener("unload", smimeComposeOnUnload, {capture: false, once: true});
|
||||
}
|
||||
|
||||
function smimeComposeOnUnload()
|
||||
{
|
||||
removeEventListener("compose-from-changed", onComposerFromChanged, true);
|
||||
removeEventListener("compose-send-message", onComposerSendMessage, true);
|
||||
|
||||
top.controllers.removeController(SecurityController);
|
||||
}
|
||||
|
||||
function showNeedSetupInfo()
|
||||
{
|
||||
let compSmimeBundle = document.getElementById("bundle_comp_smime");
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
if (!compSmimeBundle || !brandBundle)
|
||||
return;
|
||||
|
||||
let buttonPressed = Services.prompt.confirmEx(window,
|
||||
brandBundle.getString("brandShortName"),
|
||||
compSmimeBundle.getString("NeedSetup"),
|
||||
Services.prompt.STD_YES_NO_BUTTONS, 0, 0, 0, null, {});
|
||||
if (buttonPressed == 0)
|
||||
openHelp("sign-encrypt", "chrome://communicator/locale/help/suitehelp.rdf");
|
||||
}
|
||||
|
||||
function toggleEncryptMessage()
|
||||
{
|
||||
if (!gSMFields)
|
||||
return;
|
||||
|
||||
gSMFields.requireEncryptMessage = !gSMFields.requireEncryptMessage;
|
||||
|
||||
if (gSMFields.requireEncryptMessage)
|
||||
{
|
||||
// Make sure we have a cert.
|
||||
if (!gCurrentIdentity.getUnicharAttribute("encryption_cert_name"))
|
||||
{
|
||||
gSMFields.requireEncryptMessage = false;
|
||||
showNeedSetupInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
setEncryptionUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
setNoEncryptionUI();
|
||||
}
|
||||
|
||||
gEncryptOptionChanged = true;
|
||||
}
|
||||
|
||||
function toggleSignMessage()
|
||||
{
|
||||
if (!gSMFields)
|
||||
return;
|
||||
|
||||
gSMFields.signMessage = !gSMFields.signMessage;
|
||||
|
||||
if (gSMFields.signMessage) // make sure we have a cert name...
|
||||
{
|
||||
if (!gCurrentIdentity.getUnicharAttribute("signing_cert_name"))
|
||||
{
|
||||
gSMFields.signMessage = false;
|
||||
showNeedSetupInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
setSignatureUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
setNoSignatureUI();
|
||||
}
|
||||
|
||||
gSignOptionChanged = true;
|
||||
}
|
||||
|
||||
function setSecuritySettings(menu_id)
|
||||
{
|
||||
if (!gSMFields)
|
||||
return;
|
||||
|
||||
document.getElementById("menu_securityEncryptRequire" + menu_id)
|
||||
.setAttribute("checked", gSMFields.requireEncryptMessage);
|
||||
document.getElementById("menu_securitySign" + menu_id)
|
||||
.setAttribute("checked", gSMFields.signMessage);
|
||||
}
|
||||
|
||||
function setNextCommand(what)
|
||||
{
|
||||
gNextSecurityButtonCommand = what;
|
||||
}
|
||||
|
||||
function doSecurityButton()
|
||||
{
|
||||
var what = gNextSecurityButtonCommand;
|
||||
gNextSecurityButtonCommand = "";
|
||||
|
||||
switch (what)
|
||||
{
|
||||
case "encryptMessage":
|
||||
toggleEncryptMessage();
|
||||
break;
|
||||
|
||||
case "signMessage":
|
||||
toggleSignMessage();
|
||||
break;
|
||||
|
||||
case "show":
|
||||
default:
|
||||
showMessageComposeSecurityStatus();
|
||||
}
|
||||
}
|
||||
|
||||
function setNoSignatureUI()
|
||||
{
|
||||
top.document.getElementById("securityStatus").removeAttribute("signing");
|
||||
top.document.getElementById("signing-status").collapsed = true;
|
||||
}
|
||||
|
||||
function setSignatureUI()
|
||||
{
|
||||
top.document.getElementById("securityStatus").setAttribute("signing", "ok");
|
||||
top.document.getElementById("signing-status").collapsed = false;
|
||||
}
|
||||
|
||||
function setNoEncryptionUI()
|
||||
{
|
||||
top.document.getElementById("securityStatus").removeAttribute("crypto");
|
||||
top.document.getElementById("encryption-status").collapsed = true;
|
||||
}
|
||||
|
||||
function setEncryptionUI()
|
||||
{
|
||||
top.document.getElementById("securityStatus").setAttribute("crypto", "ok");
|
||||
top.document.getElementById("encryption-status").collapsed = false;
|
||||
}
|
||||
|
||||
function showMessageComposeSecurityStatus()
|
||||
{
|
||||
Recipients2CompFields(gMsgCompose.compFields);
|
||||
|
||||
window.openDialog(
|
||||
"chrome://messenger-smime/content/msgCompSecurityInfo.xul",
|
||||
"",
|
||||
"chrome,modal,resizable,centerscreen",
|
||||
{
|
||||
compFields : gMsgCompose.compFields,
|
||||
subject : GetMsgSubjectElement().value,
|
||||
smFields : gSMFields,
|
||||
isSigningCertAvailable :
|
||||
gCurrentIdentity.getUnicharAttribute("signing_cert_name") != "",
|
||||
isEncryptionCertAvailable :
|
||||
gCurrentIdentity.getUnicharAttribute("encryption_cert_name") != "",
|
||||
currentIdentity : gCurrentIdentity
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var SecurityController =
|
||||
{
|
||||
supportsCommand: function(command)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case "cmd_viewSecurityStatus":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
isCommandEnabled: function(command)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case "cmd_viewSecurityStatus":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function onComposerSendMessage()
|
||||
{
|
||||
let missingCount = new Object();
|
||||
let emailAddresses = new Object();
|
||||
|
||||
try
|
||||
{
|
||||
if (!gMsgCompose.compFields.securityInfo.requireEncryptMessage)
|
||||
return;
|
||||
|
||||
Components.classes["@mozilla.org/messenger-smime/smimejshelper;1"]
|
||||
.createInstance(Components.interfaces.nsISMimeJSHelper)
|
||||
.getNoCertAddresses(gMsgCompose.compFields,
|
||||
missingCount,
|
||||
emailAddresses);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (missingCount.value > 0)
|
||||
{
|
||||
// The rules here: If the current identity has a directoryServer set, then
|
||||
// use that, otherwise, try the global preference instead.
|
||||
|
||||
let autocompleteDirectory;
|
||||
|
||||
// Does the current identity override the global preference?
|
||||
if (gCurrentIdentity.overrideGlobalPref)
|
||||
{
|
||||
autocompleteDirectory = gCurrentIdentity.directoryServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try the global one
|
||||
if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory"))
|
||||
autocompleteDirectory =
|
||||
Services.prefs.getCharPref("ldap_2.autoComplete.directoryServer");
|
||||
}
|
||||
|
||||
if (autocompleteDirectory)
|
||||
window.openDialog("chrome://messenger-smime/content/certFetchingStatus.xul",
|
||||
"",
|
||||
"chrome,modal,resizable,centerscreen",
|
||||
autocompleteDirectory,
|
||||
emailAddresses.value);
|
||||
}
|
||||
}
|
||||
|
||||
function onComposerFromChanged()
|
||||
{
|
||||
if (!gSMFields)
|
||||
return;
|
||||
|
||||
var encryptionPolicy = gCurrentIdentity.getIntAttribute("encryptionpolicy");
|
||||
var useEncryption = false;
|
||||
|
||||
if (!gEncryptOptionChanged)
|
||||
{
|
||||
// Encryption wasn't manually checked.
|
||||
// Set up the encryption policy from the setting of the new identity.
|
||||
|
||||
// 0 == never, 1 == if possible (ns4), 2 == always encrypt.
|
||||
useEncryption = (encryptionPolicy == kEncryptionPolicy_Always);
|
||||
}
|
||||
else
|
||||
{
|
||||
useEncryption = !!gCurrentIdentity.getUnicharAttribute("encryption_cert_name");
|
||||
}
|
||||
|
||||
gSMFields.requireEncryptMessage = useEncryption;
|
||||
if (useEncryption)
|
||||
setEncryptionUI();
|
||||
else
|
||||
setNoEncryptionUI();
|
||||
|
||||
// - If signing is disabled, we will not turn it on automatically.
|
||||
// - If signing is enabled, but the new account defaults to not sign, we will turn signing off.
|
||||
var signMessage = gCurrentIdentity.getBoolAttribute("sign_mail");
|
||||
var useSigning = false;
|
||||
|
||||
if (!gSignOptionChanged)
|
||||
{
|
||||
// Signing wasn't manually checked.
|
||||
// Set up the signing policy from the setting of the new identity.
|
||||
useSigning = signMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
useSigning = !!gCurrentIdentity.getUnicharAttribute("signing_cert_name");
|
||||
}
|
||||
gSMFields.signMessage = useSigning;
|
||||
if (useSigning)
|
||||
setSignatureUI();
|
||||
else
|
||||
setNoSignatureUI();
|
||||
}
|
||||
85
mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul
Normal file
85
mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSMIMEOverlay.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE overlay SYSTEM "chrome://messenger-smime/locale/msgCompSMIMEOverlay.dtd">
|
||||
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript" src="chrome://messenger-smime/content/msgCompSMIMEOverlay.js"/>
|
||||
|
||||
<window id="msgcomposeWindow">
|
||||
<broadcaster id="securityStatus" crypto="" signing=""/>
|
||||
<observes element="securityStatus" attribute="crypto" />
|
||||
<observes element="securityStatus" attribute="signing" />
|
||||
<stringbundle id="bundle_comp_smime" src="chrome://messenger-smime/locale/msgCompSMIMEOverlay.properties"/>
|
||||
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
|
||||
</window>
|
||||
|
||||
<menupopup id="optionsMenuPopup"
|
||||
onpopupshowing="setSecuritySettings(1);">
|
||||
<menuseparator id="smimeOptionsSeparator"/>
|
||||
|
||||
<menuitem id="menu_securityEncryptRequire1"
|
||||
type="checkbox"
|
||||
label="&menu_securityEncryptRequire.label;"
|
||||
accesskey="&menu_securityEncryptRequire.accesskey;"
|
||||
oncommand="toggleEncryptMessage();"/>
|
||||
<menuitem id="menu_securitySign1"
|
||||
type="checkbox"
|
||||
label="&menu_securitySign.label;"
|
||||
accesskey="&menu_securitySign.accesskey;"
|
||||
oncommand="toggleSignMessage();"/>
|
||||
</menupopup>
|
||||
|
||||
<toolbarpalette id="MsgComposeToolbarPalette">
|
||||
<toolbarbutton id="button-security"
|
||||
type="menu-button"
|
||||
class="toolbarbutton-1"
|
||||
label="&securityButton.label;"
|
||||
tooltiptext="&securityButton.tooltip;"
|
||||
oncommand="doSecurityButton();">
|
||||
<menupopup onpopupshowing="setSecuritySettings(2);">
|
||||
<menuitem id="menu_securityEncryptRequire2"
|
||||
type="checkbox"
|
||||
label="&menu_securityEncryptRequire.label;"
|
||||
accesskey="&menu_securityEncryptRequire.accesskey;"
|
||||
oncommand="setNextCommand('encryptMessage');"/>
|
||||
<menuitem id="menu_securitySign2"
|
||||
type="checkbox"
|
||||
label="&menu_securitySign.label;"
|
||||
accesskey="&menu_securitySign.accesskey;"
|
||||
oncommand="setNextCommand('signMessage');"/>
|
||||
<menuseparator id="smimeToolbarButtonSeparator"/>
|
||||
<menuitem id="menu_securityStatus2"
|
||||
label="&menu_securityStatus.label;"
|
||||
accesskey="&menu_securityStatus.accesskey;"
|
||||
oncommand="setNextCommand('show');"/>
|
||||
</menupopup>
|
||||
</toolbarbutton>
|
||||
</toolbarpalette>
|
||||
|
||||
<statusbar id="status-bar">
|
||||
<statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" collapsed="true"
|
||||
id="signing-status" oncommand="showMessageComposeSecurityStatus();"/>
|
||||
<statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" collapsed="true"
|
||||
id="encryption-status" oncommand="showMessageComposeSecurityStatus();"/>
|
||||
</statusbar>
|
||||
|
||||
<commandset id="composeCommands">
|
||||
<command id="cmd_viewSecurityStatus" oncommand="showMessageComposeSecurityStatus();"/>
|
||||
</commandset>
|
||||
|
||||
<menupopup id="menu_View_Popup">
|
||||
<menuseparator id="viewMenuBeforeSecurityStatusSeparator"/>
|
||||
<menuitem id="menu_viewSecurityStatus"
|
||||
label="&menu_viewSecurityStatus.label;"
|
||||
accesskey="&menu_viewSecurityStatus.accesskey;"
|
||||
command="cmd_viewSecurityStatus"/>
|
||||
</menupopup>
|
||||
|
||||
</overlay>
|
||||
244
mailnews/extensions/smime/content/msgCompSecurityInfo.js
Normal file
244
mailnews/extensions/smime/content/msgCompSecurityInfo.js
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var gListBox;
|
||||
var gViewButton;
|
||||
var gBundle;
|
||||
|
||||
var gEmailAddresses;
|
||||
var gCertStatusSummaries;
|
||||
var gCertIssuedInfos;
|
||||
var gCertExpiresInfos;
|
||||
var gCerts;
|
||||
var gCount;
|
||||
|
||||
var gSMimeContractID = "@mozilla.org/messenger-smime/smimejshelper;1";
|
||||
var gISMimeJSHelper = Components.interfaces.nsISMimeJSHelper;
|
||||
var gIX509Cert = Components.interfaces.nsIX509Cert;
|
||||
var nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
|
||||
var nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1"
|
||||
|
||||
function getStatusExplanation(value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case gIX509Cert.VERIFIED_OK:
|
||||
return gBundle.getString("StatusValid");
|
||||
|
||||
case gIX509Cert.NOT_VERIFIED_UNKNOWN:
|
||||
case gIX509Cert.INVALID_CA:
|
||||
case gIX509Cert.USAGE_NOT_ALLOWED:
|
||||
return gBundle.getString("StatusInvalid");
|
||||
|
||||
case gIX509Cert.CERT_REVOKED:
|
||||
return gBundle.getString("StatusRevoked");
|
||||
|
||||
case gIX509Cert.CERT_EXPIRED:
|
||||
return gBundle.getString("StatusExpired");
|
||||
|
||||
case gIX509Cert.CERT_NOT_TRUSTED:
|
||||
case gIX509Cert.ISSUER_NOT_TRUSTED:
|
||||
case gIX509Cert.ISSUER_UNKNOWN:
|
||||
return gBundle.getString("StatusUntrusted");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function onLoad()
|
||||
{
|
||||
var params = window.arguments[0];
|
||||
if (!params)
|
||||
return;
|
||||
|
||||
var helper = Components.classes[gSMimeContractID].createInstance(gISMimeJSHelper);
|
||||
|
||||
if (!helper)
|
||||
return;
|
||||
|
||||
gListBox = document.getElementById("infolist");
|
||||
gViewButton = document.getElementById("viewCertButton");
|
||||
gBundle = document.getElementById("bundle_smime_comp_info");
|
||||
|
||||
gEmailAddresses = new Object();
|
||||
gCertStatusSummaries = new Object();
|
||||
gCertIssuedInfos = new Object();
|
||||
gCertExpiresInfos = new Object();
|
||||
gCerts = new Object();
|
||||
gCount = new Object();
|
||||
var canEncrypt = new Object();
|
||||
|
||||
var allow_ldap_cert_fetching = false;
|
||||
|
||||
try {
|
||||
if (params.compFields.securityInfo.requireEncryptMessage) {
|
||||
allow_ldap_cert_fetching = true;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
helper.getRecipientCertsInfo(
|
||||
params.compFields,
|
||||
gCount,
|
||||
gEmailAddresses,
|
||||
gCertStatusSummaries,
|
||||
gCertIssuedInfos,
|
||||
gCertExpiresInfos,
|
||||
gCerts,
|
||||
canEncrypt);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
dump(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allow_ldap_cert_fetching)
|
||||
break;
|
||||
|
||||
allow_ldap_cert_fetching = false;
|
||||
|
||||
var missing = new Array();
|
||||
|
||||
for (var j = gCount.value - 1; j >= 0; --j)
|
||||
{
|
||||
if (!gCerts.value[j])
|
||||
{
|
||||
missing[missing.length] = gEmailAddresses.value[j];
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length > 0)
|
||||
{
|
||||
var autocompleteLdap = Services.prefs
|
||||
.getBoolPref("ldap_2.autoComplete.useDirectory");
|
||||
|
||||
if (autocompleteLdap)
|
||||
{
|
||||
var autocompleteDirectory = null;
|
||||
if (params.currentIdentity.overrideGlobalPref) {
|
||||
autocompleteDirectory = params.currentIdentity.directoryServer;
|
||||
} else {
|
||||
autocompleteDirectory = Services.prefs
|
||||
.getCharPref("ldap_2.autoComplete.directoryServer");
|
||||
}
|
||||
|
||||
if (autocompleteDirectory)
|
||||
{
|
||||
window.openDialog('chrome://messenger-smime/content/certFetchingStatus.xul',
|
||||
'',
|
||||
'chrome,resizable=1,modal=1,dialog=1',
|
||||
autocompleteDirectory,
|
||||
missing
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gBundle)
|
||||
{
|
||||
var yes_string = gBundle.getString("StatusYes");
|
||||
var no_string = gBundle.getString("StatusNo");
|
||||
var not_possible_string = gBundle.getString("StatusNotPossible");
|
||||
|
||||
var signed_element = document.getElementById("signed");
|
||||
var encrypted_element = document.getElementById("encrypted");
|
||||
|
||||
if (params.smFields.requireEncryptMessage)
|
||||
{
|
||||
if (params.isEncryptionCertAvailable && canEncrypt.value)
|
||||
{
|
||||
encrypted_element.value = yes_string;
|
||||
}
|
||||
else
|
||||
{
|
||||
encrypted_element.value = not_possible_string;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
encrypted_element.value = no_string;
|
||||
}
|
||||
|
||||
if (params.smFields.signMessage)
|
||||
{
|
||||
if (params.isSigningCertAvailable)
|
||||
{
|
||||
signed_element.value = yes_string;
|
||||
}
|
||||
else
|
||||
{
|
||||
signed_element.value = not_possible_string;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
signed_element.value = no_string;
|
||||
}
|
||||
}
|
||||
|
||||
var imax = gCount.value;
|
||||
|
||||
for (var i = 0; i < imax; ++i)
|
||||
{
|
||||
var listitem = document.createElement("listitem");
|
||||
|
||||
listitem.appendChild(createCell(gEmailAddresses.value[i]));
|
||||
|
||||
if (!gCerts.value[i])
|
||||
{
|
||||
listitem.appendChild(createCell(gBundle.getString("StatusNotFound")));
|
||||
}
|
||||
else
|
||||
{
|
||||
listitem.appendChild(createCell(getStatusExplanation(gCertStatusSummaries.value[i])));
|
||||
listitem.appendChild(createCell(gCertIssuedInfos.value[i]));
|
||||
listitem.appendChild(createCell(gCertExpiresInfos.value[i]));
|
||||
}
|
||||
|
||||
gListBox.appendChild(listitem);
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectionChange(event)
|
||||
{
|
||||
gViewButton.disabled = !(gListBox.selectedItems.length == 1 &&
|
||||
certForRow(gListBox.selectedIndex));
|
||||
}
|
||||
|
||||
function viewCertHelper(parent, cert) {
|
||||
var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs);
|
||||
cd.viewCert(parent, cert);
|
||||
}
|
||||
|
||||
function certForRow(aRowIndex) {
|
||||
return gCerts.value[aRowIndex];
|
||||
}
|
||||
|
||||
function viewSelectedCert()
|
||||
{
|
||||
if (!gViewButton.disabled)
|
||||
viewCertHelper(window, certForRow(gListBox.selectedIndex));
|
||||
}
|
||||
|
||||
function doHelpButton()
|
||||
{
|
||||
openHelp('compose_security', 'chrome://communicator/locale/help/suitehelp.rdf');
|
||||
}
|
||||
|
||||
function createCell(label)
|
||||
{
|
||||
var cell = document.createElement("listcell");
|
||||
cell.setAttribute("label", label)
|
||||
return cell;
|
||||
}
|
||||
68
mailnews/extensions/smime/content/msgCompSecurityInfo.xul
Normal file
68
mailnews/extensions/smime/content/msgCompSecurityInfo.xul
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSecurityInfo.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/msgCompSecurityInfo.dtd">
|
||||
|
||||
<dialog id="msgCompSecurityInfo" title="&title.label;"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
style="width: 50em;"
|
||||
persist="width height"
|
||||
buttons="accept"
|
||||
onload="onLoad();">
|
||||
|
||||
<script type="application/javascript" src="chrome://messenger-smime/content/msgCompSecurityInfo.js"/>
|
||||
|
||||
<stringbundle id="bundle_smime_comp_info" src="chrome://messenger-smime/locale/msgCompSecurityInfo.properties"/>
|
||||
|
||||
<description>&subject.plaintextWarning;</description>
|
||||
<separator class="thin"/>
|
||||
<description>&status.heading;</description>
|
||||
<grid>
|
||||
<columns>
|
||||
<column/>
|
||||
<column/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row>
|
||||
<label value="&status.signed;"/>
|
||||
<label id="signed"/>
|
||||
</row>
|
||||
<row>
|
||||
<label value="&status.encrypted;"/>
|
||||
<label id="encrypted"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
|
||||
<separator class="thin"/>
|
||||
<label value="&status.certificates;" control="infolist"/>
|
||||
|
||||
<listbox id="infolist" flex="1"
|
||||
onselect="onSelectionChange(event);">
|
||||
<listcols>
|
||||
<listcol flex="3" width="0"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<listcol flex="1" width="0"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<listcol flex="2" width="0"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<listcol flex="2" width="0"/>
|
||||
</listcols>
|
||||
<listhead>
|
||||
<listheader label="&tree.recipient;"/>
|
||||
<listheader label="&tree.status;"/>
|
||||
<listheader label="&tree.issuedDate;"/>
|
||||
<listheader label="&tree.expiresDate;"/>
|
||||
</listhead>
|
||||
</listbox>
|
||||
<hbox pack="start">
|
||||
<button id="viewCertButton" disabled="true"
|
||||
label="&view.label;" accesskey="&view.accesskey;"
|
||||
oncommand="viewSelectedCert();"/>
|
||||
</hbox>
|
||||
</dialog>
|
||||
264
mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js
Normal file
264
mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var gSignedUINode = null;
|
||||
var gEncryptedUINode = null;
|
||||
var gSMIMEContainer = null;
|
||||
var gStatusBar = null;
|
||||
var gSignedStatusPanel = null;
|
||||
var gEncryptedStatusPanel = null;
|
||||
|
||||
var gEncryptedURIService = null;
|
||||
var gMyLastEncryptedURI = null;
|
||||
|
||||
var gSMIMEBundle = null;
|
||||
// var gBrandBundle; -- defined in mailWindow.js
|
||||
|
||||
// manipulates some globals from msgReadSMIMEOverlay.js
|
||||
|
||||
var nsICMSMessageErrors = Components.interfaces.nsICMSMessageErrors;
|
||||
|
||||
/// Get the necko URL for the message URI.
|
||||
function neckoURLForMessageURI(aMessageURI)
|
||||
{
|
||||
let msgSvc = Components.classes["@mozilla.org/messenger;1"]
|
||||
.createInstance(Components.interfaces.nsIMessenger)
|
||||
.messageServiceFromURI(aMessageURI);
|
||||
let neckoURI = {};
|
||||
msgSvc.GetUrlForUri(aMessageURI, neckoURI, null);
|
||||
return neckoURI.value.spec;
|
||||
}
|
||||
|
||||
var smimeHeaderSink =
|
||||
{
|
||||
maxWantedNesting: function()
|
||||
{
|
||||
return 1;
|
||||
},
|
||||
|
||||
signedStatus: function(aNestingLevel, aSignatureStatus, aSignerCert)
|
||||
{
|
||||
if (aNestingLevel > 1) {
|
||||
// we are not interested
|
||||
return;
|
||||
}
|
||||
|
||||
gSignatureStatus = aSignatureStatus;
|
||||
gSignerCert = aSignerCert;
|
||||
|
||||
gSMIMEContainer.collapsed = false;
|
||||
gSignedUINode.collapsed = false;
|
||||
gSignedStatusPanel.collapsed = false;
|
||||
|
||||
switch (aSignatureStatus) {
|
||||
case nsICMSMessageErrors.SUCCESS:
|
||||
gSignedUINode.setAttribute("signed", "ok");
|
||||
gStatusBar.setAttribute("signed", "ok");
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED:
|
||||
gSignedUINode.setAttribute("signed", "unknown");
|
||||
gStatusBar.setAttribute("signed", "unknown");
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS:
|
||||
case nsICMSMessageErrors.VERIFY_HEADER_MISMATCH:
|
||||
gSignedUINode.setAttribute("signed", "mismatch");
|
||||
gStatusBar.setAttribute("signed", "mismatch");
|
||||
break;
|
||||
|
||||
default:
|
||||
gSignedUINode.setAttribute("signed", "notok");
|
||||
gStatusBar.setAttribute("signed", "notok");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
encryptionStatus: function(aNestingLevel, aEncryptionStatus, aRecipientCert)
|
||||
{
|
||||
if (aNestingLevel > 1) {
|
||||
// we are not interested
|
||||
return;
|
||||
}
|
||||
|
||||
gEncryptionStatus = aEncryptionStatus;
|
||||
gEncryptionCert = aRecipientCert;
|
||||
|
||||
gSMIMEContainer.collapsed = false;
|
||||
gEncryptedUINode.collapsed = false;
|
||||
gEncryptedStatusPanel.collapsed = false;
|
||||
|
||||
if (nsICMSMessageErrors.SUCCESS == aEncryptionStatus)
|
||||
{
|
||||
gEncryptedUINode.setAttribute("encrypted", "ok");
|
||||
gStatusBar.setAttribute("encrypted", "ok");
|
||||
}
|
||||
else
|
||||
{
|
||||
gEncryptedUINode.setAttribute("encrypted", "notok");
|
||||
gStatusBar.setAttribute("encrypted", "notok");
|
||||
}
|
||||
|
||||
if (gEncryptedURIService)
|
||||
{
|
||||
// Remember the message URI and the corresponding necko URI.
|
||||
gMyLastEncryptedURI = GetLoadedMessage();
|
||||
gEncryptedURIService.rememberEncrypted(gMyLastEncryptedURI);
|
||||
gEncryptedURIService.rememberEncrypted(
|
||||
neckoURLForMessageURI(gMyLastEncryptedURI));
|
||||
}
|
||||
|
||||
switch (aEncryptionStatus)
|
||||
{
|
||||
case nsICMSMessageErrors.SUCCESS:
|
||||
case nsICMSMessageErrors.ENCRYPT_INCOMPLETE:
|
||||
break;
|
||||
default:
|
||||
var brand = gBrandBundle.getString("brandShortName");
|
||||
var title = gSMIMEBundle.getString("CantDecryptTitle").replace(/%brand%/g, brand);
|
||||
var body = gSMIMEBundle.getString("CantDecryptBody").replace(/%brand%/g, brand);
|
||||
|
||||
// insert our message
|
||||
msgWindow.displayHTMLInMessagePane(title,
|
||||
"<html>\n" +
|
||||
"<body bgcolor=\"#fafaee\">\n" +
|
||||
"<center><br><br><br>\n" +
|
||||
"<table>\n" +
|
||||
"<tr><td>\n" +
|
||||
"<center><strong><font size=\"+3\">\n" +
|
||||
title+"</font></center><br>\n" +
|
||||
body+"\n" +
|
||||
"</td></tr></table></center></body></html>", false);
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface : function(iid)
|
||||
{
|
||||
if (iid.equals(Components.interfaces.nsIMsgSMIMEHeaderSink) || iid.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_NOINTERFACE;
|
||||
}
|
||||
};
|
||||
|
||||
function forgetEncryptedURI()
|
||||
{
|
||||
if (gMyLastEncryptedURI && gEncryptedURIService)
|
||||
{
|
||||
gEncryptedURIService.forgetEncrypted(gMyLastEncryptedURI);
|
||||
gEncryptedURIService.forgetEncrypted(
|
||||
neckoURLForMessageURI(gMyLastEncryptedURI));
|
||||
gMyLastEncryptedURI = null;
|
||||
}
|
||||
}
|
||||
|
||||
function onSMIMEStartHeaders()
|
||||
{
|
||||
gEncryptionStatus = -1;
|
||||
gSignatureStatus = -1;
|
||||
|
||||
gSignerCert = null;
|
||||
gEncryptionCert = null;
|
||||
|
||||
gSMIMEContainer.collapsed = true;
|
||||
|
||||
gSignedUINode.collapsed = true;
|
||||
gSignedUINode.removeAttribute("signed");
|
||||
gSignedStatusPanel.collapsed = true;
|
||||
gStatusBar.removeAttribute("signed");
|
||||
|
||||
gEncryptedUINode.collapsed = true;
|
||||
gEncryptedUINode.removeAttribute("encrypted");
|
||||
gEncryptedStatusPanel.collapsed = true;
|
||||
gStatusBar.removeAttribute("encrypted");
|
||||
|
||||
forgetEncryptedURI();
|
||||
}
|
||||
|
||||
function onSMIMEEndHeaders()
|
||||
{}
|
||||
|
||||
function onSmartCardChange()
|
||||
{
|
||||
// only reload encrypted windows
|
||||
if (gMyLastEncryptedURI && gEncryptionStatus != -1)
|
||||
ReloadMessage();
|
||||
}
|
||||
|
||||
function msgHdrViewSMIMEOnLoad(event)
|
||||
{
|
||||
window.crypto.enableSmartCardEvents = true;
|
||||
document.addEventListener("smartcard-insert", onSmartCardChange, false);
|
||||
document.addEventListener("smartcard-remove", onSmartCardChange, false);
|
||||
if (!gSMIMEBundle)
|
||||
gSMIMEBundle = document.getElementById("bundle_read_smime");
|
||||
|
||||
// we want to register our security header sink as an opaque nsISupports
|
||||
// on the msgHdrSink used by mail.....
|
||||
msgWindow.msgHeaderSink.securityInfo = smimeHeaderSink;
|
||||
|
||||
gSignedUINode = document.getElementById('signedHdrIcon');
|
||||
gEncryptedUINode = document.getElementById('encryptedHdrIcon');
|
||||
gSMIMEContainer = document.getElementById('smimeBox');
|
||||
gStatusBar = document.getElementById('status-bar');
|
||||
gSignedStatusPanel = document.getElementById('signed-status');
|
||||
gEncryptedStatusPanel = document.getElementById('encrypted-status');
|
||||
|
||||
// add ourself to the list of message display listeners so we get notified when we are about to display a
|
||||
// message.
|
||||
var listener = {};
|
||||
listener.onStartHeaders = onSMIMEStartHeaders;
|
||||
listener.onEndHeaders = onSMIMEEndHeaders;
|
||||
gMessageListeners.push(listener);
|
||||
|
||||
gEncryptedURIService =
|
||||
Components.classes["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"]
|
||||
.getService(Components.interfaces.nsIEncryptedSMIMEURIsService);
|
||||
}
|
||||
|
||||
function msgHdrViewSMIMEOnUnload(event)
|
||||
{
|
||||
window.crypto.enableSmartCardEvents = false;
|
||||
document.removeEventListener("smartcard-insert", onSmartCardChange, false);
|
||||
document.removeEventListener("smartcard-remove", onSmartCardChange, false);
|
||||
forgetEncryptedURI();
|
||||
removeEventListener("messagepane-loaded", msgHdrViewSMIMEOnLoad, true);
|
||||
removeEventListener("messagepane-unloaded", msgHdrViewSMIMEOnUnload, true);
|
||||
removeEventListener("messagepane-hide", msgHdrViewSMIMEOnMessagePaneHide, true);
|
||||
removeEventListener("messagepane-unhide", msgHdrViewSMIMEOnMessagePaneUnhide, true);
|
||||
}
|
||||
|
||||
function msgHdrViewSMIMEOnMessagePaneHide()
|
||||
{
|
||||
gSMIMEContainer.collapsed = true;
|
||||
gSignedUINode.collapsed = true;
|
||||
gSignedStatusPanel.collapsed = true;
|
||||
gEncryptedUINode.collapsed = true;
|
||||
gEncryptedStatusPanel.collapsed = true;
|
||||
}
|
||||
|
||||
function msgHdrViewSMIMEOnMessagePaneUnhide()
|
||||
{
|
||||
if (gEncryptionStatus != -1 || gSignatureStatus != -1)
|
||||
{
|
||||
gSMIMEContainer.collapsed = false;
|
||||
|
||||
if (gSignatureStatus != -1)
|
||||
{
|
||||
gSignedUINode.collapsed = false;
|
||||
gSignedStatusPanel.collapsed = false;
|
||||
}
|
||||
|
||||
if (gEncryptionStatus != -1)
|
||||
{
|
||||
gEncryptedUINode.collapsed = false;
|
||||
gEncryptedStatusPanel.collapsed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('messagepane-loaded', msgHdrViewSMIMEOnLoad, true);
|
||||
addEventListener('messagepane-unloaded', msgHdrViewSMIMEOnUnload, true);
|
||||
addEventListener('messagepane-hide', msgHdrViewSMIMEOnMessagePaneHide, true);
|
||||
addEventListener('messagepane-unhide', msgHdrViewSMIMEOnMessagePaneUnhide, true);
|
||||
29
mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul
Normal file
29
mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/smime/msgHdrViewSMIMEOverlay.css" type="text/css"?>
|
||||
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript" src="chrome://messenger-smime/content/msgHdrViewSMIMEOverlay.js"/>
|
||||
<!-- These stringbundles are already defined in msgReadSMIMEOverlay.xul!
|
||||
<stringbundleset id="stringbundleset">
|
||||
<stringbundle id="bundle_read_smime" src="chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"/>
|
||||
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
|
||||
</stringbundleset>
|
||||
-->
|
||||
|
||||
<hbox id="expandedHeaderView">
|
||||
<vbox id="smimeBox" insertafter="expandedHeaders" collapsed="true">
|
||||
<spacer flex="1"/>
|
||||
<image id="signedHdrIcon"
|
||||
onclick="showMessageReadSecurityInfo();" collapsed="true"/>
|
||||
<image id="encryptedHdrIcon"
|
||||
onclick="showMessageReadSecurityInfo();" collapsed="true"/>
|
||||
<spacer flex="1"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</overlay>
|
||||
|
||||
102
mailnews/extensions/smime/content/msgReadSMIMEOverlay.js
Normal file
102
mailnews/extensions/smime/content/msgReadSMIMEOverlay.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var gEncryptionStatus = -1;
|
||||
var gSignatureStatus = -1;
|
||||
var gSignerCert = null;
|
||||
var gEncryptionCert = null;
|
||||
|
||||
addEventListener("load", smimeReadOnLoad, {capture: false, once: true});
|
||||
|
||||
function smimeReadOnLoad()
|
||||
{
|
||||
top.controllers.appendController(SecurityController);
|
||||
|
||||
addEventListener("unload", smimeReadOnUnload, {capture: false, once: true});
|
||||
}
|
||||
|
||||
function smimeReadOnUnload()
|
||||
{
|
||||
top.controllers.removeController(SecurityController);
|
||||
}
|
||||
|
||||
function showImapSignatureUnknown()
|
||||
{
|
||||
let readSmimeBundle = document.getElementById("bundle_read_smime");
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
if (!readSmimeBundle || !brandBundle)
|
||||
return;
|
||||
|
||||
if (Services.prompt.confirm(window, brandBundle.getString("brandShortName"),
|
||||
readSmimeBundle.getString("ImapOnDemand")))
|
||||
{
|
||||
gDBView.reloadMessageWithAllParts();
|
||||
}
|
||||
}
|
||||
|
||||
function showMessageReadSecurityInfo()
|
||||
{
|
||||
let gSignedUINode = document.getElementById("signedHdrIcon");
|
||||
if (gSignedUINode && gSignedUINode.getAttribute("signed") == "unknown")
|
||||
{
|
||||
showImapSignatureUnknown();
|
||||
return;
|
||||
}
|
||||
|
||||
let params = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
|
||||
.createInstance(Components.interfaces.nsIDialogParamBlock);
|
||||
params.objects = Components.classes["@mozilla.org/array;1"]
|
||||
.createInstance(Components.interfaces.nsIMutableArray);
|
||||
// Append even if null... the receiver must handle that.
|
||||
params.objects.appendElement(gSignerCert, false);
|
||||
params.objects.appendElement(gEncryptionCert, false);
|
||||
|
||||
// int array starts with index 0, but that is used for window exit status
|
||||
params.SetInt(1, gSignatureStatus);
|
||||
params.SetInt(2, gEncryptionStatus);
|
||||
|
||||
window.openDialog("chrome://messenger-smime/content/msgReadSecurityInfo.xul",
|
||||
"", "chrome,resizable,modal,dialog,centerscreen", params);
|
||||
}
|
||||
|
||||
var SecurityController =
|
||||
{
|
||||
supportsCommand: function(command)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case "cmd_viewSecurityStatus":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
isCommandEnabled: function(command)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case "cmd_viewSecurityStatus":
|
||||
if (document.documentElement.getAttribute('windowtype') == "mail:messageWindow")
|
||||
return GetNumSelectedMessages() > 0;
|
||||
|
||||
if (GetNumSelectedMessages() > 0 && gDBView)
|
||||
{
|
||||
let enabled = {value: false};
|
||||
let checkStatus = {};
|
||||
gDBView.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody,
|
||||
enabled, checkStatus);
|
||||
return enabled.value;
|
||||
}
|
||||
// else: fall through.
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
34
mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul
Normal file
34
mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/smime/msgReadSMIMEOverlay.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE overlay SYSTEM "chrome://messenger-smime/locale/msgReadSMIMEOverlay.dtd">
|
||||
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript" src="chrome://messenger-smime/content/msgReadSMIMEOverlay.js"/>
|
||||
|
||||
<commandset id="mailViewMenuItems">
|
||||
<command id="cmd_viewSecurityStatus" oncommand="showMessageReadSecurityInfo();" disabled="true"/>
|
||||
</commandset>
|
||||
|
||||
<menupopup id="menu_View_Popup">
|
||||
<menuitem insertafter="pageSourceMenuItem" label="&menu_securityStatus.label;"
|
||||
accesskey="&menu_securityStatus.accesskey;" command="cmd_viewSecurityStatus"/>
|
||||
</menupopup>
|
||||
|
||||
<statusbar id="status-bar">
|
||||
<statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic"
|
||||
id="signed-status" collapsed="true" oncommand="showMessageReadSecurityInfo();"/>
|
||||
<statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic"
|
||||
id="encrypted-status" collapsed="true" oncommand="showMessageReadSecurityInfo();"/>
|
||||
<stringbundle id="bundle_read_smime" src="chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"/>
|
||||
<!-- This stringbundle is already defined on top window level!
|
||||
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
|
||||
-->
|
||||
</statusbar>
|
||||
|
||||
</overlay>
|
||||
232
mailnews/extensions/smime/content/msgReadSecurityInfo.js
Normal file
232
mailnews/extensions/smime/content/msgReadSecurityInfo.js
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
|
||||
var nsIX509Cert = Components.interfaces.nsIX509Cert;
|
||||
var nsICMSMessageErrors = Components.interfaces.nsICMSMessageErrors;
|
||||
var nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
|
||||
var nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1"
|
||||
|
||||
var gSignerCert = null;
|
||||
var gEncryptionCert = null;
|
||||
|
||||
var gSignatureStatus = -1;
|
||||
var gEncryptionStatus = -1;
|
||||
|
||||
function setText(id, value) {
|
||||
var element = document.getElementById(id);
|
||||
if (!element)
|
||||
return;
|
||||
if (element.hasChildNodes())
|
||||
element.firstChild.remove();
|
||||
var textNode = document.createTextNode(value);
|
||||
element.appendChild(textNode);
|
||||
}
|
||||
|
||||
function onLoad()
|
||||
{
|
||||
var paramBlock = window.arguments[0].QueryInterface(nsIDialogParamBlock);
|
||||
paramBlock.objects.QueryInterface(Components.interfaces.nsIMutableArray);
|
||||
try {
|
||||
gSignerCert = paramBlock.objects.queryElementAt(0, nsIX509Cert);
|
||||
} catch(e) { } // maybe null
|
||||
try {
|
||||
gEncryptionCert = paramBlock.objects.queryElementAt(1, nsIX509Cert);
|
||||
} catch(e) { } // maybe null
|
||||
|
||||
gSignatureStatus = paramBlock.GetInt(1);
|
||||
gEncryptionStatus = paramBlock.GetInt(2);
|
||||
|
||||
var bundle = document.getElementById("bundle_smime_read_info");
|
||||
|
||||
if (bundle) {
|
||||
var sigInfoLabel = null;
|
||||
var sigInfoHeader = null;
|
||||
var sigInfo = null;
|
||||
var sigInfo_clueless = false;
|
||||
|
||||
switch (gSignatureStatus) {
|
||||
case -1:
|
||||
case nsICMSMessageErrors.VERIFY_NOT_SIGNED:
|
||||
sigInfoLabel = "SINoneLabel";
|
||||
sigInfo = "SINone";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.SUCCESS:
|
||||
sigInfoLabel = "SIValidLabel";
|
||||
sigInfo = "SIValid";
|
||||
break;
|
||||
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_BAD_SIGNATURE:
|
||||
case nsICMSMessageErrors.VERIFY_DIGEST_MISMATCH:
|
||||
sigInfoLabel = "SIInvalidLabel";
|
||||
sigInfoHeader = "SIInvalidHeader";
|
||||
sigInfo = "SIContentAltered";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_UNKNOWN_ALGO:
|
||||
case nsICMSMessageErrors.VERIFY_UNSUPPORTED_ALGO:
|
||||
sigInfoLabel = "SIInvalidLabel";
|
||||
sigInfoHeader = "SIInvalidHeader";
|
||||
sigInfo = "SIInvalidCipher";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_HEADER_MISMATCH:
|
||||
sigInfoLabel = "SIPartiallyValidLabel";
|
||||
sigInfoHeader = "SIPartiallyValidHeader";
|
||||
sigInfo = "SIHeaderMismatch";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS:
|
||||
sigInfoLabel = "SIPartiallyValidLabel";
|
||||
sigInfoHeader = "SIPartiallyValidHeader";
|
||||
sigInfo = "SICertWithoutAddress";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_UNTRUSTED:
|
||||
sigInfoLabel = "SIInvalidLabel";
|
||||
sigInfoHeader = "SIInvalidHeader";
|
||||
sigInfo = "SIUntrustedCA";
|
||||
// XXX Need to extend to communicate better errors
|
||||
// might also be:
|
||||
// SIExpired SIRevoked SINotYetValid SIUnknownCA SIExpiredCA SIRevokedCA SINotYetValidCA
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED:
|
||||
case nsICMSMessageErrors.GENERAL_ERROR:
|
||||
case nsICMSMessageErrors.VERIFY_NO_CONTENT_INFO:
|
||||
case nsICMSMessageErrors.VERIFY_BAD_DIGEST:
|
||||
case nsICMSMessageErrors.VERIFY_NOCERT:
|
||||
case nsICMSMessageErrors.VERIFY_ERROR_UNVERIFIED:
|
||||
case nsICMSMessageErrors.VERIFY_ERROR_PROCESSING:
|
||||
case nsICMSMessageErrors.VERIFY_MALFORMED_SIGNATURE:
|
||||
sigInfoLabel = "SIInvalidLabel";
|
||||
sigInfoHeader = "SIInvalidHeader";
|
||||
sigInfo_clueless = true;
|
||||
break;
|
||||
default:
|
||||
Components.utils.reportError("Unexpected gSignatureStatus: " +
|
||||
gSignatureStatus);
|
||||
}
|
||||
|
||||
document.getElementById("signatureLabel").value =
|
||||
bundle.getString(sigInfoLabel);
|
||||
|
||||
var label;
|
||||
if (sigInfoHeader) {
|
||||
label = document.getElementById("signatureHeader");
|
||||
label.collapsed = false;
|
||||
label.value = bundle.getString(sigInfoHeader);
|
||||
}
|
||||
|
||||
var str;
|
||||
if (sigInfo) {
|
||||
str = bundle.getString(sigInfo);
|
||||
}
|
||||
else if (sigInfo_clueless) {
|
||||
str = bundle.getString("SIClueless") + " (" + gSignatureStatus + ")";
|
||||
}
|
||||
setText("signatureExplanation", str);
|
||||
|
||||
var encInfoLabel = null;
|
||||
var encInfoHeader = null;
|
||||
var encInfo = null;
|
||||
var encInfo_clueless = false;
|
||||
|
||||
switch (gEncryptionStatus) {
|
||||
case -1:
|
||||
encInfoLabel = "EINoneLabel2";
|
||||
encInfo = "EINone";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.SUCCESS:
|
||||
encInfoLabel = "EIValidLabel";
|
||||
encInfo = "EIValid";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.ENCRYPT_INCOMPLETE:
|
||||
encInfoLabel = "EIInvalidLabel";
|
||||
encInfo = "EIContentAltered";
|
||||
break;
|
||||
|
||||
case nsICMSMessageErrors.GENERAL_ERROR:
|
||||
encInfoLabel = "EIInvalidLabel";
|
||||
encInfoHeader = "EIInvalidHeader";
|
||||
encInfo_clueless = 1;
|
||||
break;
|
||||
default:
|
||||
Components.utils.reportError("Unexpected gEncryptionStatus: " +
|
||||
gEncryptionStatus);
|
||||
}
|
||||
|
||||
document.getElementById("encryptionLabel").value =
|
||||
bundle.getString(encInfoLabel);
|
||||
|
||||
if (encInfoHeader) {
|
||||
label = document.getElementById("encryptionHeader");
|
||||
label.collapsed = false;
|
||||
label.value = bundle.getString(encInfoHeader);
|
||||
}
|
||||
|
||||
if (encInfo) {
|
||||
str = bundle.getString(encInfo);
|
||||
}
|
||||
else if (encInfo_clueless) {
|
||||
str = bundle.getString("EIClueless");
|
||||
}
|
||||
setText("encryptionExplanation", str);
|
||||
}
|
||||
|
||||
if (gSignerCert) {
|
||||
document.getElementById("signatureCert").collapsed = false;
|
||||
if (gSignerCert.subjectName) {
|
||||
document.getElementById("signedBy").value = gSignerCert.commonName;
|
||||
}
|
||||
if (gSignerCert.emailAddress) {
|
||||
document.getElementById("signerEmail").value = gSignerCert.emailAddress;
|
||||
}
|
||||
if (gSignerCert.issuerName) {
|
||||
document.getElementById("sigCertIssuedBy").value = gSignerCert.issuerCommonName;
|
||||
}
|
||||
}
|
||||
|
||||
if (gEncryptionCert) {
|
||||
document.getElementById("encryptionCert").collapsed = false;
|
||||
if (gEncryptionCert.subjectName) {
|
||||
document.getElementById("encryptedFor").value = gEncryptionCert.commonName;
|
||||
}
|
||||
if (gEncryptionCert.emailAddress) {
|
||||
document.getElementById("recipientEmail").value = gEncryptionCert.emailAddress;
|
||||
}
|
||||
if (gEncryptionCert.issuerName) {
|
||||
document.getElementById("encCertIssuedBy").value = gEncryptionCert.issuerCommonName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function viewCertHelper(parent, cert) {
|
||||
var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs);
|
||||
cd.viewCert(parent, cert);
|
||||
}
|
||||
|
||||
function viewSignatureCert()
|
||||
{
|
||||
if (gSignerCert) {
|
||||
viewCertHelper(window, gSignerCert);
|
||||
}
|
||||
}
|
||||
|
||||
function viewEncryptionCert()
|
||||
{
|
||||
if (gEncryptionCert) {
|
||||
viewCertHelper(window, gEncryptionCert);
|
||||
}
|
||||
}
|
||||
|
||||
function doHelpButton()
|
||||
{
|
||||
openHelp('received_security');
|
||||
}
|
||||
68
mailnews/extensions/smime/content/msgReadSecurityInfo.xul
Normal file
68
mailnews/extensions/smime/content/msgReadSecurityInfo.xul
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://messenger/skin/smime/msgReadSecurityInfo.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/msgReadSecurityInfo.dtd">
|
||||
|
||||
<dialog id="msgReadSecurityInfo" title="&status.label;"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
style="width: 40em;"
|
||||
buttons="accept"
|
||||
onload="onLoad();">
|
||||
|
||||
<script type="application/javascript" src="chrome://messenger-smime/content/msgReadSecurityInfo.js"/>
|
||||
|
||||
<stringbundle id="bundle_smime_read_info" src="chrome://messenger-smime/locale/msgSecurityInfo.properties"/>
|
||||
|
||||
<vbox flex="1">
|
||||
<label id="signatureLabel"/>
|
||||
<label id="signatureHeader" collapsed="true"/>
|
||||
<description id="signatureExplanation"/>
|
||||
<vbox id="signatureCert" collapsed="true">
|
||||
<hbox>
|
||||
<label id="signedByLabel">&signer.name;</label>
|
||||
<description id="signedBy"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label id="signerEmailLabel">&email.address;</label>
|
||||
<description id="signerEmail"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label id="sigCertIssuedByLabel">&issuer.name;</label>
|
||||
<description id="sigCertIssuedBy"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<button id="signatureCertView" label="&signatureCert.label;"
|
||||
oncommand="viewSignatureCert()"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<separator/>
|
||||
|
||||
<label id="encryptionLabel"/>
|
||||
<label id="encryptionHeader" collapsed="true"/>
|
||||
<description id="encryptionExplanation"/>
|
||||
<vbox id="encryptionCert" collapsed="true">
|
||||
<hbox>
|
||||
<label id="encryptedForLabel">&recipient.name;</label>
|
||||
<description id="encryptedFor"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label id="recipientEmailLabel">&email.address;</label>
|
||||
<description id="recipientEmail"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label id="encCertIssuedByLabel">&issuer.name;</label>
|
||||
<description id="encCertIssuedBy"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<button id="encryptionCertView" label="&encryptionCert.label;"
|
||||
oncommand="viewEncryptionCert()"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</dialog>
|
||||
14
mailnews/extensions/smime/content/smime.js
Normal file
14
mailnews/extensions/smime/content/smime.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
Add any default pref values we want for smime
|
||||
*/
|
||||
|
||||
pref("mail.identity.default.encryption_cert_name","");
|
||||
pref("mail.identity.default.encryptionpolicy", 0);
|
||||
pref("mail.identity.default.signing_cert_name", "");
|
||||
pref("mail.identity.default.sign_mail", false);
|
||||
|
||||
|
||||
30
mailnews/extensions/smime/jar.mn
Normal file
30
mailnews/extensions/smime/jar.mn
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifdef MOZ_SUITE
|
||||
messenger.jar:
|
||||
% content messenger-smime %content/messenger-smime/
|
||||
% overlay chrome://messenger/content/messengercompose/messengercompose.xul chrome://messenger-smime/content/msgCompSMIMEOverlay.xul
|
||||
% overlay chrome://messenger/content/msgHdrViewOverlay.xul chrome://messenger-smime/content/msgHdrViewSMIMEOverlay.xul
|
||||
% overlay chrome://messenger/content/mailWindowOverlay.xul chrome://messenger-smime/content/msgReadSMIMEOverlay.xul
|
||||
% overlay chrome://messenger/content/am-identity-edit.xul chrome://messenger/content/am-smimeIdentityEditOverlay.xul
|
||||
content/messenger/am-smime.xul (content/am-smime.xul)
|
||||
content/messenger/am-smime.js (content/am-smime.js)
|
||||
content/messenger/am-smimeIdentityEditOverlay.xul (content/am-smimeIdentityEditOverlay.xul)
|
||||
content/messenger/am-smimeOverlay.xul (content/am-smimeOverlay.xul)
|
||||
content/messenger/certpicker.js (content/certpicker.js)
|
||||
content/messenger/certpicker.xul (content/certpicker.xul)
|
||||
content/messenger-smime/msgCompSMIMEOverlay.js (content/msgCompSMIMEOverlay.js)
|
||||
content/messenger-smime/msgCompSMIMEOverlay.xul (content/msgCompSMIMEOverlay.xul)
|
||||
content/messenger-smime/msgReadSMIMEOverlay.js (content/msgReadSMIMEOverlay.js)
|
||||
content/messenger-smime/msgReadSMIMEOverlay.xul (content/msgReadSMIMEOverlay.xul)
|
||||
content/messenger-smime/msgHdrViewSMIMEOverlay.xul (content/msgHdrViewSMIMEOverlay.xul)
|
||||
content/messenger-smime/msgHdrViewSMIMEOverlay.js (content/msgHdrViewSMIMEOverlay.js)
|
||||
content/messenger-smime/msgCompSecurityInfo.xul (content/msgCompSecurityInfo.xul)
|
||||
content/messenger-smime/msgCompSecurityInfo.js (content/msgCompSecurityInfo.js)
|
||||
content/messenger-smime/msgReadSecurityInfo.xul (content/msgReadSecurityInfo.xul)
|
||||
content/messenger-smime/msgReadSecurityInfo.js (content/msgReadSecurityInfo.js)
|
||||
content/messenger-smime/certFetchingStatus.xul (content/certFetchingStatus.xul)
|
||||
content/messenger-smime/certFetchingStatus.js (content/certFetchingStatus.js)
|
||||
#endif
|
||||
15
mailnews/extensions/smime/moz.build
Normal file
15
mailnews/extensions/smime/moz.build
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += [
|
||||
'public',
|
||||
'src',
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
JS_PREFERENCE_FILES += [
|
||||
'content/smime.js',
|
||||
]
|
||||
15
mailnews/extensions/smime/public/moz.build
Normal file
15
mailnews/extensions/smime/public/moz.build
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsICertPickDialogs.idl',
|
||||
'nsIEncryptedSMIMEURIsSrvc.idl',
|
||||
'nsIMsgSMIMECompFields.idl',
|
||||
'nsIMsgSMIMEHeaderSink.idl',
|
||||
'nsISMimeJSHelper.idl',
|
||||
'nsIUserCertPicker.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'msgsmime'
|
||||
30
mailnews/extensions/smime/public/nsICertPickDialogs.idl
Normal file
30
mailnews/extensions/smime/public/nsICertPickDialogs.idl
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIInterfaceRequestor;
|
||||
|
||||
/**
|
||||
* nsICertPickDialogs
|
||||
* Provides generic UI for choosing a certificate
|
||||
*/
|
||||
[scriptable, uuid(51d59b08-1dd2-11b2-ad4a-a51b92f8a184)]
|
||||
interface nsICertPickDialogs : nsISupports
|
||||
{
|
||||
/**
|
||||
* PickCertificate
|
||||
* General purpose certificate prompter
|
||||
*/
|
||||
void PickCertificate(in nsIInterfaceRequestor ctx,
|
||||
[array, size_is(count)] in wstring certNickList,
|
||||
[array, size_is(count)] in wstring certDetailsList,
|
||||
in unsigned long count,
|
||||
inout long selectedIndex,
|
||||
out boolean canceled);
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_CERTPICKDIALOGS_CONTRACTID "@mozilla.org/nsCertPickDialogs;1"
|
||||
%}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* This is a private interface used exclusively by SMIME.
|
||||
It provides functionality to the JS UI code,
|
||||
that is only accessible from C++.
|
||||
*/
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(f86e55c9-530b-483f-91a7-10fb5b852488)]
|
||||
interface nsIEncryptedSMIMEURIsService : nsISupports
|
||||
{
|
||||
/// Remember that this URI is encrypted.
|
||||
void rememberEncrypted(in AUTF8String uri);
|
||||
|
||||
/// Forget that this URI is encrypted.
|
||||
void forgetEncrypted(in AUTF8String uri);
|
||||
|
||||
/// Check if this URI is encrypted.
|
||||
boolean isEncrypted(in AUTF8String uri);
|
||||
};
|
||||
18
mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl
Normal file
18
mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
/* This is a private interface used exclusively by SMIME. NO ONE outside of extensions/smime
|
||||
should have any knowledge nor should be referring to this interface.
|
||||
*/
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(338E91F9-5970-4f81-B771-0822A32B1161)]
|
||||
interface nsIMsgSMIMECompFields : nsISupports
|
||||
{
|
||||
attribute boolean signMessage;
|
||||
attribute boolean requireEncryptMessage;
|
||||
};
|
||||
23
mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl
Normal file
23
mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
/* This is a private interface used exclusively by SMIME. NO ONE outside of extensions/smime
|
||||
or the hard coded smime decryption files in mime/src should have any knowledge nor should
|
||||
be referring to this interface.
|
||||
*/
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIX509Cert;
|
||||
|
||||
[scriptable, uuid(25380FA1-E70C-4e82-B0BC-F31C2F41C470)]
|
||||
interface nsIMsgSMIMEHeaderSink : nsISupports
|
||||
{
|
||||
void signedStatus(in long aNestingLevel, in long aSignatureStatus, in nsIX509Cert aSignerCert);
|
||||
void encryptionStatus(in long aNestingLevel, in long aEncryptionStatus, in nsIX509Cert aReceipientCert);
|
||||
|
||||
long maxWantedNesting(); // 1 == only info on outermost nesting level wanted
|
||||
};
|
||||
73
mailnews/extensions/smime/public/nsISMimeJSHelper.idl
Normal file
73
mailnews/extensions/smime/public/nsISMimeJSHelper.idl
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* This is a private interface used exclusively by SMIME.
|
||||
It provides functionality to the JS UI code,
|
||||
that is only accessible from C++.
|
||||
*/
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIMsgCompFields;
|
||||
interface nsIX509Cert;
|
||||
|
||||
[scriptable, uuid(a54e3c8f-a000-4901-898f-fafb297b1546)]
|
||||
interface nsISMimeJSHelper : nsISupports
|
||||
{
|
||||
/**
|
||||
* Obtains detailed information about the certificate availability
|
||||
* status of email recipients.
|
||||
*
|
||||
* @param compFields - Attributes of the composed message
|
||||
*
|
||||
* @param count - The number of entries in returned arrays
|
||||
*
|
||||
* @param emailAddresses - The list of all recipient email addresses
|
||||
*
|
||||
* @param certVerification - The verification/validity status of recipient certs
|
||||
*
|
||||
* @param certIssuedInfos - If a recipient cert was found, when has it been issued?
|
||||
*
|
||||
* @param certExpiredInfos - If a recipient cert was found, when will it expire?
|
||||
*
|
||||
* @param certs - The recipient certificates, which can contain null for not found
|
||||
*
|
||||
* @param canEncrypt - whether valid certificates have been found for all recipients
|
||||
*
|
||||
* @exception NS_ERROR_FAILURE - unexptected failure
|
||||
*
|
||||
* @exception NS_ERROR_OUT_OF_MEMORY - could not create the out list
|
||||
*
|
||||
* @exception NS_ERROR_INVALID_ARG
|
||||
*/
|
||||
void getRecipientCertsInfo(in nsIMsgCompFields compFields,
|
||||
out unsigned long count,
|
||||
[array, size_is(count)] out wstring emailAddresses,
|
||||
[array, size_is(count)] out long certVerification,
|
||||
[array, size_is(count)] out wstring certIssuedInfos,
|
||||
[array, size_is(count)] out wstring certExpiresInfos,
|
||||
[array, size_is(count)] out nsIX509Cert certs,
|
||||
out boolean canEncrypt);
|
||||
|
||||
/**
|
||||
* Obtains a list of email addresses where valid email recipient certificates
|
||||
* are not yet available.
|
||||
*
|
||||
* @param compFields - Attributes of the composed message
|
||||
*
|
||||
* @param count - The number of returned email addresses
|
||||
*
|
||||
* @param emailAddresses - The list of email addresses without valid certs
|
||||
*
|
||||
* @exception NS_ERROR_FAILURE - unexptected failure
|
||||
*
|
||||
* @exception NS_ERROR_OUT_OF_MEMORY - could not create the out list
|
||||
*
|
||||
* @exception NS_ERROR_INVALID_ARG
|
||||
*/
|
||||
void getNoCertAddresses(in nsIMsgCompFields compFields,
|
||||
out unsigned long count,
|
||||
[array, size_is(count)] out wstring emailAddresses);
|
||||
};
|
||||
28
mailnews/extensions/smime/public/nsIUserCertPicker.idl
Normal file
28
mailnews/extensions/smime/public/nsIUserCertPicker.idl
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIX509Cert;
|
||||
interface nsIInterfaceRequestor;
|
||||
|
||||
[scriptable, uuid(92396323-23f2-49e0-bf98-a25a725231ab)]
|
||||
interface nsIUserCertPicker : nsISupports {
|
||||
nsIX509Cert pickByUsage(in nsIInterfaceRequestor ctx,
|
||||
in wstring selectedNickname,
|
||||
in long certUsage, // as defined by NSS enum SECCertUsage
|
||||
in boolean allowInvalid,
|
||||
in boolean allowDuplicateNicknames,
|
||||
in AString emailAddress, // optional - if non-empty,
|
||||
// skip certificates which
|
||||
// have at least one e-mail
|
||||
// address but do not
|
||||
// include this specific one
|
||||
out boolean canceled);
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_CERT_PICKER_CONTRACTID "@mozilla.org/user_cert_picker;1"
|
||||
%}
|
||||
23
mailnews/extensions/smime/src/moz.build
Normal file
23
mailnews/extensions/smime/src/moz.build
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SOURCES += [
|
||||
'nsCertPicker.cpp',
|
||||
'nsEncryptedSMIMEURIsService.cpp',
|
||||
'nsMsgComposeSecure.cpp',
|
||||
'nsSMimeJSHelper.cpp',
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'smime-service.js',
|
||||
'smime-service.manifest',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'mail'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/mozilla/security/manager/pki',
|
||||
'/mozilla/security/pkix/include'
|
||||
]
|
||||
471
mailnews/extensions/smime/src/nsCertPicker.cpp
Normal file
471
mailnews/extensions/smime/src/nsCertPicker.cpp
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsCertPicker.h"
|
||||
|
||||
#include "MainThreadUtils.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
#include "cert.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsICertPickDialogs.h"
|
||||
#include "nsIDOMWindow.h"
|
||||
#include "nsIDialogParamBlock.h"
|
||||
#include "nsIInterfaceRequestor.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsIX509CertValidity.h"
|
||||
#include "nsMemory.h"
|
||||
#include "nsMsgComposeSecure.h"
|
||||
#include "nsNSSCertificate.h"
|
||||
#include "nsNSSComponent.h"
|
||||
#include "nsNSSDialogHelper.h"
|
||||
#include "nsNSSHelper.h"
|
||||
#include "nsNSSShutDown.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "pkix/pkixtypes.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueCERTCertNicknames,
|
||||
CERTCertNicknames,
|
||||
CERT_FreeNicknames)
|
||||
|
||||
CERTCertNicknames*
|
||||
getNSSCertNicknamesFromCertList(const UniqueCERTCertList& certList)
|
||||
{
|
||||
static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
|
||||
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv));
|
||||
if (NS_FAILED(rv))
|
||||
return nullptr;
|
||||
|
||||
nsAutoString expiredString, notYetValidString;
|
||||
nsAutoString expiredStringLeadingSpace, notYetValidStringLeadingSpace;
|
||||
|
||||
nssComponent->GetPIPNSSBundleString("NicknameExpired", expiredString);
|
||||
nssComponent->GetPIPNSSBundleString("NicknameNotYetValid", notYetValidString);
|
||||
|
||||
expiredStringLeadingSpace.Append(' ');
|
||||
expiredStringLeadingSpace.Append(expiredString);
|
||||
|
||||
notYetValidStringLeadingSpace.Append(' ');
|
||||
notYetValidStringLeadingSpace.Append(notYetValidString);
|
||||
|
||||
NS_ConvertUTF16toUTF8 aUtf8ExpiredString(expiredStringLeadingSpace);
|
||||
NS_ConvertUTF16toUTF8 aUtf8NotYetValidString(notYetValidStringLeadingSpace);
|
||||
|
||||
return CERT_NicknameStringsFromCertList(certList.get(),
|
||||
const_cast<char*>(aUtf8ExpiredString.get()),
|
||||
const_cast<char*>(aUtf8NotYetValidString.get()));
|
||||
}
|
||||
|
||||
nsresult
|
||||
FormatUIStrings(nsIX509Cert* cert, const nsAutoString& nickname,
|
||||
nsAutoString& nickWithSerial, nsAutoString& details)
|
||||
{
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_ERROR("nsNSSCertificate::FormatUIStrings called off the main thread");
|
||||
return NS_ERROR_NOT_SAME_THREAD;
|
||||
}
|
||||
|
||||
RefPtr<nsMsgComposeSecure> mcs = new nsMsgComposeSecure;
|
||||
if (!mcs) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsAutoString info;
|
||||
nsAutoString temp1;
|
||||
|
||||
nickWithSerial.Append(nickname);
|
||||
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedFor", info))) {
|
||||
details.Append(info);
|
||||
details.Append(char16_t(' '));
|
||||
if (NS_SUCCEEDED(cert->GetSubjectName(temp1)) && !temp1.IsEmpty()) {
|
||||
details.Append(temp1);
|
||||
}
|
||||
details.Append(char16_t('\n'));
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(cert->GetSerialNumber(temp1)) && !temp1.IsEmpty()) {
|
||||
details.AppendLiteral(" ");
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpSerialNo", info))) {
|
||||
details.Append(info);
|
||||
details.AppendLiteral(": ");
|
||||
}
|
||||
details.Append(temp1);
|
||||
|
||||
nickWithSerial.AppendLiteral(" [");
|
||||
nickWithSerial.Append(temp1);
|
||||
nickWithSerial.Append(char16_t(']'));
|
||||
|
||||
details.Append(char16_t('\n'));
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIX509CertValidity> validity;
|
||||
nsresult rv = cert->GetValidity(getter_AddRefs(validity));
|
||||
if (NS_SUCCEEDED(rv) && validity) {
|
||||
details.AppendLiteral(" ");
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoValid", info))) {
|
||||
details.Append(info);
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(validity->GetNotBeforeLocalTime(temp1)) && !temp1.IsEmpty()) {
|
||||
details.Append(char16_t(' '));
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoFrom", info))) {
|
||||
details.Append(info);
|
||||
details.Append(char16_t(' '));
|
||||
}
|
||||
details.Append(temp1);
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(validity->GetNotAfterLocalTime(temp1)) && !temp1.IsEmpty()) {
|
||||
details.Append(char16_t(' '));
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoTo", info))) {
|
||||
details.Append(info);
|
||||
details.Append(char16_t(' '));
|
||||
}
|
||||
details.Append(temp1);
|
||||
}
|
||||
|
||||
details.Append(char16_t('\n'));
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(cert->GetKeyUsages(temp1)) && !temp1.IsEmpty()) {
|
||||
details.AppendLiteral(" ");
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpKeyUsage", info))) {
|
||||
details.Append(info);
|
||||
details.AppendLiteral(": ");
|
||||
}
|
||||
details.Append(temp1);
|
||||
details.Append(char16_t('\n'));
|
||||
}
|
||||
|
||||
UniqueCERTCertificate nssCert(cert->GetCert());
|
||||
if (!nssCert) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsAutoString firstEmail;
|
||||
const char* aWalkAddr;
|
||||
for (aWalkAddr = CERT_GetFirstEmailAddress(nssCert.get())
|
||||
;
|
||||
aWalkAddr
|
||||
;
|
||||
aWalkAddr = CERT_GetNextEmailAddress(nssCert.get(), aWalkAddr))
|
||||
{
|
||||
NS_ConvertUTF8toUTF16 email(aWalkAddr);
|
||||
if (email.IsEmpty())
|
||||
continue;
|
||||
|
||||
if (firstEmail.IsEmpty()) {
|
||||
// If the first email address from the subject DN is also present
|
||||
// in the subjectAltName extension, GetEmailAddresses() will return
|
||||
// it twice (as received from NSS). Remember the first address so that
|
||||
// we can filter out duplicates later on.
|
||||
firstEmail = email;
|
||||
|
||||
details.AppendLiteral(" ");
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoEmail", info))) {
|
||||
details.Append(info);
|
||||
details.AppendLiteral(": ");
|
||||
}
|
||||
details.Append(email);
|
||||
}
|
||||
else {
|
||||
// Append current address if it's different from the first one.
|
||||
if (!firstEmail.Equals(email)) {
|
||||
details.AppendLiteral(", ");
|
||||
details.Append(email);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!firstEmail.IsEmpty()) {
|
||||
// We got at least one email address, so we want a newline
|
||||
details.Append(char16_t('\n'));
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedBy", info))) {
|
||||
details.Append(info);
|
||||
details.Append(char16_t(' '));
|
||||
|
||||
if (NS_SUCCEEDED(cert->GetIssuerName(temp1)) && !temp1.IsEmpty()) {
|
||||
details.Append(temp1);
|
||||
}
|
||||
|
||||
details.Append(char16_t('\n'));
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoStoredIn", info))) {
|
||||
details.Append(info);
|
||||
details.Append(char16_t(' '));
|
||||
|
||||
if (NS_SUCCEEDED(cert->GetTokenName(temp1)) && !temp1.IsEmpty()) {
|
||||
details.Append(temp1);
|
||||
}
|
||||
}
|
||||
|
||||
// the above produces the following output:
|
||||
//
|
||||
// Issued to: $subjectName
|
||||
// Serial number: $serialNumber
|
||||
// Valid from: $starting_date to $expiration_date
|
||||
// Certificate Key usage: $usages
|
||||
// Email: $address(es)
|
||||
// Issued by: $issuerName
|
||||
// Stored in: $token
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsCertPicker, nsICertPickDialogs, nsIUserCertPicker)
|
||||
|
||||
nsCertPicker::nsCertPicker()
|
||||
{
|
||||
}
|
||||
|
||||
nsCertPicker::~nsCertPicker()
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
shutdown(ShutdownCalledFrom::Object);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsCertPicker::Init()
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsISupports> psm = do_GetService("@mozilla.org/psm;1", &rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCertPicker::PickCertificate(nsIInterfaceRequestor *ctx,
|
||||
const char16_t **certNickList,
|
||||
const char16_t **certDetailsList,
|
||||
uint32_t count,
|
||||
int32_t *selectedIndex,
|
||||
bool *canceled)
|
||||
{
|
||||
nsresult rv;
|
||||
uint32_t i;
|
||||
|
||||
*canceled = false;
|
||||
|
||||
nsCOMPtr<nsIDialogParamBlock> block =
|
||||
do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
|
||||
if (!block) return NS_ERROR_FAILURE;
|
||||
|
||||
block->SetNumberStrings(1+count*2);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
rv = block->SetString(i, certNickList[i]);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
rv = block->SetString(i+count, certDetailsList[i]);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
}
|
||||
|
||||
rv = block->SetInt(0, count);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = block->SetInt(1, *selectedIndex);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = nsNSSDialogHelper::openDialog(nullptr,
|
||||
"chrome://messenger/content/certpicker.xul",
|
||||
block);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
int32_t status;
|
||||
|
||||
rv = block->GetInt(0, &status);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
*canceled = (status == 0)?true:false;
|
||||
if (!*canceled) {
|
||||
rv = block->GetInt(1, selectedIndex);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsCertPicker::PickByUsage(nsIInterfaceRequestor *ctx,
|
||||
const char16_t *selectedNickname,
|
||||
int32_t certUsage,
|
||||
bool allowInvalid,
|
||||
bool allowDuplicateNicknames,
|
||||
const nsAString &emailAddress,
|
||||
bool *canceled,
|
||||
nsIX509Cert **_retval)
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
int32_t selectedIndex = -1;
|
||||
bool selectionFound = false;
|
||||
char16_t **certNicknameList = nullptr;
|
||||
char16_t **certDetailsList = nullptr;
|
||||
CERTCertListNode* node = nullptr;
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
{
|
||||
// Iterate over all certs. This assures that user is logged in to all hardware tokens.
|
||||
nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
|
||||
UniqueCERTCertList allcerts(PK11_ListCerts(PK11CertListUnique, ctx));
|
||||
}
|
||||
|
||||
/* find all user certs that are valid for the specified usage */
|
||||
/* note that we are allowing expired certs in this list */
|
||||
UniqueCERTCertList certList(
|
||||
CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(),
|
||||
(SECCertUsage)certUsage,
|
||||
!allowDuplicateNicknames,
|
||||
!allowInvalid,
|
||||
ctx));
|
||||
if (!certList) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
/* if a (non-empty) emailAddress argument is supplied to PickByUsage, */
|
||||
/* remove non-matching certificates from the candidate list */
|
||||
|
||||
if (!emailAddress.IsEmpty()) {
|
||||
node = CERT_LIST_HEAD(certList);
|
||||
while (!CERT_LIST_END(node, certList)) {
|
||||
/* if the cert has at least one e-mail address, check if suitable */
|
||||
if (CERT_GetFirstEmailAddress(node->cert)) {
|
||||
RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(node->cert));
|
||||
bool match = false;
|
||||
rv = tempCert->ContainsEmailAddress(emailAddress, &match);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (!match) {
|
||||
/* doesn't contain the specified address, so remove from the list */
|
||||
CERTCertListNode* freenode = node;
|
||||
node = CERT_LIST_NEXT(node);
|
||||
CERT_RemoveCertListNode(freenode);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
node = CERT_LIST_NEXT(node);
|
||||
}
|
||||
}
|
||||
|
||||
UniqueCERTCertNicknames nicknames(getNSSCertNicknamesFromCertList(certList));
|
||||
if (!nicknames) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
certNicknameList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames);
|
||||
certDetailsList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames);
|
||||
|
||||
if (!certNicknameList || !certDetailsList) {
|
||||
free(certNicknameList);
|
||||
free(certDetailsList);
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
int32_t CertsToUse;
|
||||
|
||||
for (CertsToUse = 0, node = CERT_LIST_HEAD(certList.get());
|
||||
!CERT_LIST_END(node, certList.get()) &&
|
||||
CertsToUse < nicknames->numnicknames;
|
||||
node = CERT_LIST_NEXT(node)
|
||||
)
|
||||
{
|
||||
RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(node->cert));
|
||||
|
||||
if (tempCert) {
|
||||
|
||||
nsAutoString i_nickname(NS_ConvertUTF8toUTF16(nicknames->nicknames[CertsToUse]));
|
||||
nsAutoString nickWithSerial;
|
||||
nsAutoString details;
|
||||
|
||||
if (!selectionFound) {
|
||||
/* for the case when selectedNickname refers to a bare nickname */
|
||||
if (i_nickname == nsDependentString(selectedNickname)) {
|
||||
selectedIndex = CertsToUse;
|
||||
selectionFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(FormatUIStrings(tempCert, i_nickname, nickWithSerial,
|
||||
details))) {
|
||||
certNicknameList[CertsToUse] = ToNewUnicode(nickWithSerial);
|
||||
certDetailsList[CertsToUse] = ToNewUnicode(details);
|
||||
if (!selectionFound) {
|
||||
/* for the case when selectedNickname refers to nickname + serial */
|
||||
if (nickWithSerial == nsDependentString(selectedNickname)) {
|
||||
selectedIndex = CertsToUse;
|
||||
selectionFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
certNicknameList[CertsToUse] = nullptr;
|
||||
certDetailsList[CertsToUse] = nullptr;
|
||||
}
|
||||
|
||||
++CertsToUse;
|
||||
}
|
||||
}
|
||||
|
||||
if (CertsToUse) {
|
||||
nsCOMPtr<nsICertPickDialogs> dialogs;
|
||||
rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsICertPickDialogs),
|
||||
NS_CERTPICKDIALOGS_CONTRACTID);
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Show the cert picker dialog and get the index of the selected cert.
|
||||
rv = dialogs->PickCertificate(ctx, (const char16_t**)certNicknameList,
|
||||
(const char16_t**)certDetailsList,
|
||||
CertsToUse, &selectedIndex, canceled);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < CertsToUse; ++i) {
|
||||
free(certNicknameList[i]);
|
||||
free(certDetailsList[i]);
|
||||
}
|
||||
free(certNicknameList);
|
||||
free(certDetailsList);
|
||||
|
||||
if (!CertsToUse) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv) && !*canceled) {
|
||||
for (i = 0, node = CERT_LIST_HEAD(certList);
|
||||
!CERT_LIST_END(node, certList);
|
||||
++i, node = CERT_LIST_NEXT(node)) {
|
||||
|
||||
if (i == selectedIndex) {
|
||||
RefPtr<nsNSSCertificate> cert = nsNSSCertificate::Create(node->cert);
|
||||
if (!cert) {
|
||||
rv = NS_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
cert.forget(_retval);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
36
mailnews/extensions/smime/src/nsCertPicker.h
Normal file
36
mailnews/extensions/smime/src/nsCertPicker.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsCertPicker_h
|
||||
#define nsCertPicker_h
|
||||
|
||||
#include "nsICertPickDialogs.h"
|
||||
#include "nsIUserCertPicker.h"
|
||||
#include "nsNSSShutDown.h"
|
||||
|
||||
#define NS_CERT_PICKER_CID \
|
||||
{ 0x735959a1, 0xaf01, 0x447e, { 0xb0, 0x2d, 0x56, 0xe9, 0x68, 0xfa, 0x52, 0xb4 } }
|
||||
|
||||
class nsCertPicker : public nsICertPickDialogs
|
||||
, public nsIUserCertPicker
|
||||
, public nsNSSShutDownObject
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICERTPICKDIALOGS
|
||||
NS_DECL_NSIUSERCERTPICKER
|
||||
|
||||
nsCertPicker();
|
||||
|
||||
// Nothing to actually release.
|
||||
virtual void virtualDestroyNSSReference() override {}
|
||||
|
||||
nsresult Init();
|
||||
|
||||
protected:
|
||||
virtual ~nsCertPicker();
|
||||
};
|
||||
|
||||
#endif // nsCertPicker_h
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue