IRCClient.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. #include "IRCClient.h"
  2. #include "IRCChannel.h"
  3. #include "IRCQuery.h"
  4. #include "IRCLogBuffer.h"
  5. #include "IRCWindow.h"
  6. #include "IRCWindowListModel.h"
  7. #include <LibCore/CNotifier.h>
  8. #include <sys/socket.h>
  9. #include <netinet/in.h>
  10. #include <arpa/inet.h>
  11. #include <unistd.h>
  12. #include <stdio.h>
  13. #include <time.h>
  14. #define IRC_DEBUG
  15. enum IRCNumeric {
  16. RPL_WHOISUSER = 311,
  17. RPL_WHOISSERVER = 312,
  18. RPL_WHOISOPERATOR = 313,
  19. RPL_WHOISIDLE = 317,
  20. RPL_ENDOFWHOIS = 318,
  21. RPL_WHOISCHANNELS = 319,
  22. RPL_TOPIC = 332,
  23. RPL_TOPICWHOTIME = 333,
  24. RPL_NAMREPLY = 353,
  25. RPL_ENDOFNAMES = 366,
  26. };
  27. IRCClient::IRCClient()
  28. : m_nickname("seren1ty")
  29. , m_client_window_list_model(IRCWindowListModel::create(*this))
  30. , m_log(IRCLogBuffer::create())
  31. {
  32. m_socket = new CTCPSocket(this);
  33. }
  34. IRCClient::~IRCClient()
  35. {
  36. }
  37. void IRCClient::set_server(const String &hostname, int port)
  38. {
  39. m_hostname = hostname;
  40. m_port = port;
  41. }
  42. void IRCClient::on_socket_connected()
  43. {
  44. m_notifier = make<CNotifier>(m_socket->fd(), CNotifier::Read);
  45. m_notifier->on_ready_to_read = [this] { receive_from_server(); };
  46. send_user();
  47. send_nick();
  48. if (on_connect)
  49. on_connect();
  50. }
  51. bool IRCClient::connect()
  52. {
  53. if (m_socket->is_connected())
  54. ASSERT_NOT_REACHED();
  55. m_socket->on_connected = [this] { on_socket_connected(); };
  56. bool success = m_socket->connect(m_hostname, m_port);
  57. if (!success)
  58. return false;
  59. return true;
  60. }
  61. void IRCClient::receive_from_server()
  62. {
  63. while (m_socket->can_read_line()) {
  64. auto line = m_socket->read_line(PAGE_SIZE);
  65. if (line.is_null()) {
  66. if (!m_socket->is_connected()) {
  67. printf("IRCClient: Connection closed!\n");
  68. exit(1);
  69. }
  70. ASSERT_NOT_REACHED();
  71. }
  72. process_line(move(line));
  73. }
  74. }
  75. void IRCClient::process_line(ByteBuffer&& line)
  76. {
  77. Message msg;
  78. Vector<char, 32> prefix;
  79. Vector<char, 32> command;
  80. Vector<char, 256> current_parameter;
  81. enum {
  82. Start,
  83. InPrefix,
  84. InCommand,
  85. InStartOfParameter,
  86. InParameter,
  87. InTrailingParameter,
  88. } state = Start;
  89. for (int i = 0; i < line.size(); ++i) {
  90. char ch = line[i];
  91. if (ch == '\r')
  92. continue;
  93. if (ch == '\n')
  94. break;
  95. switch (state) {
  96. case Start:
  97. if (ch == ':') {
  98. state = InPrefix;
  99. continue;
  100. }
  101. state = InCommand;
  102. [[fallthrough]];
  103. case InCommand:
  104. if (ch == ' ') {
  105. state = InStartOfParameter;
  106. continue;
  107. }
  108. command.append(ch);
  109. continue;
  110. case InPrefix:
  111. if (ch == ' ') {
  112. state = InCommand;
  113. continue;
  114. }
  115. prefix.append(ch);
  116. continue;
  117. case InStartOfParameter:
  118. if (ch == ':') {
  119. state = InTrailingParameter;
  120. continue;
  121. }
  122. state = InParameter;
  123. [[fallthrough]];
  124. case InParameter:
  125. if (ch == ' ') {
  126. if (!current_parameter.is_empty())
  127. msg.arguments.append(String(current_parameter.data(), current_parameter.size()));
  128. current_parameter.clear_with_capacity();
  129. state = InStartOfParameter;
  130. continue;
  131. }
  132. current_parameter.append(ch);
  133. continue;
  134. case InTrailingParameter:
  135. current_parameter.append(ch);
  136. continue;
  137. }
  138. }
  139. if (!current_parameter.is_empty())
  140. msg.arguments.append(String(current_parameter.data(), current_parameter.size()));
  141. msg.prefix = String(prefix.data(), prefix.size());
  142. msg.command = String(command.data(), command.size());
  143. handle(msg, String(m_line_buffer.data(), m_line_buffer.size()));
  144. }
  145. void IRCClient::send(const String& text)
  146. {
  147. if (!m_socket->send(ByteBuffer::wrap((void*)text.characters(), text.length()))) {
  148. perror("send");
  149. exit(1);
  150. }
  151. }
  152. void IRCClient::send_user()
  153. {
  154. send(String::format("USER %s 0 * :%s\r\n", m_nickname.characters(), m_nickname.characters()));
  155. }
  156. void IRCClient::send_nick()
  157. {
  158. send(String::format("NICK %s\r\n", m_nickname.characters()));
  159. }
  160. void IRCClient::send_pong(const String& server)
  161. {
  162. send(String::format("PONG %s\r\n", server.characters()));
  163. sleep(1);
  164. }
  165. void IRCClient::join_channel(const String& channel_name)
  166. {
  167. send(String::format("JOIN %s\r\n", channel_name.characters()));
  168. }
  169. void IRCClient::part_channel(const String& channel_name)
  170. {
  171. send(String::format("PART %s\r\n", channel_name.characters()));
  172. }
  173. void IRCClient::send_whois(const String& nick)
  174. {
  175. send(String::format("WHOIS %s\r\n", nick.characters()));
  176. }
  177. void IRCClient::handle(const Message& msg, const String&)
  178. {
  179. #ifdef IRC_DEBUG
  180. printf("IRCClient::execute: prefix='%s', command='%s', arguments=%d\n",
  181. msg.prefix.characters(),
  182. msg.command.characters(),
  183. msg.arguments.size()
  184. );
  185. int i = 0;
  186. for (auto& arg : msg.arguments) {
  187. printf(" [%d]: %s\n", i, arg.characters());
  188. ++i;
  189. }
  190. #endif
  191. bool is_numeric;
  192. int numeric = msg.command.to_uint(is_numeric);
  193. if (is_numeric) {
  194. switch (numeric) {
  195. case RPL_WHOISCHANNELS: return handle_rpl_whoischannels(msg);
  196. case RPL_ENDOFWHOIS: return handle_rpl_endofwhois(msg);
  197. case RPL_WHOISOPERATOR: return handle_rpl_whoisoperator(msg);
  198. case RPL_WHOISSERVER: return handle_rpl_whoisserver(msg);
  199. case RPL_WHOISUSER: return handle_rpl_whoisuser(msg);
  200. case RPL_WHOISIDLE: return handle_rpl_whoisidle(msg);
  201. case RPL_TOPICWHOTIME: return handle_rpl_topicwhotime(msg);
  202. case RPL_TOPIC: return handle_rpl_topic(msg);
  203. case RPL_NAMREPLY: return handle_rpl_namreply(msg);
  204. case RPL_ENDOFNAMES: return handle_rpl_endofnames(msg);
  205. }
  206. }
  207. if (msg.command == "PING")
  208. return handle_ping(msg);
  209. if (msg.command == "JOIN")
  210. return handle_join(msg);
  211. if (msg.command == "PART")
  212. return handle_part(msg);
  213. if (msg.command == "TOPIC")
  214. return handle_topic(msg);
  215. if (msg.command == "PRIVMSG")
  216. return handle_privmsg(msg);
  217. if (msg.command == "NICK")
  218. return handle_nick(msg);
  219. if (msg.arguments.size() >= 2)
  220. add_server_message(String::format("[%s] %s", msg.command.characters(), msg.arguments[1].characters()));
  221. }
  222. void IRCClient::add_server_message(const String& text)
  223. {
  224. m_log->add_message(0, "", text);
  225. m_server_subwindow->did_add_message();
  226. }
  227. void IRCClient::send_privmsg(const String& target, const String& text)
  228. {
  229. send(String::format("PRIVMSG %s :%s\r\n", target.characters(), text.characters()));
  230. }
  231. void IRCClient::handle_user_input_in_channel(const String& channel_name, const String& input)
  232. {
  233. if (input.is_empty())
  234. return;
  235. if (input[0] == '/')
  236. return handle_user_command(input);
  237. ensure_channel(channel_name).say(input);
  238. }
  239. void IRCClient::handle_user_input_in_query(const String& query_name, const String& input)
  240. {
  241. if (input.is_empty())
  242. return;
  243. if (input[0] == '/')
  244. return handle_user_command(input);
  245. ensure_query(query_name).say(input);
  246. }
  247. void IRCClient::handle_user_input_in_server(const String& input)
  248. {
  249. if (input.is_empty())
  250. return;
  251. if (input[0] == '/')
  252. return handle_user_command(input);
  253. }
  254. bool IRCClient::is_nick_prefix(char ch) const
  255. {
  256. switch (ch) {
  257. case '@':
  258. case '+':
  259. case '~':
  260. case '&':
  261. case '%':
  262. return true;
  263. }
  264. return false;
  265. }
  266. void IRCClient::handle_privmsg(const Message& msg)
  267. {
  268. if (msg.arguments.size() < 2)
  269. return;
  270. if (msg.prefix.is_empty())
  271. return;
  272. auto parts = msg.prefix.split('!');
  273. auto sender_nick = parts[0];
  274. auto target = msg.arguments[0];
  275. #ifdef IRC_DEBUG
  276. printf("handle_privmsg: sender_nick='%s', target='%s'\n", sender_nick.characters(), target.characters());
  277. #endif
  278. if (sender_nick.is_empty())
  279. return;
  280. char sender_prefix = 0;
  281. if (is_nick_prefix(sender_nick[0])) {
  282. sender_prefix = sender_nick[0];
  283. sender_nick = sender_nick.substring(1, sender_nick.length() - 1);
  284. }
  285. {
  286. auto it = m_channels.find(target);
  287. if (it != m_channels.end()) {
  288. (*it).value->add_message(sender_prefix, sender_nick, msg.arguments[1]);
  289. return;
  290. }
  291. }
  292. auto& query = ensure_query(sender_nick);
  293. query.add_message(sender_prefix, sender_nick, msg.arguments[1]);
  294. }
  295. IRCQuery& IRCClient::ensure_query(const String& name)
  296. {
  297. auto it = m_queries.find(name);
  298. if (it != m_queries.end())
  299. return *(*it).value;
  300. auto query = IRCQuery::create(*this, name);
  301. auto& query_reference = *query;
  302. m_queries.set(name, query.copy_ref());
  303. return query_reference;
  304. }
  305. IRCChannel& IRCClient::ensure_channel(const String& name)
  306. {
  307. auto it = m_channels.find(name);
  308. if (it != m_channels.end())
  309. return *(*it).value;
  310. auto channel = IRCChannel::create(*this, name);
  311. auto& channel_reference = *channel;
  312. m_channels.set(name, channel.copy_ref());
  313. return channel_reference;
  314. }
  315. void IRCClient::handle_ping(const Message& msg)
  316. {
  317. if (msg.arguments.size() < 0)
  318. return;
  319. m_log->add_message(0, "", "Ping? Pong!");
  320. send_pong(msg.arguments[0]);
  321. }
  322. void IRCClient::handle_join(const Message& msg)
  323. {
  324. if (msg.arguments.size() != 1)
  325. return;
  326. auto prefix_parts = msg.prefix.split('!');
  327. if (prefix_parts.size() < 1)
  328. return;
  329. auto nick = prefix_parts[0];
  330. auto& channel_name = msg.arguments[0];
  331. ensure_channel(channel_name).handle_join(nick, msg.prefix);
  332. }
  333. void IRCClient::handle_part(const Message& msg)
  334. {
  335. if (msg.arguments.size() < 1)
  336. return;
  337. auto prefix_parts = msg.prefix.split('!');
  338. if (prefix_parts.size() < 1)
  339. return;
  340. auto nick = prefix_parts[0];
  341. auto& channel_name = msg.arguments[0];
  342. ensure_channel(channel_name).handle_part(nick, msg.prefix);
  343. }
  344. void IRCClient::handle_nick(const Message& msg)
  345. {
  346. auto prefix_parts = msg.prefix.split('!');
  347. if (prefix_parts.size() < 1)
  348. return;
  349. auto old_nick = prefix_parts[0];
  350. if (msg.arguments.size() != 1)
  351. return;
  352. auto& new_nick = msg.arguments[0];
  353. if (old_nick == m_nickname)
  354. m_nickname = new_nick;
  355. add_server_message(String::format("~ %s changed nickname to %s", old_nick.characters(), new_nick.characters()));
  356. if (on_nickname_changed)
  357. on_nickname_changed(new_nick);
  358. for (auto& it : m_channels) {
  359. it.value->notify_nick_changed(old_nick, new_nick);
  360. }
  361. }
  362. void IRCClient::handle_topic(const Message& msg)
  363. {
  364. if (msg.arguments.size() != 2)
  365. return;
  366. auto prefix_parts = msg.prefix.split('!');
  367. if (prefix_parts.size() < 1)
  368. return;
  369. auto nick = prefix_parts[0];
  370. auto& channel_name = msg.arguments[0];
  371. ensure_channel(channel_name).handle_topic(nick, msg.arguments[1]);
  372. }
  373. void IRCClient::handle_rpl_topic(const Message& msg)
  374. {
  375. if (msg.arguments.size() < 3)
  376. return;
  377. auto& channel_name = msg.arguments[1];
  378. auto& topic = msg.arguments[2];
  379. ensure_channel(channel_name).handle_topic({ }, topic);
  380. // FIXME: Handle RPL_TOPICWHOTIME so we can know who set it and when.
  381. }
  382. void IRCClient::handle_rpl_namreply(const Message& msg)
  383. {
  384. if (msg.arguments.size() < 4)
  385. return;
  386. auto& channel_name = msg.arguments[2];
  387. auto& channel = ensure_channel(channel_name);
  388. auto members = msg.arguments[3].split(' ');
  389. for (auto& member : members) {
  390. if (member.is_empty())
  391. continue;
  392. char prefix = 0;
  393. if (is_nick_prefix(member[0]))
  394. prefix = member[0];
  395. channel.add_member(member, prefix);
  396. }
  397. }
  398. void IRCClient::handle_rpl_endofnames(const Message&)
  399. {
  400. }
  401. void IRCClient::handle_rpl_endofwhois(const Message&)
  402. {
  403. add_server_message("// End of WHOIS");
  404. }
  405. void IRCClient::handle_rpl_whoisoperator(const Message& msg)
  406. {
  407. if (msg.arguments.size() < 2)
  408. return;
  409. auto& nick = msg.arguments[1];
  410. add_server_message(String::format("* %s is an IRC operator", nick.characters()));
  411. }
  412. void IRCClient::handle_rpl_whoisserver(const Message& msg)
  413. {
  414. if (msg.arguments.size() < 3)
  415. return;
  416. auto& nick = msg.arguments[1];
  417. auto& server = msg.arguments[2];
  418. add_server_message(String::format("* %s is using server %s", nick.characters(), server.characters()));
  419. }
  420. void IRCClient::handle_rpl_whoisuser(const Message& msg)
  421. {
  422. if (msg.arguments.size() < 6)
  423. return;
  424. auto& nick = msg.arguments[1];
  425. auto& username = msg.arguments[2];
  426. auto& host = msg.arguments[3];
  427. auto& asterisk = msg.arguments[4];
  428. auto& realname = msg.arguments[5];
  429. (void)asterisk;
  430. add_server_message(String::format("* %s is %s@%s, real name: %s",
  431. nick.characters(),
  432. username.characters(),
  433. host.characters(),
  434. realname.characters()
  435. ));
  436. }
  437. void IRCClient::handle_rpl_whoisidle(const Message& msg)
  438. {
  439. if (msg.arguments.size() < 3)
  440. return;
  441. auto& nick = msg.arguments[1];
  442. auto& secs = msg.arguments[2];
  443. add_server_message(String::format("* %s is %s seconds idle", nick.characters(), secs.characters()));
  444. }
  445. void IRCClient::handle_rpl_whoischannels(const Message& msg)
  446. {
  447. if (msg.arguments.size() < 3)
  448. return;
  449. auto& nick = msg.arguments[1];
  450. auto& channel_list = msg.arguments[2];
  451. add_server_message(String::format("* %s is in channels %s", nick.characters(), channel_list.characters()));
  452. }
  453. void IRCClient::handle_rpl_topicwhotime(const Message& msg)
  454. {
  455. if (msg.arguments.size() < 4)
  456. return;
  457. auto& channel_name = msg.arguments[1];
  458. auto& nick = msg.arguments[2];
  459. auto setat = msg.arguments[3];
  460. bool ok;
  461. time_t setat_time = setat.to_uint(ok);
  462. if (ok) {
  463. auto* tm = localtime(&setat_time);
  464. setat = String::format("%4u-%02u-%02u %02u:%02u:%02u",
  465. tm->tm_year + 1900,
  466. tm->tm_mon + 1,
  467. tm->tm_mday,
  468. tm->tm_hour,
  469. tm->tm_min,
  470. tm->tm_sec
  471. );
  472. }
  473. ensure_channel(channel_name).add_message(String::format("*** (set by %s at %s)", nick.characters(), setat.characters()), Color::Blue);
  474. }
  475. void IRCClient::register_subwindow(IRCWindow& subwindow)
  476. {
  477. if (subwindow.type() == IRCWindow::Server) {
  478. m_server_subwindow = &subwindow;
  479. subwindow.set_log_buffer(*m_log);
  480. }
  481. m_windows.append(&subwindow);
  482. m_client_window_list_model->update();
  483. }
  484. void IRCClient::unregister_subwindow(IRCWindow& subwindow)
  485. {
  486. if (subwindow.type() == IRCWindow::Server) {
  487. m_server_subwindow = &subwindow;
  488. }
  489. for (int i = 0; i < m_windows.size(); ++i) {
  490. if (m_windows.at(i) == &subwindow) {
  491. m_windows.remove(i);
  492. break;
  493. }
  494. }
  495. m_client_window_list_model->update();
  496. }
  497. void IRCClient::handle_user_command(const String& input)
  498. {
  499. auto parts = input.split(' ');
  500. if (parts.is_empty())
  501. return;
  502. auto command = String(parts[0]).to_uppercase();
  503. if (command == "/NICK") {
  504. if (parts.size() >= 2)
  505. change_nick(parts[1]);
  506. return;
  507. }
  508. if (command == "/JOIN") {
  509. if (parts.size() >= 2)
  510. join_channel(parts[1]);
  511. return;
  512. }
  513. if (command == "/PART") {
  514. if (parts.size() >= 2)
  515. part_channel(parts[1]);
  516. return;
  517. }
  518. if (command == "/QUERY") {
  519. if (parts.size() >= 2)
  520. ensure_query(parts[1]);
  521. return;
  522. }
  523. if (command == "/WHOIS") {
  524. if (parts.size() >= 2)
  525. send_whois(parts[1]);
  526. return;
  527. }
  528. }
  529. void IRCClient::change_nick(const String& nick)
  530. {
  531. send(String::format("NICK %s\r\n", nick.characters()));
  532. }
  533. void IRCClient::handle_whois_action(const String& nick)
  534. {
  535. send_whois(nick);
  536. }
  537. void IRCClient::handle_open_query_action(const String& nick)
  538. {
  539. ensure_query(nick);
  540. }
  541. void IRCClient::handle_change_nick_action(const String& nick)
  542. {
  543. change_nick(nick);
  544. }
  545. void IRCClient::handle_close_query_action(const String& nick)
  546. {
  547. m_queries.remove(nick);
  548. m_client_window_list_model->update();
  549. }
  550. void IRCClient::handle_join_action(const String& channel)
  551. {
  552. join_channel(channel);
  553. }
  554. void IRCClient::handle_part_action(const String& channel)
  555. {
  556. part_channel(channel);
  557. }