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) 100 { 101 c = line = new LineBufferedAdapter(c); 102 line.delimiter = "\n"; 103 104 c = timer = new TimeoutAdapter(c); 105 timer.setIdleTimeout(90.seconds); 106 timer.handleIdleTimeout = &onIdleTimeout; 107 timer.handleNonIdle = &onNonIdle; 108 109 conn = c; 110 conn.handleReadData = &onReadData; 111 } 112 113 void onNonIdle() 114 { 115 if (pingSent) 116 pingSent = false; 117 } 118 119 void send(string line) 120 { 121 debug(IRC) stderr.writeln("> ", line); 122 // Send with \r\n, but support receiving with \n 123 import ae.sys.data; 124 conn.send(Data(line ~ "\r\n")); 125 } 126 127 void delegate() handleInactivity; 128 void delegate() handleTimeout; 129 void delegate(string line) handleReadLine; 130 131 private: 132 void onReadData(Data data) 133 { 134 string line = (cast(string)data.toHeap()).chomp("\r"); 135 debug(IRC) stderr.writeln("< ", line); 136 137 if (handleReadLine) 138 handleReadLine(line); 139 } 140 141 void onIdleTimeout() 142 { 143 if (pingSent || handleInactivity is null || conn.state != ConnectionState.connected) 144 { 145 if (handleTimeout) 146 handleTimeout(); 147 else 148 conn.disconnect("Time-out", DisconnectType.error); 149 } 150 else 151 { 152 handleInactivity(); 153 pingSent = true; 154 } 155 } 156 157 bool pingSent; 158 } 159 160 // TODO: this is server-specific 161 const string IRC_NICK_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-`"; 162 const string IRC_USER_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 163 const string IRC_HOST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-."; 164 165 enum Reply 166 { 167 RPL_WELCOME = 001, 168 RPL_YOURHOST = 002, 169 RPL_CREATED = 003, 170 RPL_MYINFO = 004, 171 RPL_BOUNCE = 005, 172 RPL_TRACELINK = 200, 173 RPL_TRACECONNECTING = 201, 174 RPL_TRACEHANDSHAKE = 202, 175 RPL_TRACEUNKNOWN = 203, 176 RPL_TRACEOPERATOR = 204, 177 RPL_TRACEUSER = 205, 178 RPL_TRACESERVER = 206, 179 RPL_TRACESERVICE = 207, 180 RPL_TRACENEWTYPE = 208, 181 RPL_TRACECLASS = 209, 182 RPL_TRACERECONNECT = 210, 183 RPL_STATSLINKINFO = 211, 184 RPL_STATSCOMMANDS = 212, 185 RPL_STATSCLINE = 213, 186 RPL_STATSNLINE = 214, 187 RPL_STATSILINE = 215, 188 RPL_STATSKLINE = 216, 189 RPL_STATSQLINE = 217, 190 RPL_STATSYLINE = 218, 191 RPL_ENDOFSTATS = 219, 192 RPL_UMODEIS = 221, 193 RPL_SERVICEINFO = 231, 194 RPL_ENDOFSERVICES = 232, 195 RPL_SERVICE = 233, 196 RPL_SERVLIST = 234, 197 RPL_SERVLISTEND = 235, 198 RPL_STATSVLINE = 240, 199 RPL_STATSLLINE = 241, 200 RPL_STATSUPTIME = 242, 201 RPL_STATSOLINE = 243, 202 RPL_STATSHLINE = 244, 203 RPL_STATSSLINE = 244, 204 RPL_STATSPING = 246, 205 RPL_STATSBLINE = 247, 206 RPL_STATSDLINE = 250, 207 RPL_LUSERCLIENT = 251, 208 RPL_LUSEROP = 252, 209 RPL_LUSERUNKNOWN = 253, 210 RPL_LUSERCHANNELS = 254, 211 RPL_LUSERME = 255, 212 RPL_ADMINME = 256, 213 RPL_ADMINLOC1 = 257, 214 RPL_ADMINLOC2 = 258, 215 RPL_ADMINEMAIL = 259, 216 RPL_TRACELOG = 261, 217 RPL_TRACEEND = 262, 218 RPL_TRYAGAIN = 263, 219 RPL_NONE = 300, 220 RPL_AWAY = 301, 221 RPL_USERHOST = 302, 222 RPL_ISON = 303, 223 RPL_UNAWAY = 305, 224 RPL_NOWAWAY = 306, 225 RPL_WHOISUSER = 311, 226 RPL_WHOISSERVER = 312, 227 RPL_WHOISOPERATOR = 313, 228 RPL_WHOWASUSER = 314, 229 RPL_ENDOFWHO = 315, 230 RPL_WHOISCHANOP = 316, 231 RPL_WHOISIDLE = 317, 232 RPL_ENDOFWHOIS = 318, 233 RPL_WHOISCHANNELS = 319, 234 RPL_LISTSTART = 321, 235 RPL_LIST = 322, 236 RPL_LISTEND = 323, 237 RPL_CHANNELMODEIS = 324, 238 RPL_UNIQOPIS = 325, 239 RPL_NOTOPIC = 331, 240 RPL_TOPIC = 332, 241 RPL_INVITING = 341, 242 RPL_SUMMONING = 342, 243 RPL_INVITELIST = 346, 244 RPL_ENDOFINVITELIST = 347, 245 RPL_EXCEPTLIST = 348, 246 RPL_ENDOFEXCEPTLIST = 349, 247 RPL_VERSION = 351, 248 RPL_WHOREPLY = 352, 249 RPL_NAMREPLY = 353, 250 RPL_KILLDONE = 361, 251 RPL_CLOSING = 362, 252 RPL_CLOSEEND = 363, 253 RPL_LINKS = 364, 254 RPL_ENDOFLINKS = 365, 255 RPL_ENDOFNAMES = 366, 256 RPL_BANLIST = 367, 257 RPL_ENDOFBANLIST = 368, 258 RPL_ENDOFWHOWAS = 369, 259 RPL_INFO = 371, 260 RPL_MOTD = 372, 261 RPL_INFOSTART = 373, 262 RPL_ENDOFINFO = 374, 263 RPL_MOTDSTART = 375, 264 RPL_ENDOFMOTD = 376, 265 RPL_YOUREOPER = 381, 266 RPL_REHASHING = 382, 267 RPL_YOURESERVICE = 383, 268 RPL_MYPORTIS = 384, 269 RPL_TIME = 391, 270 RPL_USERSSTART = 392, 271 RPL_USERS = 393, 272 RPL_ENDOFUSERS = 394, 273 RPL_NOUSERS = 395, 274 ERR_NOSUCHNICK = 401, 275 ERR_NOSUCHSERVER = 402, 276 ERR_NOSUCHCHANNEL = 403, 277 ERR_CANNOTSENDTOCHAN = 404, 278 ERR_TOOMANYCHANNELS = 405, 279 ERR_WASNOSUCHNICK = 406, 280 ERR_TOOMANYTARGETS = 407, 281 ERR_NOSUCHSERVICE = 408, 282 ERR_NOORIGIN = 409, 283 ERR_NORECIPIENT = 411, 284 ERR_NOTEXTTOSEND = 412, 285 ERR_NOTOPLEVEL = 413, 286 ERR_WILDTOPLEVEL = 414, 287 ERR_BADMASK = 415, 288 ERR_UNKNOWNCOMMAND = 421, 289 ERR_NOMOTD = 422, 290 ERR_NOADMININFO = 423, 291 ERR_FILEERROR = 424, 292 ERR_NONICKNAMEGIVEN = 431, 293 ERR_ERRONEUSNICKNAME = 432, 294 ERR_NICKNAMEINUSE = 433, 295 ERR_NICKCOLLISION = 436, 296 ERR_UNAVAILRESOURCE = 437, 297 ERR_USERNOTINCHANNEL = 441, 298 ERR_NOTONCHANNEL = 442, 299 ERR_USERONCHANNEL = 443, 300 ERR_NOLOGIN = 444, 301 ERR_SUMMONDISABLED = 445, 302 ERR_USERSDISABLED = 446, 303 ERR_NOTREGISTERED = 451, 304 ERR_NEEDMOREPARAMS = 461, 305 ERR_ALREADYREGISTRED = 462, 306 ERR_NOPERMFORHOST = 463, 307 ERR_PASSWDMISMATCH = 464, 308 ERR_YOUREBANNEDCREEP = 465, 309 ERR_YOUWILLBEBANNED = 466, 310 ERR_KEYSET = 467, 311 ERR_CHANNELISFULL = 471, 312 ERR_UNKNOWNMODE = 472, 313 ERR_INVITEONLYCHAN = 473, 314 ERR_BANNEDFROMCHAN = 474, 315 ERR_BADCHANNELKEY = 475, 316 ERR_BADCHANMASK = 476, 317 ERR_NOCHANMODES = 477, 318 ERR_BANLISTFULL = 478, 319 ERR_NOPRIVILEGES = 481, 320 ERR_CHANOPRIVSNEEDED = 482, 321 ERR_CANTKILLSERVER = 483, 322 ERR_RESTRICTED = 484, 323 ERR_UNIQOPPRIVSNEEDED = 485, 324 ERR_NOOPERHOST = 491, 325 ERR_NOSERVICEHOST = 492, 326 ERR_UMODEUNKNOWNFLAG = 501, 327 ERR_USERSDONTMATCH = 502, 328 }