1 // Smuxi - Smart MUltipleXed Irc 2 // 3 // Copyright (c) 2011, 2014-2017 Mirco Bauer <meebey@meebey.net> 4 // 5 // Full GPL License: <http://www.gnu.org/licenses/gpl.txt> 6 // 7 // This program is free software; you can redistribute it and/or modify 8 // it under the terms of the GNU General Public License as published by 9 // the Free Software Foundation; either version 2 of the License, or 10 // (at your option) any later version. 11 // 12 // This program is distributed in the hope that it will be useful, 13 // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 // GNU General Public License for more details. 16 // 17 // You should have received a copy of the GNU General Public License 18 // along with this program; if not, write to the Free Software 19 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 using System; 21 using System.Text; 22 using System.Text.RegularExpressions; 23 using System.Collections.Generic; 24 using Smuxi.Common; 25 26 namespace Smuxi.Engine 27 { 28 public class MessageBuilderSettings 29 { 30 static List<MessagePatternModel> BuiltinPatterns { get; set; } 31 static MessagePatternModel EmojiMessagePattern { get; set; } 32 public List<MessagePatternModel> UserPatterns { get; set; } 33 public List<MessagePatternModel> Patterns { get; set; } 34 public bool NickColors { get; set; } 35 public bool StripFormattings { get; set; } 36 public bool StripColors { get; set; } 37 public TextColor HighlightColor { get; set; } 38 public List<string> HighlightWords { get; set; } 39 40 public bool Emojis { 41 get { 42 return Patterns.Contains(EmojiMessagePattern); 43 } 44 set { 45 if (value && !Emojis) { 46 Patterns.Add(EmojiMessagePattern); 47 } 48 if (!value) { 49 Patterns.Remove(EmojiMessagePattern); 50 } 51 } 52 } 53 MessageBuilderSettings()54 static MessageBuilderSettings() 55 { 56 // OPT: this emoji regex is really long, around 27k characters 57 var emojiRegexBuilder = new StringBuilder(32 * 1024); 58 emojiRegexBuilder.Append(":("); 59 foreach (var emojiShortname in Emojione.ShortnameToUnicodeMap.Keys) { 60 emojiRegexBuilder.AppendFormat("{0}|", Regex.Escape(emojiShortname)); 61 } 62 // remove trailing | 63 emojiRegexBuilder.Length--; 64 emojiRegexBuilder.Append("):"); 65 66 var emojiRegex = new Regex(emojiRegexBuilder.ToString(), RegexOptions.Compiled); 67 EmojiMessagePattern = new MessagePatternModel(emojiRegex) { 68 MessagePartType = typeof(ImageMessagePartModel), 69 LinkFormat = "smuxi-emoji://{1}" 70 }; 71 72 BuiltinPatterns = new List<MessagePatternModel>(); 73 InitBuiltinSmartLinks(); 74 } 75 MessageBuilderSettings()76 public MessageBuilderSettings() 77 { 78 NickColors = true; 79 80 // No need to lock BuiltinPatterns as List<T> is thread-safe for 81 // multiple readers as long as there is no writer at the same time. 82 // BuiltinPatterns is only written once before the first instance 83 // of MessageBuilderSettings is created via the static initializer. 84 Patterns = new List<MessagePatternModel>(BuiltinPatterns); 85 } 86 MessageBuilderSettings(MessageBuilderSettings settings)87 public MessageBuilderSettings(MessageBuilderSettings settings) 88 { 89 if (settings == null) { 90 throw new ArgumentNullException("settings"); 91 } 92 93 UserPatterns = new List<MessagePatternModel>(settings.UserPatterns); 94 Patterns = new List<MessagePatternModel>(settings.Patterns); 95 NickColors = settings.NickColors; 96 StripFormattings = settings.StripFormattings; 97 StripColors = settings.StripColors; 98 HighlightColor = settings.HighlightColor; 99 HighlightWords = settings.HighlightWords; 100 } 101 102 internal const string StartDelimiterGroupName = "DelimiterForStartOfPattern"; 103 internal const string EndDelimiterGroupName = "DelimiterForEndOfPattern"; 104 InitBuiltinSmartLinks()105 static void InitBuiltinSmartLinks() 106 { 107 string path_last_chars = @"a-zA-Z0-9#/%&@=\-_+;:~'"; 108 string path_chars = path_last_chars + @"\(\)\[\]\{\}?!.,"; 109 string domain_chars = @"[a-z0-9\-\p{L}]+"; 110 string subdomain = domain_chars + @"\."; 111 string common_tld = @"de|es|im|us|com|net|org|info|biz|gov|name|edu|onion|museum"; 112 string any_tld = @"[a-z\p{L}]+"; 113 string ip6 = @"(?:[0-9a-f]{0,4}:){1,7}[0-9a-f]{1,4}"; 114 string quoted_ip6 = @"\[" + ip6 + @"\]"; 115 string ip4 = @"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"; 116 string ip = "(?:" + ip4 + "|" + ip6 + "|" + quoted_ip6 + ")"; 117 string domain = @"(?:(?:" + subdomain + ")+(?:" + any_tld + ")|localhost)"; 118 string bare_host = @"[a-z]+"; 119 string host = "(?:" + domain + "|" + bare_host + "|" + ip + ")"; 120 string short_number = "[1-9][0-9]{0,4}"; 121 string port = ":" + short_number; 122 string user = "[a-z0-9._%+-]+@"; 123 string host_port = host + "(?:" + port + ")?"; 124 string user_host_port = "(?:" + user + ")?" + host_port; 125 string user_domain = user + domain; 126 string path = @"/(?:["+ path_chars +"]*["+ path_last_chars +"]+)?"; 127 string protocol = @"[a-z][a-z0-9\-+]*://"; 128 string protocol_user_host_port_path = protocol + user_host_port + "(?:" + path + ")?"; 129 string start_delimiter = String.Format(@"(?<{0}>^|\s|\W)", StartDelimiterGroupName); 130 string end_delimiter = String.Format(@"(?<{0}>$|\s|\W)", EndDelimiterGroupName); 131 132 // facebook attachment 133 var regex = new Regex( 134 @"(<[1-9][0-9]* attachments?>) (http://www\.facebook\.com/messages/\?action=read&tid=[0-9a-f]+)", 135 RegexOptions.Compiled 136 ); 137 BuiltinPatterns.Add(new MessagePatternModel(regex) { 138 LinkFormat = "{2}", 139 TextFormat = "{1}", 140 }); 141 142 // protocol://user@domain:port/path 143 regex = new Regex( 144 protocol_user_host_port_path, 145 RegexOptions.IgnoreCase | RegexOptions.Compiled 146 ); 147 BuiltinPatterns.Add(new MessagePatternModel(regex)); 148 149 // email 150 regex = new Regex( 151 @"(?:mailto:)?(" + user_domain + ")", 152 RegexOptions.IgnoreCase | RegexOptions.Compiled 153 ); 154 BuiltinPatterns.Add(new MessagePatternModel(regex) { 155 LinkFormat = "mailto:{1}" 156 }); 157 158 // bitcoin address 159 var bitcoin_address = @"[13][a-km-zA-HJ-NP-Z1-9]{25,34}"; 160 var query = @"(\?[" + path_chars + @"]+)?"; 161 var bitcoin_address_query = bitcoin_address + query; 162 regex = new Regex( 163 @"bitcoin:(" + bitcoin_address_query + @")", 164 RegexOptions.IgnoreCase | RegexOptions.Compiled 165 ); 166 BuiltinPatterns.Add(new MessagePatternModel(regex) { 167 LinkFormat = "bitcoin:{1}" 168 }); 169 170 // bitcoin address (heuristical) 171 regex = new Regex( 172 start_delimiter + @"(" + bitcoin_address_query + @")" + end_delimiter, 173 RegexOptions.IgnoreCase | RegexOptions.Compiled 174 ); 175 BuiltinPatterns.Add(new MessagePatternModel(regex) { 176 LinkFormat = "bitcoin:{1}" 177 }); 178 179 // bitcoin tx hash 180 var bitcoin_tx_hash = @"[a-fA-F0-9]{64}"; 181 regex = new Regex( 182 start_delimiter + @"(" + bitcoin_tx_hash + @")" + end_delimiter, 183 RegexOptions.IgnoreCase | RegexOptions.Compiled 184 ); 185 BuiltinPatterns.Add(new MessagePatternModel(regex) { 186 LinkFormat = "https://blockchain.info/tx/{1}" 187 }); 188 189 // addresses without protocol (heuristical) 190 // include well known TLDs to prevent autogen.sh, configure.ac or 191 // Gst.Buffer.Unref() from matching 192 string heuristic_domain = @"(?:(?:" + subdomain + ")+(?:" + common_tld + ")|localhost)"; 193 string heuristic_address = @"(" +heuristic_domain + ")(?:" + path + ")?" + end_delimiter; 194 regex = new Regex( 195 heuristic_address, 196 RegexOptions.IgnoreCase | RegexOptions.Compiled 197 ); 198 BuiltinPatterns.Add(new MessagePatternModel(regex) { 199 LinkFormat = "http://{1}" 200 }); 201 202 // Smuxi bugtracker 203 regex = new Regex(@"smuxi#([0-9]+)", 204 RegexOptions.IgnoreCase | RegexOptions.Compiled); 205 BuiltinPatterns.Add(new MessagePatternModel(regex) { 206 LinkFormat = "https://smuxi.im/issues/show/{1}" 207 }); 208 209 // RFCs 210 regex = new Regex(@"RFC[ -]?([0-9]+) (?:s\.|ss\.|sec\.|sect\.|section) ?([1-9][0-9.]*)", 211 RegexOptions.IgnoreCase | RegexOptions.Compiled); 212 BuiltinPatterns.Add(new MessagePatternModel(regex) { 213 LinkFormat = "http://tools.ietf.org/html/rfc{1}#section-{2}" 214 }); 215 regex = new Regex(@"RFC[ -]?([0-9]+) (?:p\.|pp\.|page) ?(" + short_number + ")", 216 RegexOptions.IgnoreCase | RegexOptions.Compiled); 217 BuiltinPatterns.Add(new MessagePatternModel(regex) { 218 LinkFormat = "http://tools.ietf.org/html/rfc{1}#page-{2}" 219 }); 220 regex = new Regex(@"RFC[ -]?([0-9]+)", 221 RegexOptions.IgnoreCase | RegexOptions.Compiled); 222 BuiltinPatterns.Add(new MessagePatternModel(regex) { 223 LinkFormat = "http://www.ietf.org/rfc/rfc{1}.txt" 224 }); 225 226 // XEPs 227 regex = new Regex(@"XEP[ -]?([0-9]{4})", 228 RegexOptions.IgnoreCase | RegexOptions.Compiled); 229 BuiltinPatterns.Add(new MessagePatternModel(regex) { 230 LinkFormat = "http://xmpp.org/extensions/xep-{1}.html" 231 }); 232 233 // ISO 234 regex = new Regex(@"ISO[ -]?([0-9]{4,5}(?:-[0-9]+)?(?::[0-9]{4})?)", 235 RegexOptions.IgnoreCase | RegexOptions.Compiled); 236 BuiltinPatterns.Add(new MessagePatternModel(regex) { 237 LinkFormat = "http://www.iso.org/iso/home/search.htm?qt={1}&published=on" 238 }); 239 240 // ECMA 241 regex = new Regex(@"ECMA[ -]?([0-9]{1,4})", 242 RegexOptions.IgnoreCase | RegexOptions.Compiled); 243 BuiltinPatterns.Add(new MessagePatternModel(regex) { 244 LinkFormat = "http://www.ecma-international.org/publications/standards/ECMA-{1}.htm" 245 }); 246 247 // IEEE: IEEE-1394b, IEEE 802.11, IEEE 802.1ap-2008, IEEE 802.1AEbn-2011 248 regex = new Regex(@"IEEE[ -]?([0-9]{1,4}(?:\.[0-9]{1,4})?(?:[a-z]{1,4})?(?:-[0-9]{4})?)", 249 RegexOptions.IgnoreCase | RegexOptions.Compiled); 250 BuiltinPatterns.Add(new MessagePatternModel(regex) { 251 LinkFormat = "http://odysseus.ieee.org/query.html?qt={1}&style=standard" 252 }); 253 254 // bugtracker prefixes are taken from: 255 // http://en.opensuse.org/openSUSE:Packaging_Patches_guidelines#Current_set_of_abbreviations 256 257 // boost bugtracker 258 regex = new Regex(@"boost#([0-9]+)", 259 RegexOptions.IgnoreCase | RegexOptions.Compiled); 260 BuiltinPatterns.Add(new MessagePatternModel(regex) { 261 LinkFormat = "https://svn.boost.org/trac/boost/ticket/{1}" 262 }); 263 264 // Claws bugtracker 265 regex = new Regex(@"claws#([0-9]+)", 266 RegexOptions.IgnoreCase | RegexOptions.Compiled); 267 BuiltinPatterns.Add(new MessagePatternModel(regex) { 268 LinkFormat = "http://www.thewildbeast.co.uk/claws-mail/bugzilla/show_bug.cgi?id={1}" 269 }); 270 271 // CVE list 272 regex = new Regex(@"CVE-[0-9]{4}-[0-9]{4,}", 273 RegexOptions.IgnoreCase | RegexOptions.Compiled); 274 BuiltinPatterns.Add(new MessagePatternModel(regex) { 275 LinkFormat = "http://cve.mitre.org/cgi-bin/cvename.cgi?name={0}" 276 }); 277 278 // CPAN bugtracker 279 regex = new Regex(@"RT#([0-9]+)", 280 RegexOptions.IgnoreCase | RegexOptions.Compiled); 281 BuiltinPatterns.Add(new MessagePatternModel(regex) { 282 LinkFormat = "http://rt.cpan.org/Public/Bug/Display.html?id={1}" 283 }); 284 285 // Debian bugtracker 286 regex = new Regex(@"deb#([0-9]+)", 287 RegexOptions.IgnoreCase | RegexOptions.Compiled); 288 BuiltinPatterns.Add(new MessagePatternModel(regex) { 289 LinkFormat = "http://bugs.debian.org/{1}" 290 }); 291 292 // Debian Security Advisories (DSA) 293 regex = new Regex(@"DSA[ -]?([0-9]{4})(-[0-9]{1,2})?", 294 RegexOptions.IgnoreCase | RegexOptions.Compiled); 295 BuiltinPatterns.Add(new MessagePatternModel(regex) { 296 LinkFormat = "http://www.debian.org/security/dsa-{1}" 297 }); 298 299 // openSUSE feature tracker 300 regex = new Regex(@"fate#([0-9]+)", 301 RegexOptions.IgnoreCase | RegexOptions.Compiled); 302 BuiltinPatterns.Add(new MessagePatternModel(regex) { 303 LinkFormat = "http://features.opensuse.org/{1}" 304 }); 305 306 // freedesktop bugtracker 307 regex = new Regex(@"fdo#([0-9]+)", 308 RegexOptions.IgnoreCase | RegexOptions.Compiled); 309 BuiltinPatterns.Add(new MessagePatternModel(regex) { 310 LinkFormat = "http://bugs.freedesktop.org/{1}" 311 }); 312 313 // GNU bugtracker 314 regex = new Regex(@"gnu#([0-9]+)", 315 RegexOptions.IgnoreCase | RegexOptions.Compiled); 316 BuiltinPatterns.Add(new MessagePatternModel(regex) { 317 LinkFormat = "http://debbugs.gnu.org/{1}" 318 }); 319 320 // GCC bugtracker 321 regex = new Regex(@"gcc#([0-9]+)", 322 RegexOptions.IgnoreCase | RegexOptions.Compiled); 323 BuiltinPatterns.Add(new MessagePatternModel(regex) { 324 LinkFormat = "http://gcc.gnu.org/bugzilla/show_bug.cgi?id={1}" 325 }); 326 327 // GNOME bugtracker 328 regex = new Regex(@"bgo#([0-9]+)", 329 RegexOptions.IgnoreCase | RegexOptions.Compiled); 330 BuiltinPatterns.Add(new MessagePatternModel(regex) { 331 LinkFormat = "http://bugzilla.gnome.org/{1}" 332 }); 333 334 // KDE bugtracker 335 regex = new Regex(@"kde#([0-9]+)", 336 RegexOptions.IgnoreCase | RegexOptions.Compiled); 337 BuiltinPatterns.Add(new MessagePatternModel(regex) { 338 LinkFormat = "http://bugs.kde.org/{1}" 339 }); 340 341 // kernel bugtracker 342 regex = new Regex(@"bko#([0-9]+)", 343 RegexOptions.IgnoreCase | RegexOptions.Compiled); 344 BuiltinPatterns.Add(new MessagePatternModel(regex) { 345 LinkFormat = "http://bugzilla.kernel.org/show_bug.cgi?id={1}" 346 }); 347 348 // launchpad bugtracker 349 regex = new Regex(@"LP#([0-9]+)", 350 RegexOptions.IgnoreCase | RegexOptions.Compiled); 351 BuiltinPatterns.Add(new MessagePatternModel(regex) { 352 LinkFormat = "http://launchpad.net/bugs/{1}" 353 }); 354 355 // Mozilla bugtracker 356 regex = new Regex(@"bmo#([0-9]+)", 357 RegexOptions.IgnoreCase | RegexOptions.Compiled); 358 BuiltinPatterns.Add(new MessagePatternModel(regex) { 359 LinkFormat = "http://bugzilla.mozilla.org/{1}" 360 }); 361 362 // Novell bugtracker 363 regex = new Regex(@"bnc#([0-9]+)", 364 RegexOptions.IgnoreCase | RegexOptions.Compiled); 365 BuiltinPatterns.Add(new MessagePatternModel(regex) { 366 LinkFormat = "http://bugzilla.novell.com/{1}" 367 }); 368 369 // Redhat bugtracker 370 regex = new Regex(@"rh#([0-9]+)", 371 RegexOptions.IgnoreCase | RegexOptions.Compiled); 372 BuiltinPatterns.Add(new MessagePatternModel(regex) { 373 LinkFormat = "http://bugzilla.redhat.com/{1}" 374 }); 375 376 // Samba bugtracker 377 regex = new Regex(@"bso#([0-9]+)", 378 RegexOptions.IgnoreCase | RegexOptions.Compiled); 379 BuiltinPatterns.Add(new MessagePatternModel(regex) { 380 LinkFormat = "http://bugzilla.samba.org/show_bug.cgi?id={1}" 381 }); 382 383 // sourceforge bugtracker 384 regex = new Regex(@"sf#([0-9]+)", 385 RegexOptions.IgnoreCase | RegexOptions.Compiled); 386 BuiltinPatterns.Add(new MessagePatternModel(regex) { 387 LinkFormat = "http://sf.net/support/tracker.php?aid={1}" 388 }); 389 390 // Xfce bugtracker 391 regex = new Regex(@"bxo#([0-9]+)", 392 RegexOptions.IgnoreCase | RegexOptions.Compiled); 393 BuiltinPatterns.Add(new MessagePatternModel(regex) { 394 LinkFormat = "http://bugzilla.xfce.org/show_bug.cgi?id={1}" 395 }); 396 397 // Xamarin bugtracker 398 regex = new Regex(@"bxc#([0-9]+)", 399 RegexOptions.IgnoreCase | RegexOptions.Compiled); 400 BuiltinPatterns.Add(new MessagePatternModel(regex) { 401 LinkFormat = "http://bugzilla.xamarin.com/show_bug.cgi?id={1}" 402 }); 403 404 // TODO: msgid -> http://mid.gmane.org/{1} 405 // TODO: ISSN/ISBN 406 // TODO: Path: / or X:\ 407 // TODO: GPS -> Google Maps 408 // TODO: IP -> Browser / Whois 409 // TODO: Domain -> Browser / Whois 410 // TODO: ANSI 411 // TODO: maybe more on http://ikiwiki.info/shortcuts/ 412 // TODO: JID 413 } 414 ApplyConfig(UserConfig userConfig)415 public void ApplyConfig(UserConfig userConfig) 416 { 417 if (userConfig == null) { 418 throw new ArgumentNullException("userConfig"); 419 } 420 421 NickColors = (bool) userConfig["Interface/Notebook/Channel/NickColors"]; 422 StripColors = (bool) userConfig["Interface/Notebook/StripColors"]; 423 StripFormattings = (bool) userConfig["Interface/Notebook/StripFormattings"]; 424 HighlightColor = TextColor.Parse( 425 (string) userConfig["Interface/Notebook/Tab/HighlightColor"] 426 ); 427 HighlightWords = new List<string>( 428 (string[]) userConfig["Interface/Chat/HighlightWords"] 429 ); 430 431 var patternController = new MessagePatternListController(userConfig); 432 var userPatterns = patternController.GetList(); 433 var builtinPatterns = BuiltinPatterns; 434 var patterns = new List<MessagePatternModel>(builtinPatterns.Count + 435 userPatterns.Count); 436 // No need to lock BuiltinPatterns as List<T> is thread-safe for 437 // multiple readers as long as there is no writer at the same time. 438 // BuiltinPatterns is only written once before the first instance 439 // of MessageBuilderSettings is created via the static initializer. 440 patterns.AddRange(builtinPatterns); 441 patterns.AddRange(userPatterns); 442 Patterns = patterns; 443 UserPatterns = userPatterns; 444 445 // The Patterns property has to be initialized before we set the 446 // Emojis property as it will modify the existing Patterns collection 447 Emojis = (bool) userConfig["Interface/Chat/Emojis"]; 448 } 449 } 450 } 451