You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Server.java 44KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292
  1. /*
  2. * Copyright (c) 2006-2015 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc;
  23. import com.dmdirc.commandparser.CommandType;
  24. import com.dmdirc.commandparser.parsers.CommandParser;
  25. import com.dmdirc.config.profiles.Profile;
  26. import com.dmdirc.events.AppErrorEvent;
  27. import com.dmdirc.events.ChannelOpenedEvent;
  28. import com.dmdirc.events.QueryOpenedEvent;
  29. import com.dmdirc.events.ServerConnectErrorEvent;
  30. import com.dmdirc.events.ServerConnectedEvent;
  31. import com.dmdirc.events.ServerConnectingEvent;
  32. import com.dmdirc.events.ServerDisconnectedEvent;
  33. import com.dmdirc.events.ServerInviteExpiredEvent;
  34. import com.dmdirc.interfaces.Connection;
  35. import com.dmdirc.interfaces.GroupChat;
  36. import com.dmdirc.interfaces.User;
  37. import com.dmdirc.interfaces.config.ConfigChangeListener;
  38. import com.dmdirc.interfaces.config.ConfigProvider;
  39. import com.dmdirc.interfaces.config.ConfigProviderMigrator;
  40. import com.dmdirc.interfaces.config.IdentityFactory;
  41. import com.dmdirc.logger.ErrorLevel;
  42. import com.dmdirc.parser.common.ChannelJoinRequest;
  43. import com.dmdirc.parser.common.DefaultStringConverter;
  44. import com.dmdirc.parser.common.IgnoreList;
  45. import com.dmdirc.parser.common.ParserError;
  46. import com.dmdirc.parser.common.ThreadedParser;
  47. import com.dmdirc.parser.interfaces.ChannelInfo;
  48. import com.dmdirc.parser.interfaces.ClientInfo;
  49. import com.dmdirc.parser.interfaces.EncodingParser;
  50. import com.dmdirc.parser.interfaces.LocalClientInfo;
  51. import com.dmdirc.parser.interfaces.Parser;
  52. import com.dmdirc.parser.interfaces.ProtocolDescription;
  53. import com.dmdirc.parser.interfaces.SecureParser;
  54. import com.dmdirc.parser.interfaces.StringConverter;
  55. import com.dmdirc.tls.CertificateManager;
  56. import com.dmdirc.ui.WindowManager;
  57. import com.dmdirc.ui.core.components.WindowComponent;
  58. import com.dmdirc.ui.input.TabCompleterFactory;
  59. import com.dmdirc.ui.input.TabCompletionType;
  60. import com.dmdirc.ui.messages.BackBufferFactory;
  61. import com.dmdirc.ui.messages.Formatter;
  62. import com.dmdirc.ui.messages.HighlightManager;
  63. import com.dmdirc.ui.messages.sink.MessageSinkManager;
  64. import com.dmdirc.util.URLBuilder;
  65. import java.net.NoRouteToHostException;
  66. import java.net.SocketException;
  67. import java.net.SocketTimeoutException;
  68. import java.net.URI;
  69. import java.net.URISyntaxException;
  70. import java.net.UnknownHostException;
  71. import java.util.ArrayList;
  72. import java.util.Arrays;
  73. import java.util.Collection;
  74. import java.util.Collections;
  75. import java.util.Date;
  76. import java.util.HashSet;
  77. import java.util.List;
  78. import java.util.Map;
  79. import java.util.Optional;
  80. import java.util.concurrent.ConcurrentSkipListMap;
  81. import java.util.concurrent.ScheduledExecutorService;
  82. import java.util.concurrent.ScheduledFuture;
  83. import java.util.concurrent.TimeUnit;
  84. import java.util.concurrent.locks.ReadWriteLock;
  85. import java.util.concurrent.locks.ReentrantReadWriteLock;
  86. import java.util.function.Function;
  87. import java.util.stream.Collectors;
  88. import javax.annotation.Nonnull;
  89. import javax.annotation.Nullable;
  90. import javax.net.ssl.SSLException;
  91. import javax.net.ssl.TrustManager;
  92. import org.slf4j.Logger;
  93. import org.slf4j.LoggerFactory;
  94. import static com.google.common.base.Preconditions.checkNotNull;
  95. /**
  96. * The Server class represents the client's view of a server. It maintains a list of all channels,
  97. * queries, etc, and handles parser callbacks pertaining to the server.
  98. */
  99. public class Server extends FrameContainer implements Connection {
  100. private static final Logger LOG = LoggerFactory.getLogger(Server.class);
  101. /** The name of the general domain. */
  102. private static final String DOMAIN_GENERAL = "general";
  103. /** Open channels that currently exist on the server. */
  104. private final ChannelMap channels = new ChannelMap();
  105. /** Open query windows on the server. */
  106. private final Map<String, Query> queries = new ConcurrentSkipListMap<>();
  107. /** The user manager to retrieve users from. */
  108. private final UserManager userManager;
  109. /** The Parser instance handling this server. */
  110. @Nonnull
  111. private Optional<Parser> parser = Optional.empty();
  112. /** The Parser instance that used to be handling this server. */
  113. @Nonnull
  114. private Optional<Parser> oldParser = Optional.empty();
  115. /** The parser-supplied protocol description object. */
  116. @Nonnull
  117. private Optional<ProtocolDescription> protocolDescription = Optional.empty();
  118. /**
  119. * Object used to synchronise access to parser. This object should be locked by anything
  120. * requiring that the parser reference remains the same for a duration of time, or by anything
  121. * which is updating the parser reference.
  122. *
  123. * If used in conjunction with myStateLock, the parserLock must always be locked INSIDE the
  124. * myStateLock to prevent deadlocks.
  125. */
  126. private final ReadWriteLock parserLock = new ReentrantReadWriteLock();
  127. /** Object used to synchronise access to myState. */
  128. private final Object myStateLock = new Object();
  129. /** The current state of this server. */
  130. private final ServerStatus myState = new ServerStatus(this, myStateLock);
  131. /** The address of the server we're connecting to. */
  132. @Nonnull
  133. private URI address;
  134. /** The profile we're using. */
  135. @Nonnull
  136. private Profile profile;
  137. /** Our reason for being away, if any. */
  138. private Optional<String> awayMessage;
  139. /** Our event handler. */
  140. private final ServerEventHandler eventHandler;
  141. /** A list of outstanding invites. */
  142. private final List<Invite> invites = new ArrayList<>();
  143. /** A set of channels we want to join without focusing. */
  144. private final Collection<String> backgroundChannels = new HashSet<>();
  145. /** Our ignore list. */
  146. private final IgnoreList ignoreList = new IgnoreList();
  147. /** Our string convertor. */
  148. private StringConverter converter = new DefaultStringConverter();
  149. /** ParserFactory we use for creating parsers. */
  150. private final ParserFactory parserFactory;
  151. /** ServerManager that created us. */
  152. private final ServerManager manager;
  153. /** Factory to use to create new identities. */
  154. private final IdentityFactory identityFactory;
  155. /** Window manager to pas to children. */
  156. private final WindowManager windowManager;
  157. /** The migrator to use to change our config provider. */
  158. private final ConfigProviderMigrator configMigrator;
  159. /** Factory to use for creating channels. */
  160. private final ChannelFactory channelFactory;
  161. /** Factory to use for creating queries. */
  162. private final QueryFactory queryFactory;
  163. /** The config provider to write user settings to. */
  164. private final ConfigProvider userSettings;
  165. /** Executor service to use to schedule repeated events. */
  166. private final ScheduledExecutorService executorService;
  167. /** The message encoder factory to create a message encoder with. */
  168. private final MessageEncoderFactory messageEncoderFactory;
  169. /** The manager to use for highlighting. */
  170. private final HighlightManager highlightManager;
  171. /** Listener to use for config changes. */
  172. private final ConfigChangeListener configListener = (domain, key) -> updateTitle();
  173. /** The future used when a who timer is scheduled. */
  174. private ScheduledFuture<?> whoTimerFuture;
  175. /** The future used when a reconnect timer is scheduled. */
  176. private ScheduledFuture<?> reconnectTimerFuture;
  177. /**
  178. * Creates a new server which will connect to the specified URL with the specified profile.
  179. */
  180. public Server(
  181. final ServerManager manager,
  182. final ConfigProviderMigrator configMigrator,
  183. final CommandParser commandParser,
  184. final ParserFactory parserFactory,
  185. final TabCompleterFactory tabCompleterFactory,
  186. final IdentityFactory identityFactory,
  187. final MessageSinkManager messageSinkManager,
  188. final WindowManager windowManager,
  189. final ChannelFactory channelFactory,
  190. final QueryFactory queryFactory,
  191. final URLBuilder urlBuilder,
  192. final DMDircMBassador eventBus,
  193. final MessageEncoderFactory messageEncoderFactory,
  194. final ConfigProvider userSettings,
  195. final ScheduledExecutorService executorService,
  196. @Nonnull final URI uri,
  197. @Nonnull final Profile profile,
  198. final BackBufferFactory backBufferFactory,
  199. final UserManager userManager) {
  200. super(null, "server-disconnected",
  201. getHost(uri),
  202. getHost(uri),
  203. configMigrator.getConfigProvider(),
  204. backBufferFactory,
  205. urlBuilder,
  206. commandParser,
  207. tabCompleterFactory.getTabCompleter(configMigrator.getConfigProvider(),
  208. CommandType.TYPE_SERVER, CommandType.TYPE_GLOBAL),
  209. messageSinkManager,
  210. eventBus,
  211. Arrays.asList(
  212. WindowComponent.TEXTAREA.getIdentifier(),
  213. WindowComponent.INPUTFIELD.getIdentifier(),
  214. WindowComponent.CERTIFICATE_VIEWER.getIdentifier()));
  215. this.manager = manager;
  216. this.parserFactory = parserFactory;
  217. this.identityFactory = identityFactory;
  218. this.windowManager = windowManager;
  219. this.configMigrator = configMigrator;
  220. this.channelFactory = channelFactory;
  221. this.queryFactory = queryFactory;
  222. this.executorService = executorService;
  223. this.userSettings = userSettings;
  224. this.messageEncoderFactory = messageEncoderFactory;
  225. this.userManager = userManager;
  226. awayMessage = Optional.empty();
  227. eventHandler = new ServerEventHandler(this, eventBus);
  228. this.address = uri;
  229. this.profile = profile;
  230. setConnectionDetails(uri, profile);
  231. updateIcon();
  232. getConfigManager().addChangeListener("formatter", "serverName", configListener);
  233. getConfigManager().addChangeListener("formatter", "serverTitle", configListener);
  234. this.highlightManager = new HighlightManager();
  235. getEventBus().subscribe(highlightManager);
  236. }
  237. /**
  238. * Updates the connection details for this server. If the specified URI does not define a port,
  239. * the default port from the protocol description will be used.
  240. *
  241. * @param uri The new URI that this server should connect to
  242. * @param profile The profile that this server should use
  243. */
  244. private void setConnectionDetails(final URI uri, final Profile profile) {
  245. this.address = checkNotNull(uri);
  246. this.protocolDescription = Optional.ofNullable(parserFactory.getDescription(uri));
  247. this.profile = profile;
  248. if (uri.getPort() == -1) {
  249. protocolDescription.ifPresent(pd -> {
  250. try {
  251. this.address = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(),
  252. pd.getDefaultPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
  253. } catch (URISyntaxException ex) {
  254. getEventBus().publish(
  255. new AppErrorEvent(ErrorLevel.MEDIUM, ex, "Unable to construct URI",
  256. ""));
  257. }
  258. });
  259. }
  260. }
  261. @Override
  262. public void connect() {
  263. connect(address, profile);
  264. }
  265. @Override
  266. @Precondition({
  267. "The current parser is null or not connected",
  268. "The specified profile is not null"
  269. })
  270. @SuppressWarnings("fallthrough")
  271. public void connect(final URI address, final Profile profile) {
  272. checkNotNull(address);
  273. checkNotNull(profile);
  274. synchronized (myStateLock) {
  275. LOG.info("Connecting to {}, current state is {}", address, myState.getState());
  276. switch (myState.getState()) {
  277. case RECONNECT_WAIT:
  278. LOG.debug("Cancelling reconnection timer");
  279. if (reconnectTimerFuture != null) {
  280. reconnectTimerFuture.cancel(false);
  281. }
  282. break;
  283. case CLOSING:
  284. // Ignore the connection attempt
  285. return;
  286. case CONNECTED:
  287. case CONNECTING:
  288. disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
  289. case DISCONNECTING:
  290. while (!myState.getState().isDisconnected()) {
  291. try {
  292. myStateLock.wait();
  293. } catch (InterruptedException ex) {
  294. return;
  295. }
  296. }
  297. break;
  298. default:
  299. // Do nothing
  300. break;
  301. }
  302. final URI connectAddress;
  303. final Parser newParser;
  304. try {
  305. parserLock.writeLock().lock();
  306. if (parser.isPresent()) {
  307. throw new IllegalArgumentException("Connection attempt while parser "
  308. + "is still connected.\n\nMy state:" + getState());
  309. }
  310. configMigrator.migrate(address.getScheme(), "", "", address.getHost());
  311. setConnectionDetails(address, profile);
  312. updateTitle();
  313. updateIcon();
  314. parser = Optional.ofNullable(buildParser());
  315. if (!parser.isPresent()) {
  316. addLine("serverUnknownProtocol", address.getScheme());
  317. return;
  318. }
  319. newParser = parser.get();
  320. connectAddress = newParser.getURI();
  321. } finally {
  322. parserLock.writeLock().unlock();
  323. }
  324. addLine("serverConnecting", connectAddress.getHost(), connectAddress.getPort());
  325. myState.transition(ServerState.CONNECTING);
  326. doCallbacks();
  327. updateAwayState(Optional.empty());
  328. removeInvites();
  329. newParser.connect();
  330. if (newParser instanceof ThreadedParser) {
  331. ((ThreadedParser) newParser).getControlThread()
  332. .setName("Parser - " + connectAddress.getHost());
  333. }
  334. }
  335. getEventBus().publish(new ServerConnectingEvent(this));
  336. }
  337. @Override
  338. public void reconnect(final String reason) {
  339. synchronized (myStateLock) {
  340. if (myState.getState() == ServerState.CLOSING) {
  341. return;
  342. }
  343. disconnect(reason);
  344. connect(address, profile);
  345. }
  346. }
  347. @Override
  348. public void reconnect() {
  349. reconnect(getConfigManager().getOption(DOMAIN_GENERAL, "reconnectmessage"));
  350. }
  351. @Override
  352. public void disconnect() {
  353. disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
  354. }
  355. @Override
  356. public void disconnect(final String reason) {
  357. synchronized (myStateLock) {
  358. LOG.info("Disconnecting. Current state: {}", myState.getState());
  359. switch (myState.getState()) {
  360. case CLOSING:
  361. case DISCONNECTING:
  362. case DISCONNECTED:
  363. case TRANSIENTLY_DISCONNECTED:
  364. return;
  365. case RECONNECT_WAIT:
  366. LOG.debug("Cancelling reconnection timer");
  367. if (reconnectTimerFuture != null) {
  368. reconnectTimerFuture.cancel(false);
  369. }
  370. break;
  371. default:
  372. break;
  373. }
  374. channels.resetAll();
  375. backgroundChannels.clear();
  376. try {
  377. parserLock.readLock().lock();
  378. if (parser.isPresent()) {
  379. myState.transition(ServerState.DISCONNECTING);
  380. removeInvites();
  381. updateIcon();
  382. parser.get().disconnect(reason);
  383. } else {
  384. myState.transition(ServerState.DISCONNECTED);
  385. }
  386. } finally {
  387. parserLock.readLock().unlock();
  388. }
  389. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closechannelsonquit")) {
  390. channels.closeAll();
  391. }
  392. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closequeriesonquit")) {
  393. closeQueries();
  394. }
  395. }
  396. }
  397. /**
  398. * Schedules a reconnect attempt to be performed after a user-defined delay.
  399. */
  400. @Precondition("The server state is transiently disconnected")
  401. private void doDelayedReconnect() {
  402. synchronized (myStateLock) {
  403. LOG.info("Performing delayed reconnect. State: {}", myState.getState());
  404. if (myState.getState() != ServerState.TRANSIENTLY_DISCONNECTED) {
  405. throw new IllegalStateException("doDelayedReconnect when not "
  406. + "transiently disconnected\n\nState: " + myState);
  407. }
  408. final int delay = Math.max(1000,
  409. getConfigManager().getOptionInt(DOMAIN_GENERAL, "reconnectdelay"));
  410. handleNotification("connectRetry", getAddress(), delay / 1000);
  411. reconnectTimerFuture = executorService.schedule(() -> {
  412. synchronized (myStateLock) {
  413. LOG.debug("Reconnect task executing, state: {}", myState.getState());
  414. if (myState.getState() == ServerState.RECONNECT_WAIT) {
  415. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  416. reconnect();
  417. }
  418. }
  419. }, delay, TimeUnit.MILLISECONDS);
  420. LOG.info("Scheduling reconnect task for delay of {}", delay);
  421. myState.transition(ServerState.RECONNECT_WAIT);
  422. updateIcon();
  423. }
  424. }
  425. @Override
  426. public Optional<GroupChat> getChannel(final String channel) {
  427. return channels.get(channel).map(c -> (GroupChat) c);
  428. }
  429. @Override
  430. public Collection<GroupChat> getChannels() {
  431. return channels.getAll().parallelStream()
  432. .map(c -> (GroupChat) c)
  433. .collect(Collectors.toList());
  434. }
  435. @Override
  436. public boolean hasQuery(final String host) {
  437. return queries.containsKey(converter.toLowerCase(parseHostmask(host)[0]));
  438. }
  439. @Override
  440. public User getLocalUser() {
  441. return parser.map(Parser::getLocalClient)
  442. .map(client -> userManager.getUserFromClientInfo(client, this)).get();
  443. }
  444. @Override
  445. public User getUser(final String details) {
  446. return parser.map(p -> p.getClient(details))
  447. .map(client -> userManager.getUserFromClientInfo(client, this)).get();
  448. }
  449. @Override
  450. public Query getQuery(final String host) {
  451. return getQuery(host, false);
  452. }
  453. @Override
  454. public Query getQuery(final String host, final boolean focus) {
  455. synchronized (myStateLock) {
  456. if (myState.getState() == ServerState.CLOSING) {
  457. // Can't open queries while the server is closing
  458. return null;
  459. }
  460. }
  461. final String nick = parseHostmask(host)[0];
  462. final String lnick = converter.toLowerCase(nick);
  463. if (!queries.containsKey(lnick)) {
  464. final Query newQuery = queryFactory.getQuery(this, getUser(host));
  465. if (!getState().isDisconnected()) {
  466. newQuery.reregister();
  467. }
  468. windowManager.addWindow(this, newQuery, focus);
  469. getEventBus().publish(new QueryOpenedEvent(newQuery));
  470. getTabCompleter().addEntry(TabCompletionType.QUERY_NICK, nick);
  471. queries.put(lnick, newQuery);
  472. }
  473. return queries.get(lnick);
  474. }
  475. @Override
  476. public void updateQuery(final Query query, final String oldNick, final String newNick) {
  477. getTabCompleter().removeEntry(TabCompletionType.QUERY_NICK, oldNick);
  478. getTabCompleter().addEntry(TabCompletionType.QUERY_NICK, newNick);
  479. queries.put(converter.toLowerCase(newNick), query);
  480. queries.remove(converter.toLowerCase(oldNick));
  481. }
  482. @Override
  483. public Collection<Query> getQueries() {
  484. return Collections.unmodifiableCollection(queries.values());
  485. }
  486. @Override
  487. public void delQuery(final Query query) {
  488. getTabCompleter().removeEntry(TabCompletionType.QUERY_NICK, query.getNickname());
  489. queries.remove(converter.toLowerCase(query.getNickname()));
  490. }
  491. @Override
  492. public void delChannel(final String chan) {
  493. getTabCompleter().removeEntry(TabCompletionType.CHANNEL, chan);
  494. channels.remove(chan);
  495. }
  496. public Channel addChannel(final ChannelInfo chan) {
  497. return addChannel(chan, !backgroundChannels.contains(chan.getName())
  498. || getConfigManager().getOptionBool(DOMAIN_GENERAL, "hidechannels"));
  499. }
  500. public Channel addChannel(final ChannelInfo chan, final boolean focus) {
  501. synchronized (myStateLock) {
  502. if (myState.getState() == ServerState.CLOSING) {
  503. // Can't join channels while the server is closing
  504. return null;
  505. }
  506. }
  507. backgroundChannels.remove(chan.getName());
  508. final Optional<Channel> channel = channels.get(chan.getName());
  509. if (channel.isPresent()) {
  510. channel.get().setChannelInfo(chan);
  511. channel.get().selfJoin();
  512. return channel.get();
  513. } else {
  514. final ConfigProviderMigrator channelConfig = identityFactory.createMigratableConfig(
  515. getProtocol(), getIrcd(), getNetwork(), getAddress(), chan.getName());
  516. final Channel newChan = channelFactory.getChannel(this, chan, channelConfig);
  517. windowManager.addWindow(this, newChan, focus);
  518. getEventBus().publish(new ChannelOpenedEvent(newChan));
  519. getTabCompleter().addEntry(TabCompletionType.CHANNEL, chan.getName());
  520. channels.add(newChan);
  521. return newChan;
  522. }
  523. }
  524. /**
  525. * Closes all open query windows associated with this server.
  526. */
  527. private void closeQueries() {
  528. new ArrayList<>(queries.values()).forEach(Query::close);
  529. }
  530. /**
  531. * Retrieves the host component of the specified URI, or throws a relevant exception if this is
  532. * not possible.
  533. *
  534. * @param uri The URI to be processed
  535. *
  536. * @return The URI's host component, as returned by {@link URI#getHost()}.
  537. *
  538. * @throws NullPointerException If <code>uri</code> is null
  539. * @throws IllegalArgumentException If the specified URI has no host
  540. * @since 0.6.4
  541. */
  542. private static String getHost(final URI uri) {
  543. if (uri.getHost() == null) {
  544. throw new IllegalArgumentException("URIs must have hosts");
  545. }
  546. return uri.getHost();
  547. }
  548. /**
  549. * Builds an appropriately configured {@link Parser} for this server.
  550. *
  551. * @return A configured parser.
  552. */
  553. @Nullable
  554. private Parser buildParser() {
  555. final Parser myParser = parserFactory.getParser(profile, address, getConfigManager())
  556. .orElse(null);
  557. if (myParser != null) {
  558. myParser.setIgnoreList(ignoreList);
  559. }
  560. if (myParser instanceof SecureParser) {
  561. final CertificateManager certificateManager =
  562. new CertificateManager(this, address.getHost(), getConfigManager(),
  563. userSettings, getEventBus());
  564. final SecureParser secureParser = (SecureParser) myParser;
  565. secureParser.setTrustManagers(new TrustManager[]{certificateManager});
  566. secureParser.setKeyManagers(certificateManager.getKeyManager());
  567. }
  568. if (myParser instanceof EncodingParser) {
  569. final EncodingParser encodingParser = (EncodingParser) myParser;
  570. encodingParser.setEncoder(messageEncoderFactory.getMessageEncoder(this, myParser));
  571. }
  572. return myParser;
  573. }
  574. @Override
  575. public boolean compareURI(final URI uri) {
  576. return parser.map(p -> p.compareURI(uri)).orElse(
  577. oldParser.map(op -> op.compareURI(uri)).orElse(false));
  578. }
  579. /**
  580. * Parses a hostmask into nickname, username and hostname. Should probably use
  581. * {@link #getUser} instead.
  582. *
  583. * @param hostmask The mask to parse.
  584. */
  585. public String[] parseHostmask(final String hostmask) {
  586. return protocolDescription.get().parseHostmask(hostmask);
  587. }
  588. /**
  589. * Updates this server's icon.
  590. */
  591. private void updateIcon() {
  592. final String icon = myState.getState() == ServerState.CONNECTED
  593. ? protocolDescription.get().isSecure(address)
  594. ? "secure-server" : "server" : "server-disconnected";
  595. setIcon(icon);
  596. }
  597. /**
  598. * Registers callbacks.
  599. */
  600. private void doCallbacks() {
  601. eventHandler.registerCallbacks();
  602. queries.values().forEach(Query::reregister);
  603. }
  604. @Override
  605. public void join(final ChannelJoinRequest... requests) {
  606. join(true, requests);
  607. }
  608. @Override
  609. public void join(final boolean focus, final ChannelJoinRequest... requests) {
  610. synchronized (myStateLock) {
  611. if (myState.getState() == ServerState.CONNECTED) {
  612. final Collection<ChannelJoinRequest> pending = new ArrayList<>();
  613. for (ChannelJoinRequest request : requests) {
  614. removeInvites(request.getName());
  615. final String name;
  616. if (parser.get().isValidChannelName(request.getName())) {
  617. name = request.getName();
  618. } else {
  619. name = parser.get().getChannelPrefixes().substring(0, 1)
  620. + request.getName();
  621. }
  622. if (getChannel(name).map(GroupChat::isOnChannel).orElse(false)) {
  623. if (!focus) {
  624. backgroundChannels.add(name);
  625. }
  626. pending.add(request);
  627. }
  628. }
  629. parser.get().joinChannels(pending.toArray(new ChannelJoinRequest[pending.size()]));
  630. }
  631. // TODO: otherwise: address.getChannels().add(channel);
  632. }
  633. }
  634. @Override
  635. public void sendLine(final String line) {
  636. synchronized (myStateLock) {
  637. try {
  638. parserLock.readLock().lock();
  639. parser.ifPresent(p -> {
  640. if (!line.isEmpty() && myState.getState() == ServerState.CONNECTED) {
  641. p.sendRawMessage(line);
  642. }
  643. });
  644. } finally {
  645. parserLock.readLock().unlock();
  646. }
  647. }
  648. }
  649. @Override
  650. public int getMaxLineLength() {
  651. return withParserReadLock(Parser::getMaxLength, -1);
  652. }
  653. @Override
  654. @Nonnull
  655. public Optional<Parser> getParser() {
  656. return parser;
  657. }
  658. @Nonnull
  659. @Override
  660. public Profile getProfile() {
  661. return profile;
  662. }
  663. @Override
  664. public String getChannelPrefixes() {
  665. return withParserReadLock(Parser::getChannelPrefixes, "#&");
  666. }
  667. @Override
  668. public String getAddress() {
  669. return withParserReadLock(Parser::getServerName, address.getHost());
  670. }
  671. @Override
  672. public String getNetwork() {
  673. try {
  674. parserLock.readLock().lock();
  675. return parser.map(p -> p.getNetworkName().isEmpty()
  676. ? getNetworkFromServerName(p.getServerName()) : p.getNetworkName())
  677. .orElseThrow(() -> new IllegalStateException(
  678. "getNetwork called when " + "parser is null (state: " + getState() +
  679. ')'));
  680. } finally {
  681. parserLock.readLock().unlock();
  682. }
  683. }
  684. @Override
  685. public boolean isNetwork(final String target) {
  686. synchronized (myStateLock) {
  687. return withParserReadLock(p -> getNetwork().equalsIgnoreCase(target), false);
  688. }
  689. }
  690. /**
  691. * Calculates a network name from the specified server name. This method implements parts 2-4 of
  692. * the procedure documented at getNetwork().
  693. *
  694. * @param serverName The server name to parse
  695. *
  696. * @return A network name for the specified server
  697. */
  698. protected static String getNetworkFromServerName(final String serverName) {
  699. final String[] parts = serverName.split("\\.");
  700. final String[] tlds = {"biz", "com", "info", "net", "org"};
  701. boolean isTLD = false;
  702. for (String tld : tlds) {
  703. if (serverName.endsWith('.' + tld)) {
  704. isTLD = true;
  705. break;
  706. }
  707. }
  708. if (isTLD && parts.length > 2) {
  709. return parts[parts.length - 2] + '.' + parts[parts.length - 1];
  710. } else if (parts.length > 2) {
  711. final StringBuilder network = new StringBuilder();
  712. for (int i = 1; i < parts.length; i++) {
  713. if (network.length() > 0) {
  714. network.append('.');
  715. }
  716. network.append(parts[i]);
  717. }
  718. return network.toString();
  719. } else {
  720. return serverName;
  721. }
  722. }
  723. @Override
  724. public String getIrcd() {
  725. return parser.get().getServerSoftwareType();
  726. }
  727. @Override
  728. public String getProtocol() {
  729. return address.getScheme();
  730. }
  731. @Override
  732. public boolean isAway() {
  733. return awayMessage.isPresent();
  734. }
  735. @Override
  736. public String getAwayMessage() {
  737. return awayMessage.orElse(null);
  738. }
  739. @Override
  740. public ServerState getState() {
  741. return myState.getState();
  742. }
  743. @Override
  744. public ServerStatus getStatus() {
  745. return myState;
  746. }
  747. @Override
  748. public void close() {
  749. synchronized (myStateLock) {
  750. eventHandler.unregisterCallbacks();
  751. getConfigManager().removeListener(configListener);
  752. getEventBus().unsubscribe(highlightManager);
  753. executorService.shutdown();
  754. disconnect();
  755. myState.transition(ServerState.CLOSING);
  756. }
  757. channels.closeAll();
  758. closeQueries();
  759. removeInvites();
  760. manager.unregisterServer(this);
  761. super.close();
  762. }
  763. @Override
  764. public FrameContainer getWindowModel() {
  765. return this;
  766. }
  767. @Override
  768. public void addLineToAll(final String messageType, final Date date,
  769. final Object... args) {
  770. channels.addLineToAll(messageType, date, args);
  771. for (Query query : queries.values()) {
  772. query.addLine(messageType, date, args);
  773. }
  774. addLine(messageType, date, args);
  775. }
  776. @Override
  777. public void sendCTCPReply(final String source, final String type, final String args) {
  778. if ("VERSION".equalsIgnoreCase(type)) {
  779. parser.get().sendCTCPReply(source, "VERSION",
  780. "DMDirc " + getConfigManager().getOption("version", "version") +
  781. " - https://www.dmdirc.com/");
  782. } else if ("PING".equalsIgnoreCase(type)) {
  783. parser.get().sendCTCPReply(source, "PING", args);
  784. } else if ("CLIENTINFO".equalsIgnoreCase(type)) {
  785. parser.get().sendCTCPReply(source, "CLIENTINFO", "VERSION PING CLIENTINFO");
  786. }
  787. }
  788. @Override
  789. public boolean isValidChannelName(final String channelName) {
  790. return getChannel(channelName).isPresent()
  791. || withParserReadLock(p -> p.isValidChannelName(channelName), false);
  792. }
  793. @Override
  794. public Optional<Connection> getConnection() {
  795. return Optional.of(this);
  796. }
  797. @Override
  798. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  799. if (arg instanceof User) {
  800. final User clientInfo = (User) arg;
  801. args.add(clientInfo.getNickname());
  802. args.add(clientInfo.getUsername());
  803. args.add(clientInfo.getHostname());
  804. return true;
  805. } else if (arg instanceof ClientInfo) {
  806. final ClientInfo clientInfo = (ClientInfo) arg;
  807. args.add(clientInfo.getNickname());
  808. args.add(clientInfo.getUsername());
  809. args.add(clientInfo.getHostname());
  810. return true;
  811. } else {
  812. return super.processNotificationArg(arg, args);
  813. }
  814. }
  815. @Override
  816. public void updateTitle() {
  817. synchronized (myStateLock) {
  818. if (myState.getState() == ServerState.CLOSING) {
  819. return;
  820. }
  821. try {
  822. parserLock.readLock().lock();
  823. final Object[] arguments = {
  824. address.getHost(), parser.map(Parser::getServerName).orElse("Unknown"),
  825. address.getPort(), parser.map(p -> getNetwork()).orElse("Unknown"),
  826. parser.map(Parser::getLocalClient).map(LocalClientInfo::getNickname).orElse(
  827. "Unknown")
  828. };
  829. setName(Formatter.formatMessage(getConfigManager(),
  830. "serverName", arguments));
  831. setTitle(Formatter.formatMessage(getConfigManager(),
  832. "serverTitle", arguments));
  833. } finally {
  834. parserLock.readLock().unlock();
  835. }
  836. }
  837. }
  838. /**
  839. * Called when the socket has been closed.
  840. */
  841. public void onSocketClosed() {
  842. LOG.info("Received socket closed event, state: {}", myState.getState());
  843. if (whoTimerFuture != null) {
  844. whoTimerFuture.cancel(false);
  845. }
  846. if (Thread.holdsLock(myStateLock)) {
  847. LOG.info("State lock contended: rerunning on a new thread");
  848. executorService.schedule(this::onSocketClosed, 0, TimeUnit.SECONDS);
  849. return;
  850. }
  851. handleNotification("socketClosed", getAddress());
  852. getEventBus().publish(new ServerDisconnectedEvent(this));
  853. eventHandler.unregisterCallbacks();
  854. synchronized (myStateLock) {
  855. if (myState.getState() == ServerState.CLOSING
  856. || myState.getState() == ServerState.DISCONNECTED) {
  857. // This has been triggered via .disconnect()
  858. return;
  859. }
  860. if (myState.getState() == ServerState.DISCONNECTING) {
  861. myState.transition(ServerState.DISCONNECTED);
  862. } else {
  863. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  864. }
  865. channels.resetAll();
  866. try {
  867. parserLock.writeLock().lock();
  868. oldParser = parser;
  869. parser = Optional.empty();
  870. } finally {
  871. parserLock.writeLock().unlock();
  872. }
  873. updateIcon();
  874. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closechannelsondisconnect")) {
  875. channels.closeAll();
  876. }
  877. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closequeriesondisconnect")) {
  878. closeQueries();
  879. }
  880. removeInvites();
  881. updateAwayState(Optional.empty());
  882. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  883. "reconnectondisconnect")
  884. && myState.getState() == ServerState.TRANSIENTLY_DISCONNECTED) {
  885. doDelayedReconnect();
  886. }
  887. }
  888. }
  889. /**
  890. * Called when an error was encountered while connecting.
  891. *
  892. * @param errorInfo The parser's error information
  893. */
  894. @Precondition("The current server state is CONNECTING")
  895. public void onConnectError(final ParserError errorInfo) {
  896. synchronized (myStateLock) {
  897. LOG.info("Received connect error event, state: {}; error: {}", myState.getState(),
  898. errorInfo);
  899. if (myState.getState() == ServerState.CLOSING
  900. || myState.getState() == ServerState.DISCONNECTING) {
  901. // Do nothing
  902. return;
  903. } else if (myState.getState() != ServerState.CONNECTING) {
  904. // Shouldn't happen
  905. throw new IllegalStateException("Connect error when not "
  906. + "connecting\n\n" + getStatus().getTransitionHistory());
  907. }
  908. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  909. try {
  910. parserLock.writeLock().lock();
  911. oldParser = parser;
  912. parser = Optional.empty();
  913. } finally {
  914. parserLock.writeLock().unlock();
  915. }
  916. updateIcon();
  917. final String description;
  918. if (errorInfo.getException() == null) {
  919. description = errorInfo.getData();
  920. } else {
  921. final Exception exception = errorInfo.getException();
  922. if (exception instanceof UnknownHostException) {
  923. description = "Unknown host (unable to resolve)";
  924. } else if (exception instanceof NoRouteToHostException) {
  925. description = "No route to host";
  926. } else if (exception instanceof SocketTimeoutException) {
  927. description = "Connection attempt timed out";
  928. } else if (exception instanceof SocketException
  929. || exception instanceof SSLException) {
  930. description = exception.getMessage();
  931. } else {
  932. getEventBus().publish(new AppErrorEvent(ErrorLevel.LOW,
  933. new IllegalArgumentException(exception),
  934. "Unknown socket error: " + exception.getClass().getCanonicalName(),
  935. ""));
  936. description = "Unknown error: " + exception.getMessage();
  937. }
  938. }
  939. getEventBus().publish(new ServerConnectErrorEvent(this, description));
  940. handleNotification("connectError", getAddress(), description);
  941. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "reconnectonconnectfailure")) {
  942. doDelayedReconnect();
  943. }
  944. }
  945. }
  946. /**
  947. * Called after the parser receives the 005 headers from the server.
  948. */
  949. @Precondition("State is CONNECTING")
  950. public void onPost005() {
  951. synchronized (myStateLock) {
  952. if (myState.getState() != ServerState.CONNECTING) {
  953. // Shouldn't happen
  954. throw new IllegalStateException("Received onPost005 while not "
  955. + "connecting\n\n" + myState.getTransitionHistory());
  956. }
  957. myState.transition(ServerState.CONNECTED);
  958. configMigrator.migrate(address.getScheme(),
  959. parser.get().getServerSoftwareType(), getNetwork(), parser.get().getServerName());
  960. updateIcon();
  961. updateTitle();
  962. updateIgnoreList();
  963. converter = parser.get().getStringConverter();
  964. channels.setStringConverter(converter);
  965. final List<ChannelJoinRequest> requests = new ArrayList<>();
  966. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "rejoinchannels")) {
  967. requests.addAll(channels.asJoinRequests());
  968. }
  969. join(requests.toArray(new ChannelJoinRequest[requests.size()]));
  970. final int whoTime = getConfigManager().getOptionInt(DOMAIN_GENERAL, "whotime");
  971. whoTimerFuture = executorService.scheduleAtFixedRate(
  972. channels.getWhoRunnable(), whoTime, whoTime, TimeUnit.MILLISECONDS);
  973. }
  974. getEventBus().publish(new ServerConnectedEvent(this));
  975. }
  976. @Override
  977. public IgnoreList getIgnoreList() {
  978. return ignoreList;
  979. }
  980. @Override
  981. public void updateIgnoreList() {
  982. ignoreList.clear();
  983. ignoreList.addAll(getConfigManager().getOptionList("network", "ignorelist"));
  984. }
  985. @Override
  986. public void saveIgnoreList() {
  987. getNetworkIdentity().setOption("network", "ignorelist", ignoreList.getRegexList());
  988. }
  989. @Override
  990. public ConfigProvider getServerIdentity() {
  991. return identityFactory.createServerConfig(parser.get().getServerName());
  992. }
  993. @Override
  994. public ConfigProvider getNetworkIdentity() {
  995. return identityFactory.createNetworkConfig(getNetwork());
  996. }
  997. @Override
  998. public void addInvite(final Invite invite) {
  999. synchronized (invites) {
  1000. new ArrayList<>(invites).stream()
  1001. .filter(oldInvite -> oldInvite.getChannel().equals(invite.getChannel()))
  1002. .forEach(this::removeInvite);
  1003. invites.add(invite);
  1004. }
  1005. }
  1006. @Override
  1007. public void acceptInvites(final Invite... invites) {
  1008. final ChannelJoinRequest[] requests = new ChannelJoinRequest[invites.length];
  1009. for (int i = 0; i < invites.length; i++) {
  1010. requests[i] = new ChannelJoinRequest(invites[i].getChannel());
  1011. }
  1012. join(requests);
  1013. }
  1014. @Override
  1015. public void acceptInvites() {
  1016. synchronized (invites) {
  1017. acceptInvites(invites.toArray(new Invite[invites.size()]));
  1018. }
  1019. }
  1020. @Override
  1021. public void removeInvites(final String channel) {
  1022. new ArrayList<>(invites).stream().filter(invite -> invite.getChannel().equals(channel))
  1023. .forEach(this::removeInvite);
  1024. }
  1025. @Override
  1026. public void removeInvites() {
  1027. new ArrayList<>(invites).forEach(this::removeInvite);
  1028. }
  1029. @Override
  1030. public void removeInvite(final Invite invite) {
  1031. synchronized (invites) {
  1032. invites.remove(invite);
  1033. getEventBus().publishAsync(new ServerInviteExpiredEvent(this, invite));
  1034. }
  1035. }
  1036. @Override
  1037. public List<Invite> getInvites() {
  1038. return Collections.unmodifiableList(invites);
  1039. }
  1040. @Override
  1041. public void updateAwayState(final Optional<String> message) {
  1042. checkNotNull(message);
  1043. if (awayMessage.equals(message)) {
  1044. return;
  1045. }
  1046. awayMessage = message;
  1047. }
  1048. @Override
  1049. public String getUserModes() {
  1050. return getParser().map(Parser::getChannelUserModes).orElse("");
  1051. }
  1052. @Override
  1053. public String getBooleanModes() {
  1054. return getParser().map(Parser::getBooleanChannelModes).orElse("");
  1055. }
  1056. @Override
  1057. public String getListModes() {
  1058. return getParser().map(Parser::getListChannelModes).orElse("");
  1059. }
  1060. @Override
  1061. public String getParameterModes() {
  1062. return getParser().map(Parser::getParameterChannelModes).orElse("");
  1063. }
  1064. @Override
  1065. public String getDoubleParameterModes() {
  1066. return getParser().map(Parser::getDoubleParameterChannelModes).orElse("");
  1067. }
  1068. @Override
  1069. public int getMaxListModes(final char mode) {
  1070. return getParser().map(p -> p.getMaxListModes(mode)).orElse(-1);
  1071. }
  1072. /**
  1073. * Utility method to get a result from the parser while holding the {@link #parserLock}
  1074. * read lock.
  1075. *
  1076. * @param func The function to use to retrieve information from the parser.
  1077. * @param orElse The value to return if the parser is otherwise not present.
  1078. * @param <T> The type of result returned.
  1079. * @return The value returned by {@code func}, if the parser is present, otherwise the
  1080. * {@code orElse} value.
  1081. */
  1082. private <T> T withParserReadLock(
  1083. final Function<? super Parser, ? extends T> func,
  1084. final T orElse) {
  1085. try {
  1086. parserLock.readLock().lock();
  1087. return parser.map(func).orElse(orElse);
  1088. } finally {
  1089. parserLock.readLock().unlock();
  1090. }
  1091. }
  1092. }