/** * linklocator.cpp * * Copyright (c) 2002 Dave Corrie * * This file is part of KMail. * * KMail is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "linklocator.h" LinkLocator::LinkLocator(const QString& text, int pos) : mText(text), mPos(pos), mMaxUrlLen(4096), mMaxAddressLen(255) { // If you change either of the above values for maxUrlLen or // maxAddressLen, then please also update the documentation for // setMaxUrlLen()/setMaxAddressLen() in the header file AND the // default values used for the maxUrlLen/maxAddressLen parameters // of convertToHtml(). } void LinkLocator::setMaxUrlLen(int length) { mMaxUrlLen = length; } int LinkLocator::maxUrlLen() const { return mMaxUrlLen; } void LinkLocator::setMaxAddressLen(int length) { mMaxAddressLen = length; } int LinkLocator::maxAddressLen() const { return mMaxAddressLen; } QString LinkLocator::getUrl() { QString url; if(atUrl()) { // handle cases like this: http://foobar.org/ int start = mPos; while(mPos < (int)mText.length() && mText[mPos] > ' ' && mText[mPos] != '"' && QString("<>()[]").find(mText[mPos]) == -1) { ++mPos; } /* some URLs really end with: # / & */ const QString allowedSpecialChars = QString("#/&"); while(mPos > start && mText[mPos-1].isPunct() && allowedSpecialChars.find(mText[mPos-1]) == -1 ) { --mPos; } url = mText.mid(start, mPos - start); if(isEmptyUrl(url) || mPos - start > maxUrlLen()) { mPos = start; url = ""; } else { --mPos; } } return url; } // keep this in sync with KMMainWin::slotUrlClicked() bool LinkLocator::atUrl() const { QChar ch = mText[mPos]; return (ch=='h' && mText.mid(mPos, 7) == "http://") || (ch=='h' && mText.mid(mPos, 8) == "https://") || (ch=='v' && mText.mid(mPos, 6) == "vnc://") || (ch=='f' && ( mText.mid(mPos, 6) == "ftp://" || mText.mid(mPos, 7) == "ftps://") ) || (ch=='s' && mText.mid(mPos, 7) == "sftp://") || (ch=='s' && mText.mid(mPos, 6) == "smb://") || (ch=='m' && mText.mid(mPos, 7) == "mailto:") || (ch=='w' && mText.mid(mPos, 4) == "www.") || (ch=='f' && mText.mid(mPos, 4) == "ftp."); // note: no "file:" for security reasons } bool LinkLocator::isEmptyUrl(const QString& url) { return url.isEmpty() || url == "http://" || url == "https://" || url == "ftp://" || url == "ftps://" || url == "sftp://" || url == "smb://" || url == "vnc://" || url == "mailto" || url == "www" || url == "ftp"; } QString LinkLocator::getEmailAddress() { QString address; if(mText[mPos] == '@') { // the following characters are allowed in a dot-atom (RFC 2822): // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ const QString allowedSpecialChars = QString(".!#$%&'*+-/=?^_`{|}~"); // determine the local part of the email address int start = mPos - 1; while (start >= 0 && mText[start].unicode() < 128 && (mText[start].isLetterOrNumber() || mText[start] == '@' || // allow @ to find invalid email addresses allowedSpecialChars.find(mText[start]) != -1)) { --start; } ++start; // we assume that an email address starts with a letter or a digit while (allowedSpecialChars.find(mText[start]) != -1) ++start; // determine the domain part of the email address int end = mPos + 1; while (end < (int)mText.length() && mText[end].unicode() < 128 && (mText[end].isLetterOrNumber() || mText[end] == '@' || // allow @ to find invalid email addresses allowedSpecialChars.find(mText[end]) != -1)) { ++end; } // we assume that an email address ends with a letter or a digit while (allowedSpecialChars.find(mText[end - 1]) != -1) --end; address = mText.mid(start, end - start); if(isEmptyAddress(address) || end - start > maxAddressLen() || address.contains('@') != 1) address = ""; if(!address.isEmpty()) mPos = end - 1; } return address; } bool LinkLocator::isEmptyAddress(const QString& address) { return address.isEmpty() || address[0] == '@' || address[address.length() - 1] == '@'; } QString LinkLocator::convertToHtml(const QString& plainText, bool preserveBlanks, int maxUrlLen, int maxAddressLen) { LinkLocator locator(plainText); locator.setMaxUrlLen(maxUrlLen); locator.setMaxAddressLen(maxAddressLen); QString str; QString result((QChar*)0, (int)locator.mText.length() * 2); QChar ch; int x; bool startOfLine = true; for (locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length(); locator.mPos++, x++) { ch = locator.mText[locator.mPos]; if (preserveBlanks) { if (ch==' ') { if (startOfLine) { result += " "; locator.mPos++, x++; startOfLine = false; } while (locator.mText[locator.mPos] == ' ') { result += " "; locator.mPos++, x++; if (locator.mText[locator.mPos] == ' ') { result += " "; locator.mPos++, x++; } } locator.mPos--, x--; continue; } else if (ch=='\t') { do { result += " "; x++; } while((x&7) != 0); x--; startOfLine = false; continue; } } if (ch=='\n') { result += "
"; startOfLine = true; x = -1; continue; } startOfLine = false; if (ch=='&') result += "&"; else if (ch=='"') result += """; else if (ch=='<') result += "<"; else if (ch=='>') result += ">"; else { int start = locator.mPos; str = locator.getUrl(); if(!str.isEmpty()) { QString hyperlink; if(str.left(4) == "www.") hyperlink = "http://" + str; else if(str.left(4) == "ftp.") hyperlink = "ftp://" + str; else hyperlink = str; result += "" + str + ""; x += locator.mPos - start; } else { str = locator.getEmailAddress(); if(!str.isEmpty()) { // len is the length of the local part int len = str.find('@'); QString localPart = str.left(len); // remove the local part from the result (as '&'s have been expanded to // & we have to take care of the 4 additional characters per '&') result.truncate(result.length() - len - (localPart.contains('&')*4)); x -= len; result += "" + str + ""; x += str.length() - 1; } else { result += ch; } } } } return result; }