зеркало из https://github.com/mozilla/pjs.git
Address book import (ldif)
This commit is contained in:
Родитель
b9c7867990
Коммит
5dd70c6efd
|
@ -62,7 +62,7 @@ function SetCommandEnabled(id, enabled)
|
|||
|
||||
function AbDelete()
|
||||
{
|
||||
dump("\AbDelete from XUL\n");
|
||||
// dump("\AbDelete from XUL\n");
|
||||
var tree = document.getElementById('resultsTree');
|
||||
if ( tree )
|
||||
{
|
||||
|
@ -78,10 +78,11 @@ function AbDelete()
|
|||
|
||||
function AbDeleteDirectory()
|
||||
{
|
||||
dump("\AbDeleteDirectory from XUL\n");
|
||||
// dump("\AbDeleteDirectory from XUL\n");
|
||||
var tree = document.getElementById('dirTree');
|
||||
|
||||
if ( tree && tree.selectedItems && tree.selectedItems.length )
|
||||
// if ( tree && tree.selectedItems && tree.selectedItems.length )
|
||||
if ( tree )
|
||||
top.addressbook.DeleteAddressBooks(tree.database, tree, tree.selectedItems);
|
||||
}
|
||||
|
||||
|
@ -135,3 +136,8 @@ function AbPrintAddressBook()
|
|||
dump("failed to print address book\n");
|
||||
}
|
||||
}
|
||||
|
||||
function AbImport()
|
||||
{
|
||||
addressbook.ImportAddressBook();
|
||||
}
|
||||
|
|
|
@ -93,11 +93,12 @@ Rights Reserved.
|
|||
<menuitem value="&undoCmd.label;" oncommand="AbEditUndo()"/>
|
||||
<menuitem value="&redoCmd.label;" oncommand="AbEditRedo()"/>
|
||||
<menuseparator/>
|
||||
<menuitem value="&cutCmd.label;" oncommand="AbEditCut();"/>
|
||||
<menuitem value="©Cmd.label;" oncommand="AbEditCopy();"/>
|
||||
<menuitem value="&pasteCmd.label;" oncommand="AbEditPaste();"/>
|
||||
<menuitem value="&cutCmd.label;" onaction="AbEditCut();"/>
|
||||
<menuitem value="©Cmd.label;" onaction="AbEditCopy();"/>
|
||||
<menuitem value="&pasteCmd.label;" onaction="AbEditPaste();"/>
|
||||
<menuitem value="&deleteCmd.label;" oncommand="AbDelete();"/>
|
||||
<menuitem value="&selectAllCmd.label;" oncommand="AbSelectAll();"/>
|
||||
<menuitem value="&deleteAbCmd.label;" oncommand="AbDeleteDirectory();"/>
|
||||
<menuitem value="&selectAllCmd.label;" onaction="AbSelectAll();"/>
|
||||
<menuseparator/>
|
||||
<menuitem value="&searchCmd.label;" oncommand="AbSearch();"/>
|
||||
<menuseparator/>
|
||||
|
|
|
@ -45,6 +45,7 @@ Rights Reserved.
|
|||
<!ENTITY copyCmd.label ".Copy">
|
||||
<!ENTITY pasteCmd.label ".Paste">
|
||||
<!ENTITY deleteCmd.label "Delete">
|
||||
<!ENTITY deleteAbCmd.label "Delete Address Book">
|
||||
<!ENTITY selectAllCmd.label ".Select All">
|
||||
<!ENTITY searchCmd.label ".Search...">
|
||||
<!ENTITY htmlDomainCmd.label ".HTML Domains...">
|
||||
|
|
|
@ -253,7 +253,7 @@ NS_IMETHODIMP nsAddrDatabase::RemoveListener(nsIAddrDBListener *listener)
|
|||
return NS_OK;
|
||||
}
|
||||
}
|
||||
return NS_COMFALSE;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsAddrDatabase::NotifyCardAttribChange(PRUint32 abCode, nsIAddrDBListener *instigator)
|
||||
|
@ -471,7 +471,7 @@ void nsAddrDatabase::UnixToNative(char*& ioPath)
|
|||
const char* src = ioPath;
|
||||
if (*src == '/') // * full path
|
||||
src++;
|
||||
else if (strchr(src, '/')) // * partial path, and not just a leaf name
|
||||
else if (PL_strchr(src, '/')) // * partial path, and not just a leaf name
|
||||
*dst++ = ':';
|
||||
strcpy(dst, src);
|
||||
|
||||
|
@ -492,7 +492,7 @@ void nsAddrDatabase::NativeToUnix(char*& ioPath)
|
|||
// anything
|
||||
//----------------------------------------------------------------------------------------
|
||||
{
|
||||
size_t len = strlen(ioPath);
|
||||
size_t len = PL_strlen(ioPath);
|
||||
char* result = new char[len + 2]; // ... but allow for the initial colon in a partial name
|
||||
if (result)
|
||||
{
|
||||
|
@ -500,9 +500,9 @@ void nsAddrDatabase::NativeToUnix(char*& ioPath)
|
|||
const char* src = ioPath;
|
||||
if (*src == ':') // * partial path, and not just a leaf name
|
||||
src++;
|
||||
else if (strchr(src, ':')) // * full path
|
||||
else if (PL_strchr(src, ':')) // * full path
|
||||
*dst++ = '/';
|
||||
strcpy(dst, src);
|
||||
PL_strcpy(dst, src);
|
||||
|
||||
while ( *dst != 0)
|
||||
{
|
||||
|
@ -2192,7 +2192,7 @@ NS_IMETHODIMP nsAddrDBEnumerator::CurrentItem(nsISupports **aItem)
|
|||
|
||||
NS_IMETHODIMP nsAddrDBEnumerator::IsDone(void)
|
||||
{
|
||||
return mDone ? NS_OK : NS_COMFALSE;
|
||||
return mDone ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include "nsAbRDFResource.h"
|
||||
#include "nsIAddrDatabase.h"
|
||||
|
||||
#include "xp_core.h"
|
||||
#include "plstr.h"
|
||||
#include "prmem.h"
|
||||
#include "prprf.h"
|
||||
|
||||
|
@ -332,7 +334,11 @@ protected:
|
|||
nsresult ParseTabFile(PRFileDesc* file);
|
||||
nsresult ParseLdifFile(PRFileDesc* file);
|
||||
void AddTabRowToDatabase();
|
||||
void AddLdifColToDatabase(nsIMdbRow* newRow);
|
||||
void AddLdifColToDatabase(nsIMdbRow* newRow, char* typeSlot, char* valueSlot);
|
||||
|
||||
nsresult GetLdifStringRecord(char* buf, PRInt32 len, PRInt32* stopPos);
|
||||
nsresult str_parse_line(char *line, char **type, char **value, int *vlen);
|
||||
char * str_getline( char **next );
|
||||
|
||||
public:
|
||||
AddressBookParser(nsIFileSpecWithUI* fileSpec);
|
||||
|
@ -609,6 +615,223 @@ void AddressBookParser::AddTabRowToDatabase()
|
|||
mDatabase->AddCardRowToDB(newRow);
|
||||
}
|
||||
|
||||
#define RIGHT2 0x03
|
||||
#define RIGHT4 0x0f
|
||||
#define CONTINUED_LINE_MARKER '\001'
|
||||
#define IS_SPACE(VAL) \
|
||||
(((((intn)(VAL)) & 0x7f) == ((intn)(VAL))) && isspace((intn)(VAL)) )
|
||||
|
||||
static unsigned char b642nib[0x80] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
|
||||
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
|
||||
0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
|
||||
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
||||
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||
0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
/*
|
||||
* str_parse_line - takes a line of the form "type:[:] value" and splits it
|
||||
* into components "type" and "value". if a double colon separates type from
|
||||
* value, then value is encoded in base 64, and parse_line un-decodes it
|
||||
* (in place) before returning.
|
||||
*/
|
||||
|
||||
nsresult AddressBookParser::str_parse_line(
|
||||
char *line,
|
||||
char **type,
|
||||
char **value,
|
||||
int *vlen
|
||||
)
|
||||
{
|
||||
char *p, *s, *d, *byte, *stop;
|
||||
char nib;
|
||||
int i, b64;
|
||||
|
||||
/* skip any leading space */
|
||||
while ( IS_SPACE( *line ) ) {
|
||||
line++;
|
||||
}
|
||||
*type = line;
|
||||
|
||||
for ( s = line; *s && *s != ':'; s++ )
|
||||
; /* NULL */
|
||||
if ( *s == '\0' ) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
/* trim any space between type and : */
|
||||
for ( p = s - 1; p > line && isspace( *p ); p-- ) {
|
||||
*p = '\0';
|
||||
}
|
||||
*s++ = '\0';
|
||||
|
||||
/* check for double : - indicates base 64 encoded value */
|
||||
if ( *s == ':' ) {
|
||||
s++;
|
||||
b64 = 1;
|
||||
|
||||
/* single : - normally encoded value */
|
||||
} else {
|
||||
b64 = 0;
|
||||
}
|
||||
|
||||
/* skip space between : and value */
|
||||
while ( IS_SPACE( *s ) ) {
|
||||
s++;
|
||||
}
|
||||
|
||||
/* if no value is present, error out */
|
||||
if ( *s == '\0' ) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
/* check for continued line markers that should be deleted */
|
||||
for ( p = s, d = s; *p; p++ ) {
|
||||
if ( *p != CONTINUED_LINE_MARKER )
|
||||
*d++ = *p;
|
||||
}
|
||||
*d = '\0';
|
||||
|
||||
*value = s;
|
||||
if ( b64 ) {
|
||||
stop = PL_strchr( s, '\0' );
|
||||
byte = s;
|
||||
for ( p = s, *vlen = 0; p < stop; p += 4, *vlen += 3 ) {
|
||||
for ( i = 0; i < 3; i++ ) {
|
||||
if ( p[i] != '=' && (p[i] & 0x80 ||
|
||||
b642nib[ p[i] & 0x7f ] > 0x3f) ) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* first digit */
|
||||
nib = b642nib[ p[0] & 0x7f ];
|
||||
byte[0] = nib << 2;
|
||||
/* second digit */
|
||||
nib = b642nib[ p[1] & 0x7f ];
|
||||
byte[0] |= nib >> 4;
|
||||
byte[1] = (nib & RIGHT4) << 4;
|
||||
/* third digit */
|
||||
if ( p[2] == '=' ) {
|
||||
*vlen += 1;
|
||||
break;
|
||||
}
|
||||
nib = b642nib[ p[2] & 0x7f ];
|
||||
byte[1] |= nib >> 2;
|
||||
byte[2] = (nib & RIGHT2) << 6;
|
||||
/* fourth digit */
|
||||
if ( p[3] == '=' ) {
|
||||
*vlen += 2;
|
||||
break;
|
||||
}
|
||||
nib = b642nib[ p[3] & 0x7f ];
|
||||
byte[2] |= nib;
|
||||
|
||||
byte += 3;
|
||||
}
|
||||
s[ *vlen ] = '\0';
|
||||
} else {
|
||||
*vlen = (int) (d - s);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* str_getline - return the next "line" (minus newline) of input from a
|
||||
* string buffer of lines separated by newlines, terminated by \n\n
|
||||
* or \0. this routine handles continued lines, bundling them into
|
||||
* a single big line before returning. if a line begins with a white
|
||||
* space character, it is a continuation of the previous line. the white
|
||||
* space character (nb: only one char), and preceeding newline are changed
|
||||
* into CONTINUED_LINE_MARKER chars, to be deleted later by the
|
||||
* str_parse_line() routine above.
|
||||
*
|
||||
* it takes a pointer to a pointer to the buffer on the first call,
|
||||
* which it updates and must be supplied on subsequent calls.
|
||||
*/
|
||||
|
||||
char * AddressBookParser::str_getline( char **next )
|
||||
{
|
||||
char *lineStr;
|
||||
char c;
|
||||
|
||||
if ( *next == NULL || **next == '\n' || **next == '\0' ) {
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
lineStr = *next;
|
||||
while ( (*next = PL_strchr( *next, '\n' )) != NULL ) {
|
||||
c = *(*next + 1);
|
||||
if ( IS_SPACE ( c ) && c != '\n' ) {
|
||||
**next = CONTINUED_LINE_MARKER;
|
||||
*(*next+1) = CONTINUED_LINE_MARKER;
|
||||
} else {
|
||||
*(*next)++ = '\0';
|
||||
break;
|
||||
}
|
||||
/* *(*next)++; Linux complaint it is never used, comment out */
|
||||
}
|
||||
|
||||
return( lineStr );
|
||||
}
|
||||
|
||||
/*
|
||||
* get one ldif record
|
||||
*
|
||||
*/
|
||||
nsresult AddressBookParser::GetLdifStringRecord(char* buf, PRInt32 len, PRInt32* stopPos)
|
||||
{
|
||||
PRInt32 LFCount = 0;
|
||||
PRInt32 CRCount = 0;
|
||||
|
||||
for (; *stopPos < len; (*stopPos)++)
|
||||
{
|
||||
char c = buf[*stopPos];
|
||||
|
||||
if (c == 0xA)
|
||||
{
|
||||
LFCount++;
|
||||
}
|
||||
else if (c == 0xD)
|
||||
{
|
||||
CRCount++;
|
||||
}
|
||||
else if ( c != 0xA && c != 0xD)
|
||||
{
|
||||
if (LFCount == 0 && CRCount == 0)
|
||||
mLine.Append(c);
|
||||
else if (( LFCount > 1) || ( CRCount > 2 && LFCount ) ||
|
||||
( !LFCount && CRCount > 1 ))
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
else if ((LFCount == 1 || CRCount == 1) && c != ' ')
|
||||
{
|
||||
mLine.Append('\n');
|
||||
mLine.Append(c);
|
||||
LFCount = 0;
|
||||
CRCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (*stopPos ==len && (LFCount > 1) || (CRCount > 2 && LFCount) ||
|
||||
(!LFCount && CRCount > 1))
|
||||
return NS_OK;
|
||||
else
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult AddressBookParser::ParseLdifFile(PRFileDesc* file)
|
||||
{
|
||||
|
@ -616,9 +839,8 @@ nsresult AddressBookParser::ParseLdifFile(PRFileDesc* file)
|
|||
return NS_ERROR_NULL_POINTER;
|
||||
|
||||
char buf[1024];
|
||||
PRInt32 startPos = 0;
|
||||
PRInt32 len = 0;
|
||||
PRInt32 LFCount = 0;
|
||||
PRInt32 CRCount = 0;
|
||||
nsIMdbRow* newRow = nsnull;
|
||||
|
||||
if (mDatabase)
|
||||
|
@ -633,80 +855,49 @@ nsresult AddressBookParser::ParseLdifFile(PRFileDesc* file)
|
|||
|
||||
while ((len = PR_Read(file, buf, sizeof(buf))) > 0)
|
||||
{
|
||||
for (PRInt32 i = 0; i < len; i++)
|
||||
{
|
||||
char c = buf[i];
|
||||
startPos = 0;
|
||||
|
||||
if (c == 0xA)
|
||||
while (NS_SUCCEEDED(GetLdifStringRecord(buf, len, &startPos)))
|
||||
{
|
||||
if (mDatabase)
|
||||
{
|
||||
LFCount++;
|
||||
mDatabase->GetNewRow(&newRow);
|
||||
|
||||
if (!newRow)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
else if (c == 0xD)
|
||||
else
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
char* cursor = (char*)mLine.ToNewCString();
|
||||
char* saveCursor = cursor; /* keep for deleting */
|
||||
char* line = 0;
|
||||
char* typeSlot = 0;
|
||||
char* valueSlot = 0;
|
||||
int length = 0; // the length of an ldif attribute
|
||||
while ( (line = str_getline(&cursor)) != NULL)
|
||||
{
|
||||
CRCount++;
|
||||
}
|
||||
else if ( c != 0xA && c != 0xD)
|
||||
{
|
||||
if (LFCount ==0 && CRCount ==0)
|
||||
mLine.Append(c);
|
||||
else if (LFCount == 1 || CRCount == 1)
|
||||
if ( str_parse_line(line, &typeSlot, &valueSlot, &length) == 0 )
|
||||
{
|
||||
AddLdifColToDatabase(newRow);
|
||||
if (c != ' ')
|
||||
mLine.Append(c);
|
||||
LFCount = 0;
|
||||
CRCount = 0;
|
||||
}
|
||||
else if (c != ' ' && (( LFCount > 1) || ( CRCount > 2 && LFCount ) ||
|
||||
( !LFCount && CRCount > 1 )))
|
||||
{
|
||||
mDatabase->AddCardRowToDB(newRow);
|
||||
if (c != ' ')
|
||||
mLine.Append(c);
|
||||
LFCount = 0;
|
||||
CRCount = 0;
|
||||
AddLdifColToDatabase(newRow, typeSlot, valueSlot);
|
||||
}
|
||||
else
|
||||
continue; // parse error: continue with next loop iteration
|
||||
}
|
||||
delete [] saveCursor;
|
||||
mDatabase->AddCardRowToDB(newRow);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void AddressBookParser::AddLdifColToDatabase(nsIMdbRow* newRow)
|
||||
void AddressBookParser::AddLdifColToDatabase(nsIMdbRow* newRow, char* typeSlot, char* valueSlot)
|
||||
{
|
||||
nsCAutoString colType;
|
||||
nsCAutoString column;
|
||||
|
||||
// const PRUnichar *str = nsnull;
|
||||
// const char *str = nsnull;
|
||||
PRInt32 nSize = mLine.Length();
|
||||
PRBool bGetType = PR_TRUE;
|
||||
nsCAutoString colType(typeSlot);
|
||||
nsCAutoString column(valueSlot);
|
||||
|
||||
for (int i = 0; i < nSize; i++)
|
||||
{
|
||||
// PRUnichar c = mLine[i];
|
||||
char c = (mLine.GetBuffer())[i];
|
||||
if (!bGetType)
|
||||
{
|
||||
column.Append(c);
|
||||
continue;
|
||||
}
|
||||
while (bGetType && c != ':' && i < nSize)
|
||||
{
|
||||
colType.Append(c);
|
||||
c = (mLine.GetBuffer())[++i];
|
||||
}
|
||||
if (c != ':')
|
||||
{
|
||||
bGetType = PR_FALSE;
|
||||
c = (mLine.GetBuffer())[++i];
|
||||
if (c == ':')
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
mdb_u1 firstByte = (mdb_u1) (colType.GetBuffer()) [0];
|
||||
// mdb_u1 firstByte = (mdb_u1)colType[0];
|
||||
mdb_u1 firstByte = (mdb_u1)(colType.GetBuffer())[0];
|
||||
switch ( firstByte )
|
||||
{
|
||||
case 'b':
|
||||
|
|
|
@ -1196,7 +1196,7 @@ static PRBool dir_ValidateAndAddNewServer(nsVoidArray *wholeList, const char *fu
|
|||
{
|
||||
PRBool rc = PR_FALSE;
|
||||
|
||||
const char *endname = XP_STRCHR(&fullprefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
|
||||
const char *endname = PL_strchr(&fullprefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
|
||||
if (endname)
|
||||
{
|
||||
char *prefname = (char *)PR_Malloc(endname - fullprefname + 1);
|
||||
|
@ -1441,7 +1441,7 @@ DIR_PrefId DIR_AtomizePrefName(const char *prefname)
|
|||
*/
|
||||
if (PL_strstr(prefname, PREF_LDAP_SERVER_TREE_NAME) == prefname)
|
||||
{
|
||||
prefname = XP_STRCHR(&prefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
|
||||
prefname = PL_strchr(&prefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.');
|
||||
if (!prefname)
|
||||
return idNone;
|
||||
else
|
||||
|
@ -2295,7 +2295,7 @@ static nsresult DIR_AddCustomAttribute(DIR_Server *server, const char *attrName,
|
|||
* attributes without a pretty name. So find the default pretty name, and generate
|
||||
* a "complete" string to use for tokenizing.
|
||||
*/
|
||||
if (NS_SUCCEEDED(status) && !XP_STRCHR(jsAttr, ':'))
|
||||
if (NS_SUCCEEDED(status) && !PL_strchr(jsAttr, ':'))
|
||||
{
|
||||
const char *defaultPrettyName = DIR_GetAttributeName (server, id);
|
||||
if (defaultPrettyName)
|
||||
|
@ -2803,8 +2803,8 @@ void DIR_GetServerFileName(char** filename, const char* leafName)
|
|||
char* realLeafName;
|
||||
char* nativeName;
|
||||
char* urlName;
|
||||
if (XP_STRCHR(leafName, ':') != nsnull)
|
||||
realLeafName = XP_STRRCHR(leafName, ':') + 1; /* makes sure that leafName is not a fullpath */
|
||||
if (PL_strchr(leafName, ':') != nsnull)
|
||||
realLeafName = PL_strrchr(leafName, ':') + 1; /* makes sure that leafName is not a fullpath */
|
||||
else
|
||||
realLeafName = leafName;
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ Rights Reserved.
|
|||
<!ENTITY copyCmd.label ".Copy">
|
||||
<!ENTITY pasteCmd.label ".Paste">
|
||||
<!ENTITY deleteCmd.label "Delete">
|
||||
<!ENTITY deleteAbCmd.label "Delete Address Book">
|
||||
<!ENTITY selectAllCmd.label ".Select All">
|
||||
<!ENTITY searchCmd.label ".Search...">
|
||||
<!ENTITY htmlDomainCmd.label ".HTML Domains...">
|
||||
|
|
Загрузка…
Ссылка в новой задаче