1 /** 2 * Common IRC code. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Stéphan Kochen <stephan@kochen.nl> 12 * Vladimir Panteleev <vladimir@thecybershadow.net> 13 * Vincent Povirk <madewokherd@gmail.com> 14 * Simon Arlott 15 */ 16 17 module ae.net.irc.common; 18 19 import core.time; 20 import std.algorithm.comparison; 21 import std.algorithm.iteration; 22 import std.array; 23 import std.exception; 24 import std.string; 25 import std.utf; 26 27 import ae.net.asockets; 28 29 debug(IRC) import std.stdio : stderr; 30 31 /// Types of a chat message. 32 enum IrcMessageType 33 { 34 NORMAL, 35 ACTION, 36 NOTICE 37 } 38 39 // RFC1459 case mapping 40 static assert(toLower("[") == "[" && toUpper("[") == "["); 41 static assert(toLower("]") == "]" && toUpper("]") == "]"); 42 static assert(toLower("{") == "{" && toUpper("{") == "{"); 43 static assert(toLower("}") == "}" && toUpper("}") == "}"); 44 static assert(toLower("|") == "|" && toUpper("|") == "|"); 45 static assert(toLower("\\") == "\\" && toUpper("\\") == "\\"); 46 47 char rfc1459toLower(char c) pure 48 { 49 if (c >= 'A' && c <= ']') 50 c += ('a' - 'A'); 51 return c; 52 } 53 54 char rfc1459toUpper(char c) pure 55 { 56 if (c >= 'a' && c <= '}') 57 c -= ('a' - 'A'); 58 return c; 59 } 60 61 string rfc1459toLower(string name) pure 62 { 63 return name.byChar.map!rfc1459toLower.array; 64 } 65 66 67 string rfc1459toUpper(string name) pure 68 { 69 return name.byChar.map!rfc1459toUpper.array; 70 } 71 72 unittest 73 { 74 assert(rfc1459toLower("{}|[]\\") == "{}|{}|"); 75 assert(rfc1459toUpper("{}|[]\\") == "[]\\[]\\"); 76 } 77 78 int rfc1459cmp(in char[] a, in char[] b) 79 { 80 return cmp(a.byChar.map!rfc1459toUpper, b.byChar.map!rfc1459toUpper); 81 } 82 83 unittest 84 { 85 assert(rfc1459cmp("{}|[]\\", "[]\\[]\\") == 0); 86 assert(rfc1459cmp("a", "b") == -1); 87 } 88 89 final class IrcConnection 90 { 91 private: 92 LineBufferedAdapter line; 93 TimeoutAdapter timer; 94 95 public: 96 IConnection conn; 97 alias conn this; 98 99 this(IConnection c, size_t maxLineLength = 512) 100 { 101 c = line = new LineBufferedAdapter(c); 102 line.delimiter = "\n"; 103 line.maxLength = maxLineLength; 104 105 c = timer = new TimeoutAdapter(c); 106 timer.setIdleTimeout(90.seconds); 107 timer.handleIdleTimeout = &onIdleTimeout; 108 timer.handleNonIdle = &onNonIdle; 109 110 conn = c; 111 conn.handleReadData = &onReadData; 112 } 113 114 void onNonIdle() 115 { 116 if (pingSent) 117 pingSent = false; 118 } 119 120 void send(string line) 121 { 122 debug(IRC) stderr.writeln("> ", line); 123 // Send with \r\n, but support receiving with \n 124 import ae.sys.data; 125 conn.send(Data(line ~ "\r\n")); 126 } 127 128 void delegate() handleInactivity; 129 void delegate() handleTimeout; 130 void delegate(string line) handleReadLine; 131 132 private: 133 void onReadData(Data data) 134 { 135 string line = (cast(string)data.toHeap()).chomp("\r"); 136 debug(IRC) stderr.writeln("< ", line); 137 138 if (handleReadLine) 139 handleReadLine(line); 140 } 141 142 void onIdleTimeout() 143 { 144 if (pingSent || handleInactivity is null || conn.state != ConnectionState.connected) 145 { 146 if (handleTimeout) 147 handleTimeout(); 148 else 149 conn.disconnect("Time-out", DisconnectType.error); 150 } 151 else 152 { 153 handleInactivity(); 154 pingSent = true; 155 } 156 } 157 158 bool pingSent; 159 } 160 161 // TODO: this is server-specific 162 const string IRC_NICK_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-`"; 163 const string IRC_USER_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 164 const string IRC_HOST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-."; 165 166 enum Reply 167 { 168 RPL_WELCOME = 001, 169 RPL_YOURHOST = 002, 170 RPL_CREATED = 003, 171 RPL_MYINFO = 004, 172 RPL_BOUNCE = 005, 173 RPL_TRACELINK = 200, 174 RPL_TRACECONNECTING = 201, 175 RPL_TRACEHANDSHAKE = 202, 176 RPL_TRACEUNKNOWN = 203, 177 RPL_TRACEOPERATOR = 204, 178 RPL_TRACEUSER = 205, 179 RPL_TRACESERVER = 206, 180 RPL_TRACESERVICE = 207, 181 RPL_TRACENEWTYPE = 208, 182 RPL_TRACECLASS = 209, 183 RPL_TRACERECONNECT = 210, 184 RPL_STATSLINKINFO = 211, 185 RPL_STATSCOMMANDS = 212, 186 RPL_STATSCLINE = 213, 187 RPL_STATSNLINE = 214, 188 RPL_STATSILINE = 215, 189 RPL_STATSKLINE = 216, 190 RPL_STATSQLINE = 217, 191 RPL_STATSYLINE = 218, 192 RPL_ENDOFSTATS = 219, 193 RPL_UMODEIS = 221, 194 RPL_SERVICEINFO = 231, 195 RPL_ENDOFSERVICES = 232, 196 RPL_SERVICE = 233, 197 RPL_SERVLIST = 234, 198 RPL_SERVLISTEND = 235, 199 RPL_STATSVLINE = 240, 200 RPL_STATSLLINE = 241, 201 RPL_STATSUPTIME = 242, 202 RPL_STATSOLINE = 243, 203 RPL_STATSHLINE = 244, 204 RPL_STATSSLINE = 244, 205 RPL_STATSPING = 246, 206 RPL_STATSBLINE = 247, 207 RPL_STATSDLINE = 250, 208 RPL_LUSERCLIENT = 251, 209 RPL_LUSEROP = 252, 210 RPL_LUSERUNKNOWN = 253, 211 RPL_LUSERCHANNELS = 254, 212 RPL_LUSERME = 255, 213 RPL_ADMINME = 256, 214 RPL_ADMINLOC1 = 257, 215 RPL_ADMINLOC2 = 258, 216 RPL_ADMINEMAIL = 259, 217 RPL_TRACELOG = 261, 218 RPL_TRACEEND = 262, 219 RPL_TRYAGAIN = 263, 220 RPL_NONE = 300, 221 RPL_AWAY = 301, 222 RPL_USERHOST = 302, 223 RPL_ISON = 303, 224 RPL_UNAWAY = 305, 225 RPL_NOWAWAY = 306, 226 RPL_WHOISUSER = 311, 227 RPL_WHOISSERVER = 312, 228 RPL_WHOISOPERATOR = 313, 229 RPL_WHOWASUSER = 314, 230 RPL_ENDOFWHO = 315, 231 RPL_WHOISCHANOP = 316, 232 RPL_WHOISIDLE = 317, 233 RPL_ENDOFWHOIS = 318, 234 RPL_WHOISCHANNELS = 319, 235 RPL_LISTSTART = 321, 236 RPL_LIST = 322, 237 RPL_LISTEND = 323, 238 RPL_CHANNELMODEIS = 324, 239 RPL_UNIQOPIS = 325, 240 RPL_NOTOPIC = 331, 241 RPL_TOPIC = 332, 242 RPL_INVITING = 341, 243 RPL_SUMMONING = 342, 244 RPL_INVITELIST = 346, 245 RPL_ENDOFINVITELIST = 347, 246 RPL_EXCEPTLIST = 348, 247 RPL_ENDOFEXCEPTLIST = 349, 248 RPL_VERSION = 351, 249 RPL_WHOREPLY = 352, 250 RPL_NAMREPLY = 353, 251 RPL_KILLDONE = 361, 252 RPL_CLOSING = 362, 253 RPL_CLOSEEND = 363, 254 RPL_LINKS = 364, 255 RPL_ENDOFLINKS = 365, 256 RPL_ENDOFNAMES = 366, 257 RPL_BANLIST = 367, 258 RPL_ENDOFBANLIST = 368, 259 RPL_ENDOFWHOWAS = 369, 260 RPL_INFO = 371, 261 RPL_MOTD = 372, 262 RPL_INFOSTART = 373, 263 RPL_ENDOFINFO = 374, 264 RPL_MOTDSTART = 375, 265 RPL_ENDOFMOTD = 376, 266 RPL_YOUREOPER = 381, 267 RPL_REHASHING = 382, 268 RPL_YOURESERVICE = 383, 269 RPL_MYPORTIS = 384, 270 RPL_TIME = 391, 271 RPL_USERSSTART = 392, 272 RPL_USERS = 393, 273 RPL_ENDOFUSERS = 394, 274 RPL_NOUSERS = 395, 275 ERR_NOSUCHNICK = 401, 276 ERR_NOSUCHSERVER = 402, 277 ERR_NOSUCHCHANNEL = 403, 278 ERR_CANNOTSENDTOCHAN = 404, 279 ERR_TOOMANYCHANNELS = 405, 280 ERR_WASNOSUCHNICK = 406, 281 ERR_TOOMANYTARGETS = 407, 282 ERR_NOSUCHSERVICE = 408, 283 ERR_NOORIGIN = 409, 284 ERR_NORECIPIENT = 411, 285 ERR_NOTEXTTOSEND = 412, 286 ERR_NOTOPLEVEL = 413, 287 ERR_WILDTOPLEVEL = 414, 288 ERR_BADMASK = 415, 289 ERR_UNKNOWNCOMMAND = 421, 290 ERR_NOMOTD = 422, 291 ERR_NOADMININFO = 423, 292 ERR_FILEERROR = 424, 293 ERR_NONICKNAMEGIVEN = 431, 294 ERR_ERRONEUSNICKNAME = 432, 295 ERR_NICKNAMEINUSE = 433, 296 ERR_NICKCOLLISION = 436, 297 ERR_UNAVAILRESOURCE = 437, 298 ERR_USERNOTINCHANNEL = 441, 299 ERR_NOTONCHANNEL = 442, 300 ERR_USERONCHANNEL = 443, 301 ERR_NOLOGIN = 444, 302 ERR_SUMMONDISABLED = 445, 303 ERR_USERSDISABLED = 446, 304 ERR_NOTREGISTERED = 451, 305 ERR_NEEDMOREPARAMS = 461, 306 ERR_ALREADYREGISTRED = 462, 307 ERR_NOPERMFORHOST = 463, 308 ERR_PASSWDMISMATCH = 464, 309 ERR_YOUREBANNEDCREEP = 465, 310 ERR_YOUWILLBEBANNED = 466, 311 ERR_KEYSET = 467, 312 ERR_CHANNELISFULL = 471, 313 ERR_UNKNOWNMODE = 472, 314 ERR_INVITEONLYCHAN = 473, 315 ERR_BANNEDFROMCHAN = 474, 316 ERR_BADCHANNELKEY = 475, 317 ERR_BADCHANMASK = 476, 318 ERR_NOCHANMODES = 477, 319 ERR_BANLISTFULL = 478, 320 ERR_NOPRIVILEGES = 481, 321 ERR_CHANOPRIVSNEEDED = 482, 322 ERR_CANTKILLSERVER = 483, 323 ERR_RESTRICTED = 484, 324 ERR_UNIQOPPRIVSNEEDED = 485, 325 ERR_NOOPERHOST = 491, 326 ERR_NOSERVICEHOST = 492, 327 ERR_UMODEUNKNOWNFLAG = 501, 328 ERR_USERSDONTMATCH = 502, 329 }