1 /++ 2 + Module for parsing ISUPPORT replies. 3 +/ 4 module virc.numerics.isupport; 5 6 import std.range.primitives : isForwardRange; 7 8 import virc.numerics.definitions; 9 10 enum defaultModePrefixesMap = ['o': '@', 'v': '+']; 11 enum defaultModePrefixes = "@+"; 12 /++ 13 + 14 +/ 15 enum ISupportToken { 16 /// 17 accept = "ACCEPT", 18 /// 19 awayLen = "AWAYLEN", 20 /// 21 callerID = "CALLERID", 22 /// 23 caseMapping = "CASEMAPPING", 24 /// 25 chanLimit = "CHANLIMIT", 26 /// 27 chanModes = "CHANMODES", 28 /// 29 channelLen = "CHANNELLEN", 30 /// 31 chanTypes = "CHANTYPES", 32 /// 33 charSet = "CHARSET", 34 /// 35 chIdLen = "CHIDLEN", 36 /// 37 cNotice = "CNOTICE", 38 /// 39 cPrivmsg = "CPRIVMSG", 40 /// 41 deaf = "DEAF", 42 /// 43 eList = "ELIST", 44 /// 45 eSilence = "ESILENCE", 46 /// 47 excepts = "EXCEPTS", 48 /// 49 extBan = "EXTBAN", 50 /// 51 fnc = "FNC", 52 /// 53 idChan = "IDCHAN", 54 /// 55 invEx = "INVEX", 56 /// 57 kickLen = "KICKLEN", 58 /// 59 knock = "KNOCK", 60 /// 61 language = "LANGUAGE", 62 /// 63 lineLen = "LINELEN", 64 /// 65 map = "MAP", 66 /// 67 maxBans = "MAXBANS", 68 /// 69 maxChannels = "MAXCHANNELS", 70 /// 71 maxList = "MAXLIST", 72 /// 73 maxPara = "MAXPARA", 74 /// 75 maxTargets = "MAXTARGETS", 76 /// 77 metadata = "METADATA", 78 /// 79 modes = "MODES", 80 /// 81 monitor = "MONITOR", 82 /// 83 namesX = "NAMESX", 84 /// 85 network = "NETWORK", 86 /// 87 nickLen = "NICKLEN", 88 /// 89 noQuit = "NOQUIT", 90 /// 91 operLog = "OPERLOG", 92 /// 93 override_ = "OVERRIDE", 94 /// 95 penalty = "PENALTY", 96 /// 97 prefix = "PREFIX", 98 /// 99 remove = "REMOVE", 100 /// 101 rfc2812 = "RFC2812", 102 /// 103 safeList = "SAFELIST", 104 /// 105 secureList = "SECURELIST", 106 /// 107 silence = "SILENCE", 108 /// 109 ssl = "SSL", 110 /// 111 startTLS = "STARTTLS", 112 /// 113 statusMsg = "STATUSMSG", 114 /// 115 std = "STD", 116 /// 117 targMax = "TARGMAX", 118 /// 119 topicLen = "TOPICLEN", 120 /// 121 uhNames = "UHNAMES", 122 /// 123 userIP = "USERIP", 124 /// 125 userLen = "USERLEN", 126 /// 127 vBanList = "VBANLIST", 128 /// 129 vChans = "VCHANS", 130 /// 131 wallChOps = "WALLCHOPS", 132 /// 133 wallVoices = "WALLVOICES", 134 /// 135 watch = "WATCH", 136 /// 137 whoX = "WHOX" 138 } 139 140 import std.typecons : Nullable; 141 private void setToken(T : ulong)(ref T opt, Nullable!string value, T defaultIfNotPresent, T negateValue) { 142 import std.conv : parse; 143 if (value.isNull) { 144 opt = negateValue; 145 } else { 146 try { 147 opt = parse!T(value.get); 148 } catch (Exception) { 149 opt = defaultIfNotPresent; 150 } 151 } 152 } 153 private void setToken(T : ulong)(ref Nullable!T opt, Nullable!string value, T defaultIfNotPresent) { 154 import std.conv : parse; 155 if (value.isNull) { 156 opt.nullify(); 157 } else { 158 if (value == "") { 159 opt = defaultIfNotPresent; 160 } else { 161 try { 162 opt = parse!T(value.get); 163 } catch (Exception) { 164 opt.nullify(); 165 } 166 } 167 } 168 } 169 private void setToken(T: char)(ref Nullable!T opt, Nullable!string value, T defaultIfNotPresent) { 170 import std.utf : byCodeUnit; 171 if (value.isNull) { 172 opt.nullify(); 173 } else { 174 if (value.get == "") { 175 opt = defaultIfNotPresent; 176 } else { 177 opt = value.get.byCodeUnit.front; 178 } 179 } 180 } 181 private void setToken(T : bool)(ref T opt, Nullable!string val, T = T.init) { 182 opt = !val.isNull; 183 } 184 private void setToken(T : string)(ref T opt, Nullable!string val, T defaultIfNotPresent = "") { 185 if (val.isNull) { 186 opt = defaultIfNotPresent; 187 } else { 188 opt = val.get; 189 } 190 } 191 private void setToken(T : string)(ref Nullable!T opt, Nullable!string val) { 192 if (!val.isNull) { 193 opt = val.get; 194 } else { 195 opt.nullify(); 196 } 197 } 198 private void setToken(T)(ref Nullable!T opt, Nullable!string val) { 199 if (!val.isNull) { 200 try { 201 opt = val.get.to!T; 202 } catch (Exception) { 203 opt.nullify(); 204 } 205 } else { 206 opt.nullify(); 207 } 208 } 209 210 private struct TokenPair { 211 string key; 212 Nullable!string value; 213 } 214 /++ 215 + Extended bans supported by the server and what they look like. 216 +/ 217 struct BanExtension { 218 ///Prefix character for the ban, if applicable 219 Nullable!char prefix; 220 ///Types of extended bans supported by the server 221 string banTypes; 222 } 223 /++ 224 + 225 +/ 226 struct ISupport { 227 import std.typecons : Nullable; 228 import virc.casemapping : CaseMapping; 229 import virc.modes : ModeType; 230 /// 231 char[char] prefixes; 232 /// 233 string channelTypes = "#&!+"; //RFC2811 specifies four channel types. 234 /// 235 ModeType[char] channelModeTypes; 236 /// 237 ulong maxModesPerCommand = 3; 238 /// 239 ulong[char] chanLimits; 240 /// 241 ulong nickLength = 9; 242 /// 243 ulong[char] maxList; 244 /// 245 string network; 246 /// 247 Nullable!char banExceptions; 248 /// 249 Nullable!char inviteExceptions; 250 /// 251 bool wAllChannelOps; 252 /// 253 bool wAllChannelVoices; 254 /// 255 string statusMessage; 256 /// 257 CaseMapping caseMapping; 258 /// 259 string extendedList; 260 /// 261 Nullable!ulong topicLength; 262 /// 263 ulong kickLength = ulong.max; 264 /// 265 Nullable!ulong userLength; 266 /// 267 ulong channelLength = 200; 268 /// 269 ulong[char] channelIDLengths; 270 /// 271 Nullable!string standard; 272 /// 273 Nullable!ulong silence; 274 /// 275 bool extendedSilence; 276 /// 277 bool rfc2812; 278 /// 279 bool penalty; 280 /// 281 bool forcedNickChanges; 282 /// 283 bool safeList; 284 /// 285 ulong awayLength = ulong.max; 286 /// 287 bool noQuit; 288 /// 289 bool userIP; 290 /// 291 bool cPrivmsg; 292 /// 293 bool cNotice; 294 /// 295 ulong maxTargets = ulong.max; 296 /// 297 bool knock; 298 /// 299 bool virtualChannels; 300 /// 301 Nullable!ulong maximumWatches; 302 /// 303 bool whoX; 304 /// 305 Nullable!char callerID; 306 /// 307 string[] languages; 308 /// 309 ulong maxLanguages; 310 /// 311 bool startTLS; //DANGEROUS! 312 /// 313 Nullable!BanExtension banExtensions; 314 /// 315 bool logsOperCommands; 316 /// 317 string sslServer; 318 /// 319 bool userhostsInNames; 320 /// 321 bool namesExtended; 322 /// 323 bool secureList; 324 /// 325 bool supportsRemove; 326 /// 327 bool allowsOperOverride; 328 /// 329 bool variableBanList; 330 /// 331 bool supportsMap; 332 /// 333 ulong maximumParameters = 12; 334 /// 335 ulong lineLength = 512; 336 /// 337 Nullable!char deaf; 338 /// 339 Nullable!ulong metadata; 340 /// 341 Nullable!ulong monitorTargetLimit; 342 /// 343 ulong[string] targetMaxByCommand; 344 /// 345 string charSet; 346 /// 347 string[string] unknownTokens; 348 /// 349 void insertToken(string token, Nullable!string val) @safe pure { 350 import std.algorithm.iteration : splitter; 351 import std.algorithm.searching : findSplit; 352 import std.conv : parse, to; 353 import std.meta : AliasSeq; 354 import std.range : empty, popFront, zip; 355 import std.string : toLower; 356 import std.utf : byCodeUnit; 357 switch (cast(ISupportToken)token) { 358 case ISupportToken.chanModes: 359 channelModeTypes = channelModeTypes.init; 360 if (!val.isNull) { 361 auto splitModes = val.get.splitter(","); 362 foreach (modeType; AliasSeq!(ModeType.a, ModeType.b, ModeType.c, ModeType.d)) { 363 if (splitModes.empty) { 364 break; 365 } 366 foreach (modeChar; splitModes.front) { 367 channelModeTypes[modeChar] = modeType; 368 } 369 splitModes.popFront(); 370 } 371 } else { 372 channelModeTypes = channelModeTypes.init; 373 } 374 break; 375 case ISupportToken.prefix: 376 if (!val.isNull) { 377 if (val.get == "") { 378 prefixes = prefixes.init; 379 } else { 380 auto split = val.get.findSplit(")"); 381 split[0].popFront(); 382 foreach (modeChar, prefix; zip(split[0].byCodeUnit, split[2].byCodeUnit)) { 383 prefixes[modeChar] = prefix; 384 if (modeChar !in channelModeTypes) { 385 channelModeTypes[modeChar] = ModeType.d; 386 } 387 } 388 } 389 } else { 390 prefixes = defaultModePrefixesMap; 391 } 392 break; 393 case ISupportToken.chanTypes: 394 setToken(channelTypes, val, "#&!+"); 395 break; 396 case ISupportToken.wallChOps: 397 setToken(wAllChannelOps, val); 398 break; 399 case ISupportToken.wallVoices: 400 setToken(wAllChannelVoices, val); 401 break; 402 case ISupportToken.statusMsg: 403 setToken(statusMessage, val); 404 break; 405 case ISupportToken.extBan: 406 if (val.isNull) { 407 banExtensions.nullify(); 408 } else { 409 banExtensions = BanExtension(); 410 auto split = val.get.findSplit(","); 411 if (split[1] == ",") { 412 if (!split[0].empty) { 413 banExtensions.get.prefix = split[0].byCodeUnit.front; 414 } 415 banExtensions.get.banTypes = split[2]; 416 } else { 417 banExtensions.nullify(); 418 } 419 } 420 break; 421 case ISupportToken.fnc: 422 setToken(forcedNickChanges, val); 423 break; 424 case ISupportToken.userIP: 425 setToken(userIP, val); 426 break; 427 case ISupportToken.cPrivmsg: 428 setToken(cPrivmsg, val); 429 break; 430 case ISupportToken.cNotice: 431 setToken(cNotice, val); 432 break; 433 case ISupportToken.knock: 434 setToken(knock, val); 435 break; 436 case ISupportToken.vChans: 437 setToken(virtualChannels, val); 438 break; 439 case ISupportToken.whoX: 440 setToken(whoX, val); 441 break; 442 case ISupportToken.awayLen: 443 setToken(awayLength, val, ulong.max, ulong.max); 444 break; 445 case ISupportToken.nickLen: 446 setToken(nickLength, val, 9, 9); 447 break; 448 case ISupportToken.lineLen: 449 setToken(lineLength, val, 512, 512); 450 break; 451 case ISupportToken.channelLen: 452 setToken(channelLength, val, ulong.max, 200); 453 break; 454 case ISupportToken.kickLen: 455 setToken(kickLength, val, ulong.max, ulong.max); 456 break; 457 case ISupportToken.userLen: 458 setToken(userLength, val, ulong.max); 459 break; 460 case ISupportToken.topicLen: 461 setToken(topicLength, val, ulong.max); 462 break; 463 case ISupportToken.maxBans: 464 if (val.isNull) { 465 maxList.remove('b'); 466 } else { 467 maxList['b'] = 0; 468 setToken(maxList['b'], val, ulong.max, ulong.max); 469 } 470 break; 471 case ISupportToken.modes: 472 setToken(maxModesPerCommand, val, ulong.max, 3); 473 break; 474 case ISupportToken.watch: 475 setToken(maximumWatches, val, ulong.max); 476 break; 477 case ISupportToken.metadata: 478 setToken(metadata, val, ulong.max); 479 break; 480 case ISupportToken.monitor: 481 setToken(monitorTargetLimit, val, ulong.max); 482 break; 483 case ISupportToken.maxList: 484 if (val.isNull) { 485 maxList = maxList.init; 486 } else { 487 auto splitModes = val.get.splitter(","); 488 foreach (listEntry; splitModes) { 489 auto splitArgs = listEntry.findSplit(":"); 490 immutable limit = parse!ulong(splitArgs[2]); 491 foreach (modeChar; splitArgs[0]) { 492 maxList[modeChar] = limit; 493 } 494 } 495 } 496 break; 497 case ISupportToken.targMax: 498 targetMaxByCommand = targetMaxByCommand.init; 499 if (!val.isNull) { 500 auto splitCmd = val.get.splitter(","); 501 foreach (listEntry; splitCmd) { 502 auto splitArgs = listEntry.findSplit(":"); 503 if (splitArgs[2].empty) { 504 targetMaxByCommand[splitArgs[0]] = ulong.max; 505 } else { 506 immutable limit = parse!ulong(splitArgs[2]); 507 targetMaxByCommand[splitArgs[0]] = limit; 508 } 509 } 510 } 511 break; 512 case ISupportToken.chanLimit: 513 if (!val.isNull) { 514 auto splitPrefix = val.get.splitter(","); 515 foreach (listEntry; splitPrefix) { 516 auto splitArgs = listEntry.findSplit(":"); 517 if (splitArgs[1] != ":") { 518 chanLimits = chanLimits.init; 519 break; 520 } 521 try { 522 immutable limit = parse!ulong(splitArgs[2]); 523 foreach (prefix; splitArgs[0]) { 524 chanLimits[prefix] = limit; 525 } 526 } catch (Exception) { 527 if (splitArgs[2] == "") { 528 foreach (prefix; splitArgs[0]) { 529 chanLimits[prefix] = ulong.max; 530 } 531 } else { 532 chanLimits = chanLimits.init; 533 break; 534 } 535 } 536 } 537 } else { 538 chanLimits = chanLimits.init; 539 } 540 break; 541 case ISupportToken.maxTargets: 542 setToken(maxTargets, val, ulong.max, ulong.max); 543 break; 544 case ISupportToken.maxChannels: 545 if (val.isNull) { 546 chanLimits.remove('#'); 547 } else { 548 chanLimits['#'] = 0; 549 setToken(chanLimits['#'], val, ulong.max, ulong.max); 550 } 551 break; 552 case ISupportToken.maxPara: 553 setToken(maximumParameters, val, 12, 12); 554 break; 555 case ISupportToken.startTLS: 556 setToken(startTLS, val); 557 break; 558 case ISupportToken.ssl: 559 setToken(sslServer, val, ""); 560 break; 561 case ISupportToken.operLog: 562 setToken(logsOperCommands, val); 563 break; 564 case ISupportToken.silence: 565 setToken(silence, val, ulong.max); 566 break; 567 case ISupportToken.network: 568 setToken(network, val); 569 break; 570 case ISupportToken.caseMapping: 571 if (val.isNull) { 572 caseMapping = CaseMapping.unknown; 573 } else { 574 switch (val.get.toLower()) { 575 case CaseMapping.rfc1459: 576 caseMapping = CaseMapping.rfc1459; 577 break; 578 case CaseMapping.rfc3454: 579 caseMapping = CaseMapping.rfc3454; 580 break; 581 case CaseMapping.strictRFC1459: 582 caseMapping = CaseMapping.strictRFC1459; 583 break; 584 case CaseMapping.ascii: 585 caseMapping = CaseMapping.ascii; 586 break; 587 default: 588 caseMapping = CaseMapping.unknown; 589 break; 590 } 591 } 592 break; 593 case ISupportToken.charSet: 594 //Has serious issues and has been removed from drafts 595 //So we leave this one unparsed 596 setToken(charSet, val, ""); 597 break; 598 case ISupportToken.uhNames: 599 setToken(userhostsInNames, val); 600 break; 601 case ISupportToken.namesX: 602 setToken(namesExtended, val); 603 break; 604 case ISupportToken.invEx: 605 setToken(inviteExceptions, val, 'I'); 606 break; 607 case ISupportToken.excepts: 608 setToken(banExceptions, val, 'e'); 609 break; 610 case ISupportToken.callerID, ISupportToken.accept: 611 setToken(callerID, val, 'g'); 612 break; 613 case ISupportToken.deaf: 614 setToken(deaf, val, 'd'); 615 break; 616 case ISupportToken.eList: 617 setToken(extendedList, val, ""); 618 break; 619 case ISupportToken.secureList: 620 setToken(secureList, val); 621 break; 622 case ISupportToken.noQuit: 623 setToken(noQuit, val); 624 break; 625 case ISupportToken.remove: 626 setToken(supportsRemove, val); 627 break; 628 case ISupportToken.eSilence: 629 setToken(extendedSilence, val); 630 break; 631 case ISupportToken.override_: 632 setToken(allowsOperOverride, val); 633 break; 634 case ISupportToken.vBanList: 635 setToken(variableBanList, val); 636 break; 637 case ISupportToken.map: 638 setToken(supportsMap, val); 639 break; 640 case ISupportToken.safeList: 641 setToken(safeList, val); 642 break; 643 case ISupportToken.chIdLen: 644 if (val.isNull) { 645 channelIDLengths.remove('!'); 646 } else { 647 channelIDLengths['!'] = 0; 648 setToken(channelIDLengths['!'], val, ulong.max, ulong.max); 649 } 650 //channelIDLengths['!'] = parse!ulong(value); 651 break; 652 case ISupportToken.idChan: 653 if (val.isNull) { 654 channelIDLengths = channelIDLengths.init; 655 } else { 656 auto splitPrefix = val.get.splitter(","); 657 foreach (listEntry; splitPrefix) { 658 auto splitArgs = listEntry.findSplit(":"); 659 immutable limit = parse!ulong(splitArgs[2]); 660 foreach (prefix; splitArgs[0]) { 661 channelIDLengths[prefix] = limit; 662 } 663 } 664 } 665 break; 666 case ISupportToken.std: 667 setToken(standard, val); 668 break; 669 case ISupportToken.rfc2812: 670 setToken(rfc2812, val); 671 break; 672 case ISupportToken.penalty: 673 setToken(penalty, val); 674 break; 675 case ISupportToken.language: 676 if (val.isNull) { 677 languages = languages.init; 678 maxLanguages = 0; 679 } else { 680 auto splitLangs = val.get.splitter(","); 681 maxLanguages = to!ulong(splitLangs.front); 682 splitLangs.popFront(); 683 foreach (lang; splitLangs) 684 languages ~= lang; 685 } 686 break; 687 default: 688 if (val.isNull) { 689 if (token in unknownTokens) { 690 unknownTokens.remove(token); 691 } 692 } else { 693 unknownTokens[token] = val.get; 694 } 695 break; 696 } 697 } 698 private void insertToken(TokenPair pair) pure @safe { 699 insertToken(pair.key, pair.value); 700 } 701 } 702 /// 703 @safe pure unittest { 704 import virc.casemapping : CaseMapping; 705 import virc.modes : ModeType; 706 auto isupport = ISupport(); 707 { 708 assert(isupport.awayLength == ulong.max); 709 isupport.insertToken(keyValuePair("AWAYLEN=8")); 710 assert(isupport.awayLength == 8); 711 isupport.insertToken(keyValuePair("AWAYLEN=")); 712 assert(isupport.awayLength == ulong.max); 713 isupport.insertToken(keyValuePair("AWAYLEN=8")); 714 assert(isupport.awayLength == 8); 715 isupport.insertToken(keyValuePair("-AWAYLEN")); 716 assert(isupport.awayLength == ulong.max); 717 } 718 { 719 assert(isupport.callerID.isNull); 720 isupport.insertToken(keyValuePair("CALLERID=h")); 721 assert(isupport.callerID.get == 'h'); 722 isupport.insertToken(keyValuePair("CALLERID")); 723 assert(isupport.callerID.get == 'g'); 724 isupport.insertToken(keyValuePair("-CALLERID")); 725 assert(isupport.callerID.isNull); 726 } 727 { 728 assert(isupport.caseMapping == CaseMapping.unknown); 729 isupport.insertToken(keyValuePair("CASEMAPPING=rfc1459")); 730 assert(isupport.caseMapping == CaseMapping.rfc1459); 731 isupport.insertToken(keyValuePair("CASEMAPPING=ascii")); 732 assert(isupport.caseMapping == CaseMapping.ascii); 733 isupport.insertToken(keyValuePair("CASEMAPPING=rfc3454")); 734 assert(isupport.caseMapping == CaseMapping.rfc3454); 735 isupport.insertToken(keyValuePair("CASEMAPPING=strict-rfc1459")); 736 assert(isupport.caseMapping == CaseMapping.strictRFC1459); 737 isupport.insertToken(keyValuePair("-CASEMAPPING")); 738 assert(isupport.caseMapping == CaseMapping.unknown); 739 isupport.insertToken(keyValuePair("CASEMAPPING=something")); 740 assert(isupport.caseMapping == CaseMapping.unknown); 741 } 742 { 743 assert(isupport.chanLimits.length == 0); 744 isupport.insertToken(keyValuePair("CHANLIMIT=#+:25,&:")); 745 assert(isupport.chanLimits['#'] == 25); 746 assert(isupport.chanLimits['+'] == 25); 747 assert(isupport.chanLimits['&'] == ulong.max); 748 isupport.insertToken(keyValuePair("-CHANLIMIT")); 749 assert(isupport.chanLimits.length == 0); 750 isupport.insertToken(keyValuePair("CHANLIMIT=q")); 751 assert(isupport.chanLimits.length == 0); 752 isupport.insertToken(keyValuePair("CHANLIMIT=!:f")); 753 assert(isupport.chanLimits.length == 0); 754 } 755 { 756 assert(isupport.channelModeTypes.length == 0); 757 isupport.insertToken(keyValuePair("CHANMODES=b,k,l,imnpst")); 758 assert(isupport.channelModeTypes['b'] == ModeType.a); 759 assert(isupport.channelModeTypes['k'] == ModeType.b); 760 assert(isupport.channelModeTypes['l'] == ModeType.c); 761 assert(isupport.channelModeTypes['i'] == ModeType.d); 762 assert(isupport.channelModeTypes['t'] == ModeType.d); 763 isupport.insertToken(keyValuePair("CHANMODES=beI,k,l,BCMNORScimnpstz")); 764 assert(isupport.channelModeTypes['e'] == ModeType.a); 765 isupport.insertToken(keyValuePair("CHANMODES")); 766 assert(isupport.channelModeTypes.length == 0); 767 isupport.insertToken(keyValuePair("CHANMODES=w,,,")); 768 assert(isupport.channelModeTypes['w'] == ModeType.a); 769 assert('b' !in isupport.channelModeTypes); 770 isupport.insertToken(keyValuePair("-CHANMODES")); 771 assert(isupport.channelModeTypes.length == 0); 772 } 773 { 774 assert(isupport.channelLength == 200); 775 isupport.insertToken(keyValuePair("CHANNELLEN=50")); 776 assert(isupport.channelLength == 50); 777 isupport.insertToken(keyValuePair("CHANNELLEN=")); 778 assert(isupport.channelLength == ulong.max); 779 isupport.insertToken(keyValuePair("-CHANNELLEN")); 780 assert(isupport.channelLength == 200); 781 } 782 { 783 assert(isupport.channelTypes == "#&!+"); 784 isupport.insertToken(keyValuePair("CHANTYPES=&#")); 785 assert(isupport.channelTypes == "&#"); 786 isupport.insertToken(keyValuePair("CHANTYPES")); 787 assert(isupport.channelTypes == ""); 788 isupport.insertToken(keyValuePair("-CHANTYPES")); 789 assert(isupport.channelTypes == "#&!+"); 790 } 791 { 792 assert(isupport.charSet == ""); 793 isupport.insertToken(keyValuePair("CHARSET=ascii")); 794 assert(isupport.charSet == "ascii"); 795 isupport.insertToken(keyValuePair("CHARSET")); 796 assert(isupport.charSet == ""); 797 isupport.insertToken(keyValuePair("-CHARSET")); 798 assert(isupport.charSet == ""); 799 } 800 { 801 assert('!' !in isupport.channelIDLengths); 802 isupport.insertToken(keyValuePair("CHIDLEN=5")); 803 assert(isupport.channelIDLengths['!'] == 5); 804 isupport.insertToken(keyValuePair("-CHIDLEN")); 805 assert('!' !in isupport.channelIDLengths); 806 } 807 { 808 assert(!isupport.cNotice); 809 isupport.insertToken(keyValuePair("CNOTICE")); 810 assert(isupport.cNotice); 811 isupport.insertToken(keyValuePair("-CNOTICE")); 812 assert(!isupport.cNotice); 813 } 814 { 815 assert(!isupport.cPrivmsg); 816 isupport.insertToken(keyValuePair("CPRIVMSG")); 817 assert(isupport.cPrivmsg); 818 isupport.insertToken(keyValuePair("-CPRIVMSG")); 819 assert(!isupport.cPrivmsg); 820 } 821 { 822 assert(isupport.deaf.isNull); 823 isupport.insertToken(keyValuePair("DEAF=D")); 824 assert(isupport.deaf.get == 'D'); 825 isupport.insertToken(keyValuePair("DEAF")); 826 assert(isupport.deaf.get == 'd'); 827 isupport.insertToken(keyValuePair("-DEAF")); 828 assert(isupport.deaf.isNull); 829 } 830 { 831 assert(isupport.extendedList == ""); 832 isupport.insertToken(keyValuePair("ELIST=CMNTU")); 833 assert(isupport.extendedList == "CMNTU"); 834 isupport.insertToken(keyValuePair("-ELIST")); 835 assert(isupport.extendedList == ""); 836 } 837 { 838 assert(isupport.banExceptions.isNull); 839 isupport.insertToken(keyValuePair("EXCEPTS")); 840 assert(isupport.banExceptions == 'e'); 841 isupport.insertToken(keyValuePair("EXCEPTS=f")); 842 assert(isupport.banExceptions == 'f'); 843 isupport.insertToken(keyValuePair("EXCEPTS=e")); 844 assert(isupport.banExceptions == 'e'); 845 isupport.insertToken(keyValuePair("-EXCEPTS")); 846 assert(isupport.banExceptions.isNull); 847 } 848 { 849 assert(isupport.banExtensions.isNull); 850 isupport.insertToken(keyValuePair("EXTBAN=~,cqnr")); 851 assert(isupport.banExtensions.get.prefix == '~'); 852 assert(isupport.banExtensions.get.banTypes == "cqnr"); 853 isupport.insertToken(keyValuePair("EXTBAN=,ABCNOQRSTUcjmprsz")); 854 assert(isupport.banExtensions.get.prefix.isNull); 855 assert(isupport.banExtensions.get.banTypes == "ABCNOQRSTUcjmprsz"); 856 isupport.insertToken(keyValuePair("EXTBAN=~,qjncrRa")); 857 assert(isupport.banExtensions.get.prefix == '~'); 858 assert(isupport.banExtensions.get.banTypes == "qjncrRa"); 859 isupport.insertToken(keyValuePair("-EXTBAN")); 860 assert(isupport.banExtensions.isNull); 861 isupport.insertToken(keyValuePair("EXTBAN=8")); 862 assert(isupport.banExtensions.isNull); 863 } 864 { 865 assert(isupport.forcedNickChanges == false); 866 isupport.insertToken(keyValuePair("FNC")); 867 assert(isupport.forcedNickChanges == true); 868 isupport.insertToken(keyValuePair("-FNC")); 869 assert(isupport.forcedNickChanges == false); 870 } 871 { 872 assert(isupport.channelIDLengths.length == 0); 873 isupport.insertToken(keyValuePair("IDCHAN=!:5")); 874 assert(isupport.channelIDLengths['!'] == 5); 875 isupport.insertToken(keyValuePair("-IDCHAN")); 876 assert(isupport.channelIDLengths.length == 0); 877 } 878 { 879 assert(isupport.inviteExceptions.isNull); 880 isupport.insertToken(keyValuePair("INVEX")); 881 assert(isupport.inviteExceptions.get == 'I'); 882 isupport.insertToken(keyValuePair("INVEX=q")); 883 assert(isupport.inviteExceptions.get == 'q'); 884 isupport.insertToken(keyValuePair("INVEX=I")); 885 assert(isupport.inviteExceptions.get == 'I'); 886 isupport.insertToken(keyValuePair("-INVEX")); 887 assert(isupport.inviteExceptions.isNull); 888 } 889 { 890 assert(isupport.kickLength == ulong.max); 891 isupport.insertToken(keyValuePair("KICKLEN=180")); 892 assert(isupport.kickLength == 180); 893 isupport.insertToken(keyValuePair("KICKLEN=")); 894 assert(isupport.kickLength == ulong.max); 895 isupport.insertToken(keyValuePair("KICKLEN=2")); 896 isupport.insertToken(keyValuePair("-KICKLEN")); 897 assert(isupport.kickLength == ulong.max); 898 } 899 { 900 assert(!isupport.knock); 901 isupport.insertToken(keyValuePair("KNOCK")); 902 assert(isupport.knock); 903 isupport.insertToken(keyValuePair("-KNOCK")); 904 assert(!isupport.knock); 905 } 906 { 907 import std.algorithm.searching : canFind; 908 assert(isupport.languages.length == 0); 909 isupport.insertToken(keyValuePair("LANGUAGE=2,en,i-klingon")); 910 assert(isupport.languages.canFind("en")); 911 assert(isupport.languages.canFind("i-klingon")); 912 isupport.insertToken(keyValuePair("-LANGUAGE")); 913 assert(isupport.languages.length == 0); 914 } 915 { 916 assert(isupport.lineLength == 512); 917 isupport.insertToken(keyValuePair("LINELEN=2048")); 918 assert(isupport.lineLength == 2048); 919 isupport.insertToken(keyValuePair("LINELEN=512")); 920 assert(isupport.lineLength == 512); 921 isupport.insertToken(keyValuePair("LINELEN=2")); 922 assert(isupport.lineLength == 2); 923 isupport.insertToken(keyValuePair("-LINELEN")); 924 assert(isupport.lineLength == 512); 925 } 926 { 927 assert(!isupport.supportsMap); 928 isupport.insertToken(keyValuePair("MAP")); 929 assert(isupport.supportsMap); 930 isupport.insertToken(keyValuePair("-MAP")); 931 assert(!isupport.supportsMap); 932 } 933 { 934 assert('b' !in isupport.maxList); 935 isupport.insertToken(keyValuePair("MAXBANS=5")); 936 assert(isupport.maxList['b'] == 5); 937 isupport.insertToken(keyValuePair("-MAXBANS")); 938 assert('b' !in isupport.maxList); 939 } 940 { 941 assert('#' !in isupport.chanLimits); 942 isupport.insertToken(keyValuePair("MAXCHANNELS=25")); 943 assert(isupport.chanLimits['#'] == 25); 944 isupport.insertToken(keyValuePair("-MAXCHANNELS")); 945 assert('#' !in isupport.chanLimits); 946 } 947 { 948 assert(isupport.maxList.length == 0); 949 isupport.insertToken(keyValuePair("MAXLIST=beI:25")); 950 assert(isupport.maxList['b'] == 25); 951 assert(isupport.maxList['e'] == 25); 952 assert(isupport.maxList['I'] == 25); 953 isupport.insertToken(keyValuePair("MAXLIST=b:25,eI:50")); 954 assert(isupport.maxList['b'] == 25); 955 assert(isupport.maxList['e'] == 50); 956 assert(isupport.maxList['I'] == 50); 957 isupport.insertToken(keyValuePair("-MAXLIST")); 958 assert(isupport.maxList.length == 0); 959 } 960 { 961 assert(isupport.maximumParameters == 12); 962 isupport.insertToken(keyValuePair("MAXPARA=32")); 963 assert(isupport.maximumParameters == 32); 964 isupport.insertToken(keyValuePair("-MAXPARA")); 965 assert(isupport.maximumParameters == 12); 966 } 967 { 968 assert(isupport.maxTargets == ulong.max); 969 isupport.insertToken(keyValuePair("MAXTARGETS=8")); 970 assert(isupport.maxTargets == 8); 971 isupport.insertToken(keyValuePair("-MAXTARGETS")); 972 assert(isupport.maxTargets == ulong.max); 973 } 974 { 975 assert(isupport.metadata.isNull); 976 isupport.insertToken(keyValuePair("METADATA=30")); 977 assert(isupport.metadata == 30); 978 isupport.insertToken(keyValuePair("METADATA=x")); 979 assert(isupport.metadata.isNull); 980 isupport.insertToken(keyValuePair("METADATA")); 981 assert(isupport.metadata == ulong.max); 982 isupport.insertToken(keyValuePair("-METADATA")); 983 assert(isupport.metadata.isNull); 984 } 985 { 986 //As specified in RFC1459, default number of "variable" modes is 3 per command. 987 assert(isupport.maxModesPerCommand == 3); 988 isupport.insertToken(keyValuePair("MODES")); 989 assert(isupport.maxModesPerCommand == ulong.max); 990 isupport.insertToken(keyValuePair("MODES=3")); 991 assert(isupport.maxModesPerCommand == 3); 992 isupport.insertToken(keyValuePair("MODES=5")); 993 assert(isupport.maxModesPerCommand == 5); 994 isupport.insertToken(keyValuePair("-MODES")); 995 assert(isupport.maxModesPerCommand == 3); 996 } 997 { 998 assert(isupport.monitorTargetLimit.isNull); 999 isupport.insertToken(keyValuePair("MONITOR=6")); 1000 assert(isupport.monitorTargetLimit == 6); 1001 isupport.insertToken(keyValuePair("MONITOR")); 1002 assert(isupport.monitorTargetLimit == ulong.max); 1003 isupport.insertToken(keyValuePair("-MONITOR")); 1004 assert(isupport.monitorTargetLimit.isNull); 1005 } 1006 { 1007 assert(!isupport.namesExtended); 1008 isupport.insertToken(keyValuePair("NAMESX")); 1009 assert(isupport.namesExtended); 1010 isupport.insertToken(keyValuePair("-NAMESX")); 1011 assert(!isupport.namesExtended); 1012 } 1013 { 1014 assert(isupport.network == ""); 1015 isupport.insertToken(keyValuePair("NETWORK=EFNet")); 1016 assert(isupport.network == "EFNet"); 1017 isupport.insertToken(keyValuePair("NETWORK=Rizon")); 1018 assert(isupport.network == "Rizon"); 1019 isupport.insertToken(keyValuePair("-NETWORK")); 1020 assert(isupport.network == ""); 1021 } 1022 { 1023 assert(isupport.nickLength == 9); 1024 isupport.insertToken(keyValuePair("NICKLEN=32")); 1025 assert(isupport.nickLength == 32); 1026 isupport.insertToken(keyValuePair("NICKLEN=9")); 1027 assert(isupport.nickLength == 9); 1028 isupport.insertToken(keyValuePair("NICKLEN=32")); 1029 isupport.insertToken(keyValuePair("-NICKLEN")); 1030 assert(isupport.nickLength == 9); 1031 } 1032 { 1033 assert(!isupport.noQuit); 1034 isupport.insertToken(keyValuePair("NOQUIT")); 1035 assert(isupport.noQuit); 1036 isupport.insertToken(keyValuePair("-NOQUIT")); 1037 assert(!isupport.noQuit); 1038 } 1039 { 1040 assert(!isupport.allowsOperOverride); 1041 isupport.insertToken(keyValuePair("OVERRIDE")); 1042 assert(isupport.allowsOperOverride); 1043 isupport.insertToken(keyValuePair("-OVERRIDE")); 1044 assert(!isupport.allowsOperOverride); 1045 } 1046 { 1047 assert(!isupport.penalty); 1048 isupport.insertToken(keyValuePair("PENALTY")); 1049 assert(isupport.penalty); 1050 isupport.insertToken(keyValuePair("-PENALTY")); 1051 assert(!isupport.penalty); 1052 } 1053 { 1054 //assert(isupport.prefixes == ['o': '@', 'v': '+']); 1055 isupport.insertToken(keyValuePair("PREFIX")); 1056 assert(isupport.prefixes.length == 0); 1057 isupport.insertToken(keyValuePair("PREFIX=(ov)@+")); 1058 assert(isupport.prefixes == ['o': '@', 'v': '+']); 1059 isupport.insertToken(keyValuePair("PREFIX=(qaohv)~&@%+")); 1060 assert(isupport.prefixes == ['o': '@', 'v': '+', 'q': '~', 'a': '&', 'h': '%']); 1061 isupport.insertToken(keyValuePair("-PREFIX")); 1062 assert(isupport.prefixes == ['o': '@', 'v': '+']); 1063 } 1064 { 1065 assert(!isupport.rfc2812); 1066 isupport.insertToken(keyValuePair("RFC2812")); 1067 assert(isupport.rfc2812); 1068 isupport.insertToken(keyValuePair("-RFC2812")); 1069 assert(!isupport.rfc2812); 1070 } 1071 { 1072 assert(!isupport.safeList); 1073 isupport.insertToken(keyValuePair("SAFELIST")); 1074 assert(isupport.safeList); 1075 isupport.insertToken(keyValuePair("-SAFELIST")); 1076 assert(!isupport.safeList); 1077 } 1078 { 1079 assert(!isupport.secureList); 1080 isupport.insertToken(keyValuePair("SECURELIST")); 1081 assert(isupport.secureList); 1082 isupport.insertToken(keyValuePair("-SECURELIST")); 1083 assert(!isupport.secureList); 1084 } 1085 { 1086 assert(isupport.silence.isNull); 1087 isupport.insertToken(keyValuePair("SILENCE=15")); 1088 assert(isupport.silence == 15); 1089 isupport.insertToken(keyValuePair("SILENCE")); 1090 assert(isupport.silence == ulong.max); 1091 isupport.insertToken(keyValuePair("-SILENCE")); 1092 assert(isupport.silence.isNull); 1093 } 1094 { 1095 assert(isupport.sslServer == ""); 1096 isupport.insertToken(keyValuePair("SSL=1.2.3.4:6668;4.3.2.1:6669;*:6660;")); 1097 assert(isupport.sslServer == "1.2.3.4:6668;4.3.2.1:6669;*:6660;"); 1098 isupport.insertToken(keyValuePair("-SSL")); 1099 assert(isupport.sslServer == ""); 1100 } 1101 { 1102 assert(!isupport.startTLS); 1103 isupport.insertToken(keyValuePair("STARTTLS")); 1104 assert(isupport.startTLS); 1105 isupport.insertToken(keyValuePair("-STARTTLS")); 1106 assert(!isupport.startTLS); 1107 } 1108 { 1109 assert(isupport.statusMessage == ""); 1110 isupport.insertToken(keyValuePair("STATUSMSG=@+")); 1111 assert(isupport.statusMessage == "@+"); 1112 isupport.insertToken(keyValuePair("-STATUSMSG")); 1113 assert(isupport.statusMessage == ""); 1114 } 1115 { 1116 assert(isupport.standard.isNull); 1117 isupport.insertToken(keyValuePair("STD=i-d")); 1118 assert(isupport.standard == "i-d"); 1119 isupport.insertToken(keyValuePair("-STD")); 1120 assert(isupport.standard.isNull); 1121 } 1122 { 1123 assert(isupport.targetMaxByCommand.length == 0); 1124 isupport.insertToken(keyValuePair("TARGMAX=PRIVMSG:3,WHOIS:1,JOIN:")); 1125 assert(isupport.targetMaxByCommand["PRIVMSG"]== 3); 1126 assert(isupport.targetMaxByCommand["WHOIS"]== 1); 1127 assert(isupport.targetMaxByCommand["JOIN"]== ulong.max); 1128 isupport.insertToken(keyValuePair("TARGMAX")); 1129 assert(isupport.targetMaxByCommand.length == 0); 1130 isupport.insertToken(keyValuePair("-TARGMAX")); 1131 assert(isupport.targetMaxByCommand.length == 0); 1132 } 1133 { 1134 assert(isupport.topicLength.isNull); 1135 isupport.insertToken(keyValuePair("TOPICLEN=120")); 1136 assert(isupport.topicLength == 120); 1137 isupport.insertToken(keyValuePair("TOPICLEN=")); 1138 assert(isupport.topicLength == ulong.max); 1139 isupport.insertToken(keyValuePair("-TOPICLEN")); 1140 assert(isupport.topicLength.isNull); 1141 } 1142 { 1143 assert(!isupport.userhostsInNames); 1144 isupport.insertToken(keyValuePair("UHNAMES")); 1145 assert(isupport.userhostsInNames); 1146 isupport.insertToken(keyValuePair("-UHNAMES")); 1147 assert(!isupport.userhostsInNames); 1148 } 1149 { 1150 assert(!isupport.userIP); 1151 isupport.insertToken(keyValuePair("USERIP")); 1152 assert(isupport.userIP); 1153 isupport.insertToken(keyValuePair("-USERIP")); 1154 assert(!isupport.userIP); 1155 } 1156 { 1157 assert(isupport.userLength.isNull); 1158 isupport.insertToken(keyValuePair("USERLEN=12")); 1159 assert(isupport.userLength == 12); 1160 isupport.insertToken(keyValuePair("USERLEN=")); 1161 assert(isupport.userLength == ulong.max); 1162 isupport.insertToken(keyValuePair("-USERLEN")); 1163 assert(isupport.userLength.isNull); 1164 } 1165 { 1166 assert(!isupport.variableBanList); 1167 isupport.insertToken(keyValuePair("VBANLIST")); 1168 assert(isupport.variableBanList); 1169 isupport.insertToken(keyValuePair("-VBANLIST")); 1170 assert(!isupport.variableBanList); 1171 } 1172 { 1173 assert(!isupport.virtualChannels); 1174 isupport.insertToken(keyValuePair("VCHANS")); 1175 assert(isupport.virtualChannels); 1176 isupport.insertToken(keyValuePair("-VCHANS")); 1177 assert(!isupport.virtualChannels); 1178 } 1179 { 1180 assert(!isupport.wAllChannelOps); 1181 isupport.insertToken(keyValuePair("WALLCHOPS")); 1182 assert(isupport.wAllChannelOps); 1183 isupport.insertToken(keyValuePair("-WALLCHOPS")); 1184 assert(!isupport.wAllChannelOps); 1185 } 1186 { 1187 assert(!isupport.wAllChannelVoices); 1188 isupport.insertToken(keyValuePair("WALLVOICES")); 1189 assert(isupport.wAllChannelVoices); 1190 isupport.insertToken(keyValuePair("-WALLVOICES")); 1191 assert(!isupport.wAllChannelVoices); 1192 } 1193 { 1194 assert(isupport.maximumWatches.isNull); 1195 isupport.insertToken(keyValuePair("WATCH=100")); 1196 assert(isupport.maximumWatches == 100); 1197 isupport.insertToken(keyValuePair("WATCH")); 1198 assert(isupport.maximumWatches == ulong.max); 1199 isupport.insertToken(keyValuePair("-WATCH")); 1200 assert(isupport.maximumWatches.isNull); 1201 } 1202 { 1203 assert(!isupport.whoX); 1204 isupport.insertToken(keyValuePair("WHOX")); 1205 assert(isupport.whoX); 1206 isupport.insertToken(keyValuePair("-WHOX")); 1207 assert(!isupport.whoX); 1208 } 1209 { 1210 assert(isupport.unknownTokens.length == 0); 1211 isupport.insertToken(keyValuePair("WHOA")); 1212 assert(isupport.unknownTokens["WHOA"] == ""); 1213 isupport.insertToken(keyValuePair("WHOA=AOHW")); 1214 assert(isupport.unknownTokens["WHOA"] == "AOHW"); 1215 isupport.insertToken(keyValuePair("WHOA=0")); 1216 isupport.insertToken(keyValuePair("WHOA2=1")); 1217 assert(isupport.unknownTokens["WHOA"] == "0"); 1218 assert(isupport.unknownTokens["WHOA2"] == "1"); 1219 isupport.insertToken(keyValuePair("-WHOA")); 1220 isupport.insertToken(keyValuePair("-WHOA2")); 1221 assert(isupport.unknownTokens.length == 0); 1222 } 1223 } 1224 /++ 1225 + Parses an ISUPPORT token. 1226 +/ 1227 private auto keyValuePair(string token) pure @safe { 1228 import std.algorithm : findSplit, skipOver; 1229 immutable isDisabled = token.skipOver('-'); 1230 auto splitParams = token.findSplit("="); 1231 Nullable!string param; 1232 if (!isDisabled) { 1233 param = splitParams[2]; 1234 } 1235 return TokenPair(splitParams[0], param); 1236 } 1237 /++ 1238 + 1239 +/ 1240 void parseNumeric(Numeric numeric: Numeric.RPL_ISUPPORT, T)(T input, ref ISupport iSupport) if (isForwardRange!T) { 1241 import std.range : drop; 1242 import std.typecons : Nullable; 1243 input.popFront(); 1244 while (!input.empty && !input.drop(1).empty) { 1245 iSupport.insertToken(keyValuePair(input.front)); 1246 input.popFront(); 1247 } 1248 } 1249 /++ 1250 + 1251 +/ 1252 auto parseNumeric(Numeric numeric: Numeric.RPL_ISUPPORT, T)(T input) { 1253 ISupport tmp; 1254 parseNumeric!numeric(input, tmp); 1255 return tmp; 1256 } 1257 /// 1258 @safe pure /+nothrow @nogc+/ unittest { //Numeric.RPL_ISUPPORT 1259 import std.exception : assertNotThrown, assertThrown; 1260 import std.range : only; 1261 import virc.modes : ModeType; 1262 { 1263 auto support = parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "STATUSMSG=~&@%+", "CHANLIMIT=#:2", "CHANMODES=a,b,c,d", "CHANTYPES=#", "are supported by this server")); 1264 assert(support.statusMessage == "~&@%+"); 1265 assert(support.chanLimits == ['#': 2UL]); 1266 assert(support.channelTypes == "#"); 1267 assert(support.channelModeTypes == ['a':ModeType.a, 'b':ModeType.b, 'c':ModeType.c, 'd':ModeType.d]); 1268 parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "-STATUSMSG", "-CHANLIMIT", "-CHANMODES", "-CHANTYPES", "are supported by this server"), support); 1269 assert(support.statusMessage == support.statusMessage.init); 1270 assert(support.chanLimits == support.chanLimits.init); 1271 assert(support.channelTypes == "#&!+"); 1272 assert(support.channelModeTypes == support.channelModeTypes.init); 1273 } 1274 { 1275 auto support = parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "SILENCE=4", "are supported by this server")); 1276 assert(support.silence == 4); 1277 parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "SILENCE", "are supported by this server"), support); 1278 assert(support.silence == ulong.max); 1279 parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "SILENCE=6", "are supported by this server"), support); 1280 parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone" ,"-SILENCE", "are supported by this server"), support); 1281 assert(support.silence.isNull); 1282 } 1283 { 1284 assertNotThrown(parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "are supported by this server"))); 1285 } 1286 }