Mail: Detect URLs by parsing ahead instead of checking every character.
This is a *massive* performance improvement; some longer emails that took 45+ seconds to load before now take < 1 second, at the cost of a completely rewritten URL detector. This means some URLs that were detected before (e.g. anything starting with "www", emails without "mailto:", etc.) are now not detected, though support for detecting them could be reinstated in the future. Fixes #4816.
This commit is contained in:
+95
-142
@@ -275,115 +275,56 @@ FilterHTMLTag(int32 &first, char **t, char *end)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Returns the type and length of the URL in the string if it is one.
|
/*! Returns the type of the next URL in the string.
|
||||||
* If the "url" string is specified, it will fill it with the complete
|
*
|
||||||
* URL that might differ from the one in the text itself (i.e. because
|
* If the "url" string is specified, it will fill it with the complete
|
||||||
* of an prepended "http://").
|
* URL.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static uint8
|
static uint8
|
||||||
CheckForURL(const char *string, size_t &urlLength, BString *url = NULL)
|
FindURL(const BString& string, int32 startIndex, int32& urlPos,
|
||||||
|
int32& urlLength, BString* urlString = NULL)
|
||||||
{
|
{
|
||||||
const char *urlPrefixes[] = {
|
|
||||||
"http://",
|
|
||||||
"ftp://",
|
|
||||||
"shttp://",
|
|
||||||
"https://",
|
|
||||||
"finger://",
|
|
||||||
"telnet://",
|
|
||||||
"gopher://",
|
|
||||||
"news://",
|
|
||||||
"nntp://",
|
|
||||||
"file://",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Search for URL prefix
|
|
||||||
//
|
|
||||||
uint8 type = 0;
|
uint8 type = 0;
|
||||||
for (const char **prefix = urlPrefixes; *prefix != 0; prefix++) {
|
urlPos = string.Length();
|
||||||
if (!cistrncmp(string, *prefix, strlen(*prefix))) {
|
|
||||||
type = TYPE_URL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
int32 baseOffset = string.FindFirst("://", startIndex),
|
||||||
// Not a URL? check for "mailto:" or "www."
|
mailtoOffset = string.FindFirst("mailto:", startIndex);
|
||||||
//
|
if (baseOffset == B_ERROR && mailtoOffset == B_ERROR)
|
||||||
if (type == 0 && !cistrncmp(string, "mailto:", 7))
|
|
||||||
type = TYPE_MAILTO;
|
|
||||||
if (type == 0 && !strncmp(string, "www.", 4)) {
|
|
||||||
// this type will be special cased later (and a http:// is added
|
|
||||||
// for the enclosure address)
|
|
||||||
type = TYPE_URL;
|
|
||||||
}
|
|
||||||
if (type == 0) {
|
|
||||||
// check for valid eMail addresses
|
|
||||||
const char *at = strchr(string, '@');
|
|
||||||
if (at) {
|
|
||||||
const char *pos = string;
|
|
||||||
bool readName = false;
|
|
||||||
for (; pos < at; pos++) {
|
|
||||||
// ToDo: are these all allowed characters?
|
|
||||||
if (!isalnum(pos[0]) && pos[0] != '_' && pos[0] != '.' && pos[0] != '-')
|
|
||||||
break;
|
|
||||||
|
|
||||||
readName = true;
|
|
||||||
}
|
|
||||||
if (pos == at && readName)
|
|
||||||
type = TYPE_MAILTO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
if (baseOffset == B_ERROR)
|
||||||
|
baseOffset = string.Length();
|
||||||
|
if (mailtoOffset == B_ERROR)
|
||||||
|
mailtoOffset = string.Length();
|
||||||
|
|
||||||
int32 index = strcspn(string, " \t<>)\"\\,\r\n");
|
if (baseOffset < mailtoOffset) {
|
||||||
|
type = TYPE_URL;
|
||||||
|
|
||||||
|
// Find the actual start of the URL
|
||||||
|
urlPos = baseOffset;
|
||||||
|
while (urlPos >= startIndex && (isalnum(string.ByteAt(urlPos - 1))
|
||||||
|
|| string.ByteAt(urlPos - 1) == '-'))
|
||||||
|
urlPos--;
|
||||||
|
} else if (mailtoOffset < baseOffset) {
|
||||||
|
type = TYPE_MAILTO;
|
||||||
|
urlPos = mailtoOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the end of the URL based on word boundaries
|
||||||
|
const char* str = string.String() + urlPos;
|
||||||
|
urlLength = strcspn(str, " \t<>)\"\\,\r\n");
|
||||||
|
|
||||||
// filter out some punctuation marks if they are the last character
|
// filter out some punctuation marks if they are the last character
|
||||||
char suffix = string[index - 1];
|
char suffix = str[urlLength - 1];
|
||||||
if (suffix == '.'
|
while (suffix == '.'
|
||||||
|| suffix == ','
|
|| suffix == ','
|
||||||
|| suffix == '?'
|
|| suffix == '?'
|
||||||
|| suffix == '!'
|
|| suffix == '!'
|
||||||
|| suffix == ':'
|
|| suffix == ':'
|
||||||
|| suffix == ';')
|
|| suffix == ';')
|
||||||
index--;
|
urlLength--;
|
||||||
|
|
||||||
if (type == TYPE_URL) {
|
if (urlString != NULL)
|
||||||
char *parenthesis = NULL;
|
*urlString = BString(string.String() + urlPos, urlLength);
|
||||||
|
|
||||||
// filter out a trailing ')' if there is no left parenthesis before
|
|
||||||
if (string[index - 1] == ')') {
|
|
||||||
char *parenthesis = strchr(string, '(');
|
|
||||||
if (parenthesis == NULL || parenthesis > string + index)
|
|
||||||
index--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter out a trailing ']' if there is no left bracket before
|
|
||||||
if (parenthesis == NULL && string[index - 1] == ']') {
|
|
||||||
char *parenthesis = strchr(string, '[');
|
|
||||||
if (parenthesis == NULL || parenthesis > string + index)
|
|
||||||
index--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url != NULL) {
|
|
||||||
// copy the address to the specified string
|
|
||||||
if (type == TYPE_URL && string[0] == 'w') {
|
|
||||||
// URL starts with "www.", so add the protocol to it
|
|
||||||
url->SetTo("http://");
|
|
||||||
url->Append(string, index);
|
|
||||||
} else if (type == TYPE_MAILTO && cistrncmp(string, "mailto:", 7)) {
|
|
||||||
// eMail address had no "mailto:" prefix
|
|
||||||
url->SetTo("mailto:");
|
|
||||||
url->Append(string, index);
|
|
||||||
} else
|
|
||||||
url->SetTo(string, index);
|
|
||||||
}
|
|
||||||
urlLength = index;
|
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -2151,10 +2092,15 @@ TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_enc
|
|||||||
// hard-wrap, based on TextView's soft-wrapping
|
// hard-wrap, based on TextView's soft-wrapping
|
||||||
int32 numLines = CountLines();
|
int32 numLines = CountLines();
|
||||||
bool spaceMoved = false;
|
bool spaceMoved = false;
|
||||||
char *content = (char *)malloc(textLength + numLines * 72); // more we'll ever need
|
char *content = (char *)malloc(textLength + numLines * 72);
|
||||||
|
// more we'll ever need
|
||||||
if (content != NULL) {
|
if (content != NULL) {
|
||||||
int32 contentLength = 0;
|
int32 contentLength = 0;
|
||||||
|
|
||||||
|
int32 nextUrlAt = 0, nextUrlLength = 0;
|
||||||
|
BString textStr(text);
|
||||||
|
FindURL(text, 0, nextUrlAt, nextUrlLength, NULL);
|
||||||
|
|
||||||
for (int32 i = 0; i < numLines; i++) {
|
for (int32 i = 0; i < numLines; i++) {
|
||||||
int32 startOffset = OffsetAt(i);
|
int32 startOffset = OffsetAt(i);
|
||||||
if (spaceMoved) {
|
if (spaceMoved) {
|
||||||
@@ -2164,42 +2110,41 @@ TTextView::AddAsContent(BEmailMessage *mail, bool wrap, uint32 charset, mail_enc
|
|||||||
int32 endOffset = OffsetAt(i + 1);
|
int32 endOffset = OffsetAt(i + 1);
|
||||||
int32 lineLength = endOffset - startOffset;
|
int32 lineLength = endOffset - startOffset;
|
||||||
|
|
||||||
// quick hack to not break URLs into several parts
|
// don't break URLs into several parts
|
||||||
for (int32 pos = startOffset; pos < endOffset; pos++) {
|
if (nextUrlAt >= startOffset && nextUrlAt < endOffset
|
||||||
size_t urlLength;
|
&& (nextUrlAt + nextUrlLength) > endOffset) {
|
||||||
uint8 type = CheckForURL(text + pos, urlLength);
|
int32 pos = nextUrlAt + nextUrlLength;
|
||||||
if (type != 0)
|
|
||||||
pos += urlLength;
|
|
||||||
|
|
||||||
if (pos > endOffset) {
|
// find first break character after the URL
|
||||||
// find first break character after the URL
|
for (; text[pos]; pos++) {
|
||||||
for (; text[pos]; pos++) {
|
if (isalnum(text[pos]) || isspace(text[pos]))
|
||||||
if (isalnum(text[pos]) || isspace(text[pos]))
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (text[pos] && isspace(text[pos]) && text[pos] != '\n')
|
|
||||||
pos++;
|
|
||||||
|
|
||||||
endOffset += pos - endOffset;
|
|
||||||
lineLength = endOffset - startOffset;
|
|
||||||
|
|
||||||
// insert a newline (and the same number of quotes) after the
|
|
||||||
// URL to make sure the rest of the text is properly wrapped
|
|
||||||
|
|
||||||
char buffer[64];
|
|
||||||
if (text[pos] == '\n')
|
|
||||||
buffer[0] = '\0';
|
|
||||||
else
|
|
||||||
strcpy(buffer, "\n");
|
|
||||||
|
|
||||||
size_t quoteLength;
|
|
||||||
CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength);
|
|
||||||
|
|
||||||
Insert(pos, buffer, strlen(buffer));
|
|
||||||
numLines = CountLines();
|
|
||||||
text = Text();
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
if (text[pos] && isspace(text[pos]) && text[pos] != '\n')
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
endOffset += pos - endOffset;
|
||||||
|
lineLength = endOffset - startOffset;
|
||||||
|
|
||||||
|
// insert a newline (and the same number of quotes) after the
|
||||||
|
// URL to make sure the rest of the text is properly wrapped
|
||||||
|
|
||||||
|
char buffer[64];
|
||||||
|
if (text[pos] == '\n')
|
||||||
|
buffer[0] = '\0';
|
||||||
|
else
|
||||||
|
strcpy(buffer, "\n");
|
||||||
|
|
||||||
|
size_t quoteLength;
|
||||||
|
CopyQuotes(text + startOffset, lineLength, buffer + strlen(buffer), quoteLength);
|
||||||
|
|
||||||
|
Insert(pos, buffer, strlen(buffer));
|
||||||
|
numLines = CountLines();
|
||||||
|
text = Text();
|
||||||
|
i++;
|
||||||
|
|
||||||
|
textStr = BString(text);
|
||||||
|
FindURL(text, endOffset, nextUrlAt, nextUrlLength, NULL);
|
||||||
}
|
}
|
||||||
if (text[endOffset - 1] != ' '
|
if (text[endOffset - 1] != ' '
|
||||||
&& text[endOffset - 1] != '\n'
|
&& text[endOffset - 1] != '\n'
|
||||||
@@ -2370,6 +2315,12 @@ TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader)
|
|||||||
char line[522];
|
char line[522];
|
||||||
int32 count = 0;
|
int32 count = 0;
|
||||||
|
|
||||||
|
const BString dataStr(data, data_len);
|
||||||
|
BString nextUrl;
|
||||||
|
int32 nextUrlPos = 0, nextUrlLength = 0;
|
||||||
|
uint8 nextUrlType
|
||||||
|
= FindURL(dataStr, 0, nextUrlPos, nextUrlLength, &nextUrl);
|
||||||
|
|
||||||
for (int32 loop = 0; loop < data_len; loop++) {
|
for (int32 loop = 0; loop < data_len; loop++) {
|
||||||
if (fView->fStopLoading)
|
if (fView->fStopLoading)
|
||||||
return false;
|
return false;
|
||||||
@@ -2379,9 +2330,7 @@ TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader)
|
|||||||
count += strlen(QUOTE);
|
count += strlen(QUOTE);
|
||||||
}
|
}
|
||||||
if (!fRaw && fIncoming && (loop < data_len - 7)) {
|
if (!fRaw && fIncoming && (loop < data_len - 7)) {
|
||||||
size_t urlLength;
|
uint8 type = (nextUrlPos == loop) ? nextUrlType : 0;
|
||||||
BString url;
|
|
||||||
uint8 type = CheckForURL(data + loop, urlLength, &url);
|
|
||||||
|
|
||||||
if (type) {
|
if (type) {
|
||||||
if (!Insert(line, count, false, isHeader))
|
if (!Insert(line, count, false, isHeader))
|
||||||
@@ -2394,19 +2343,23 @@ TTextView::Reader::Process(const char *data, int32 data_len, bool isHeader)
|
|||||||
|
|
||||||
memset(enclosure, 0, sizeof(hyper_text));
|
memset(enclosure, 0, sizeof(hyper_text));
|
||||||
fView->GetSelection(&enclosure->text_start,
|
fView->GetSelection(&enclosure->text_start,
|
||||||
&enclosure->text_end);
|
&enclosure->text_end);
|
||||||
enclosure->type = type;
|
enclosure->type = type;
|
||||||
enclosure->name = strdup(url.String());
|
enclosure->name = strdup(nextUrl.String());
|
||||||
if (enclosure->name == NULL) {
|
if (enclosure->name == NULL) {
|
||||||
free(enclosure);
|
free(enclosure);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Insert(&data[loop], urlLength, true, isHeader);
|
Insert(&data[loop], nextUrlLength, true, isHeader);
|
||||||
enclosure->text_end += urlLength;
|
enclosure->text_end += nextUrlLength;
|
||||||
loop += urlLength - 1;
|
loop += nextUrlLength - 1;
|
||||||
|
|
||||||
fEnclosures->AddItem(enclosure);
|
fEnclosures->AddItem(enclosure);
|
||||||
|
|
||||||
|
nextUrlType
|
||||||
|
= FindURL(dataStr, loop,
|
||||||
|
nextUrlPos, nextUrlLength, &nextUrl);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user