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.

DCCManager.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  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.addons.dcc;
  23. import com.dmdirc.ClientModule.GlobalConfig;
  24. import com.dmdirc.DMDircMBassador;
  25. import com.dmdirc.addons.dcc.events.DccChatRequestEvent;
  26. import com.dmdirc.addons.dcc.events.DccChatStartingEvent;
  27. import com.dmdirc.addons.dcc.events.DccSendRequestEvent;
  28. import com.dmdirc.addons.dcc.io.DCC;
  29. import com.dmdirc.addons.dcc.io.DCCChat;
  30. import com.dmdirc.addons.dcc.io.DCCTransfer;
  31. import com.dmdirc.addons.dcc.kde.KFileChooser;
  32. import com.dmdirc.addons.dcc.ui.PlaceholderPanel;
  33. import com.dmdirc.addons.dcc.ui.TransferPanel;
  34. import com.dmdirc.addons.ui_swing.SwingWindowFactory;
  35. import com.dmdirc.addons.ui_swing.components.frames.ComponentFrameFactory;
  36. import com.dmdirc.addons.ui_swing.components.frames.TextFrame;
  37. import com.dmdirc.addons.ui_swing.dialogs.StandardQuestionDialog;
  38. import com.dmdirc.addons.ui_swing.injection.MainWindow;
  39. import com.dmdirc.commandline.CommandLineOptionsModule.Directory;
  40. import com.dmdirc.commandline.CommandLineOptionsModule.DirectoryType;
  41. import com.dmdirc.commandparser.parsers.CommandParser;
  42. import com.dmdirc.commandparser.parsers.GlobalCommandParser;
  43. import com.dmdirc.config.prefs.PluginPreferencesCategory;
  44. import com.dmdirc.config.prefs.PreferencesCategory;
  45. import com.dmdirc.config.prefs.PreferencesDialogModel;
  46. import com.dmdirc.config.prefs.PreferencesSetting;
  47. import com.dmdirc.config.prefs.PreferencesType;
  48. import com.dmdirc.events.ClientPrefsOpenedEvent;
  49. import com.dmdirc.events.ServerCtcpEvent;
  50. import com.dmdirc.interfaces.CommandController;
  51. import com.dmdirc.interfaces.Connection;
  52. import com.dmdirc.interfaces.User;
  53. import com.dmdirc.interfaces.WindowModel;
  54. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  55. import com.dmdirc.interfaces.config.ConfigProvider;
  56. import com.dmdirc.interfaces.config.IdentityController;
  57. import com.dmdirc.parser.interfaces.Parser;
  58. import com.dmdirc.plugins.PluginDomain;
  59. import com.dmdirc.plugins.PluginInfo;
  60. import com.dmdirc.ui.WindowManager;
  61. import com.dmdirc.ui.input.TabCompleterFactory;
  62. import com.dmdirc.ui.messages.BackBufferFactory;
  63. import com.google.common.collect.Sets;
  64. import java.awt.Dialog;
  65. import java.awt.Window;
  66. import java.io.File;
  67. import java.io.IOException;
  68. import java.net.InetAddress;
  69. import java.net.UnknownHostException;
  70. import java.util.Collections;
  71. import java.util.Set;
  72. import java.util.function.Supplier;
  73. import javax.inject.Inject;
  74. import javax.inject.Singleton;
  75. import javax.swing.JComponent;
  76. import javax.swing.JFileChooser;
  77. import javax.swing.JOptionPane;
  78. import org.slf4j.Logger;
  79. import org.slf4j.LoggerFactory;
  80. import net.engio.mbassy.listener.Handler;
  81. import static com.dmdirc.util.LogUtils.USER_ERROR;
  82. /**
  83. * This plugin adds DCC to DMDirc.
  84. */
  85. @Singleton
  86. public class DCCManager {
  87. private static final Logger LOG = LoggerFactory.getLogger(DCCManager.class);
  88. private final BackBufferFactory backBufferFactory;
  89. /** Our DCC Container window. */
  90. private PlaceholderContainer container;
  91. /** Config manager to read settings from. */
  92. private final AggregateConfigProvider config;
  93. /** Window Management. */
  94. private final WindowManager windowManager;
  95. /** The command controller to use. */
  96. private final CommandController commandController;
  97. /** The factory to use for tab completers. */
  98. private final TabCompleterFactory tabCompleterFactory;
  99. /** The client's main window that will parent any new windows. */
  100. private final Window mainWindow;
  101. /** The configuration domain to use. */
  102. private final String domain;
  103. /** The bus to dispatch events on. */
  104. private final DMDircMBassador eventBus;
  105. /** Plugin info. */
  106. private final PluginInfo pluginInfo;
  107. /**
  108. * Creates a new instance of this plugin.
  109. */
  110. @Inject
  111. public DCCManager(
  112. @MainWindow final Window mainWindow,
  113. @PluginDomain(DCCPlugin.class) final PluginInfo pluginInfo,
  114. final IdentityController identityController,
  115. @GlobalConfig final AggregateConfigProvider globalConfig,
  116. final CommandController commandController,
  117. final WindowManager windowManager,
  118. final TabCompleterFactory tabCompleterFactory,
  119. final SwingWindowFactory windowFactory,
  120. final ComponentFrameFactory componentFrameFactory,
  121. final DMDircMBassador eventBus,
  122. final GlobalCommandParser commandParser,
  123. @Directory(DirectoryType.BASE) final String baseDirectory,
  124. final BackBufferFactory backBufferFactory) {
  125. this.mainWindow = mainWindow;
  126. this.windowManager = windowManager;
  127. this.commandController = commandController;
  128. this.tabCompleterFactory = tabCompleterFactory;
  129. this.pluginInfo = pluginInfo;
  130. this.domain = pluginInfo.getDomain();
  131. this.config = globalConfig;
  132. this.eventBus = eventBus;
  133. this.backBufferFactory = backBufferFactory;
  134. windowFactory.registerImplementation(new ComponentFrameWindowProvider(
  135. "com.dmdirc.addons.dcc.ui.PlaceholderPanel", componentFrameFactory,
  136. commandParser, PlaceholderPanel::new));
  137. windowFactory.registerImplementation(new ComponentFrameWindowProvider(
  138. "com.dmdirc.addons.dcc.ui.TransferPanel", componentFrameFactory,
  139. commandParser, () -> new TransferPanel(container, eventBus)));
  140. final ConfigProvider defaults = identityController.getAddonSettings();
  141. defaults.setOption(domain, "receive.savelocation",
  142. baseDirectory + "downloads" + File.separator);
  143. }
  144. @Handler
  145. public void handlePrefsOpened(final ClientPrefsOpenedEvent event) {
  146. final PreferencesDialogModel manager = event.getModel();
  147. final PreferencesCategory general = new PluginPreferencesCategory(
  148. pluginInfo, "DCC", "", "category-dcc");
  149. final PreferencesCategory firewall = new PluginPreferencesCategory(
  150. pluginInfo, "Firewall", "");
  151. final PreferencesCategory sending = new PluginPreferencesCategory(
  152. pluginInfo, "Sending", "");
  153. final PreferencesCategory receiving = new PluginPreferencesCategory(
  154. pluginInfo, "Receiving", "");
  155. manager.getCategory("Plugins").addSubCategory(general.setInlineAfter());
  156. general.addSubCategory(firewall.setInline());
  157. general.addSubCategory(sending.setInline());
  158. general.addSubCategory(receiving.setInline());
  159. firewall.addSetting(
  160. new PreferencesSetting(PreferencesType.TEXT, pluginInfo.getDomain(), "firewall.ip",
  161. "Forced IP", "What IP should be sent as our IP (Blank = work it out)",
  162. manager.getConfigManager(), manager.getIdentity()));
  163. firewall.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN, pluginInfo.getDomain(),
  164. "firewall.ports.usePortRange", "Use Port Range",
  165. "Useful if you have a firewall that only forwards specific " + "ports",
  166. manager.getConfigManager(), manager.getIdentity()));
  167. firewall.addSetting(new PreferencesSetting(PreferencesType.INTEGER,
  168. pluginInfo.getDomain(), "firewall.ports.startPort", "Start Port",
  169. "Port to try to listen on first", manager.getConfigManager(),
  170. manager.getIdentity()));
  171. firewall.addSetting(new PreferencesSetting(PreferencesType.INTEGER,
  172. pluginInfo.getDomain(), "firewall.ports.endPort", "End Port",
  173. "Port to try to listen on last", manager.getConfigManager(),
  174. manager.getIdentity()));
  175. receiving.addSetting(new PreferencesSetting(PreferencesType.DIRECTORY,
  176. pluginInfo.getDomain(), "receive.savelocation", "Default save location",
  177. "Where the save as window defaults to?",
  178. manager.getConfigManager(), manager.getIdentity()));
  179. sending.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  180. pluginInfo.getDomain(), "send.reverse", "Reverse DCC",
  181. "With reverse DCC, the sender connects rather than "
  182. + "listens like normal dcc", manager.getConfigManager(),
  183. manager.getIdentity()));
  184. sending.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  185. pluginInfo.getDomain(), "send.forceturbo", "Use Turbo DCC",
  186. "Turbo DCC doesn't wait for ack packets. this is "
  187. + "faster but not always supported.",
  188. manager.getConfigManager(), manager.getIdentity()));
  189. receiving.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN,
  190. pluginInfo.getDomain(), "receive.reverse.sendtoken",
  191. "Send token in reverse receive",
  192. "If you have problems with reverse dcc receive resume,"
  193. + " try toggling this.", manager.getConfigManager(),
  194. manager.getIdentity()));
  195. general.addSetting(new PreferencesSetting(PreferencesType.INTEGER,
  196. pluginInfo.getDomain(), "send.blocksize", "Blocksize to use for DCC",
  197. "Change the block size for send/receive, this can "
  198. + "sometimes speed up transfers.", manager.getConfigManager(),
  199. manager.getIdentity()));
  200. general.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN, pluginInfo.getDomain(),
  201. "general.percentageInTitle", "Show percentage of transfers in the window title",
  202. "Show the current percentage of transfers in the DCC window " + "title",
  203. manager.getConfigManager(), manager.getIdentity()));
  204. }
  205. public String getDomain() {
  206. return domain;
  207. }
  208. /**
  209. * Ask the location to save a file, then start the download.
  210. *
  211. * @param nickname Person this dcc is from.
  212. * @param send The DCCSend to save for.
  213. * @param parser The parser this send was received on
  214. * @param reverse Is this a reverse dcc?
  215. * @param token Token used in reverse dcc.
  216. */
  217. public void saveFile(final String nickname, final DCCTransfer send,
  218. final Parser parser, final boolean reverse, final String token) {
  219. // New thread to ask the user where to save in to stop us locking the UI
  220. new Thread(() -> {
  221. final JFileChooser jc = KFileChooser.getFileChooser(config, this,
  222. config.getOption(getDomain(), "receive.savelocation"));
  223. final int result;
  224. if (config.getOptionBool(getDomain(), "receive.autoaccept")) {
  225. result = JFileChooser.APPROVE_OPTION;
  226. } else {
  227. result = showFileChooser(send, jc);
  228. }
  229. if (result != JFileChooser.APPROVE_OPTION) {
  230. return;
  231. }
  232. send.setFileName(jc.getSelectedFile().getPath());
  233. if (!handleExists(send, jc, nickname, parser, reverse, token)) {
  234. return;
  235. }
  236. final boolean resume = handleResume(jc);
  237. if (reverse && !token.isEmpty()) {
  238. final TransferContainer container1 = new TransferContainer(this, send,
  239. config, backBufferFactory, "*Receive: " + nickname, nickname, null,
  240. eventBus);
  241. windowManager.addWindow(getContainer(), container1);
  242. send.setToken(token);
  243. if (resume) {
  244. if (config.getOptionBool(getDomain(),
  245. "receive.reverse.sendtoken")) {
  246. parser.sendCTCP(nickname, "DCC", "RESUME "
  247. + send.getShortFileName() + " 0 "
  248. + jc.getSelectedFile().length() + ' '
  249. + token);
  250. } else {
  251. parser.sendCTCP(nickname, "DCC", "RESUME "
  252. + send.getShortFileName() + " 0 "
  253. + jc.getSelectedFile().length());
  254. }
  255. } else {
  256. if (listen(send)) {
  257. parser.sendCTCP(nickname, "DCC", "SEND "
  258. + send.getShortFileName() + ' '
  259. + DCC.ipToLong(getListenIP(parser))
  260. + ' ' + send.getPort() + ' '
  261. + send.getFileSize() + ' ' + token);
  262. }
  263. }
  264. } else {
  265. final TransferContainer container1 = new TransferContainer(this, send,
  266. config, backBufferFactory, "Receive: " + nickname, nickname, null,
  267. eventBus);
  268. windowManager.addWindow(getContainer(), container1);
  269. if (resume) {
  270. parser.sendCTCP(nickname, "DCC", "RESUME "
  271. + send.getShortFileName() + ' '
  272. + send.getPort() + ' '
  273. + jc.getSelectedFile().length());
  274. } else {
  275. send.connect();
  276. }
  277. }
  278. }, "saveFileThread: " + send.getShortFileName()).start();
  279. }
  280. /**
  281. * Checks if the selected file exists and prompts the user as required.
  282. *
  283. * @param send DCC Transfer
  284. * @param jc File chooser
  285. * @param nickname Remote nickname
  286. * @param parser Parser
  287. * @param reverse Reverse DCC?
  288. * @param token DCC token
  289. *
  290. * @return true if the user wants to continue, false if they wish to abort
  291. */
  292. private boolean handleExists(final DCCTransfer send, final JFileChooser jc,
  293. final String nickname, final Parser parser, final boolean reverse,
  294. final String token) {
  295. if (jc.getSelectedFile().exists() && send.getFileSize() > -1
  296. && send.getFileSize() <= jc.getSelectedFile().length()) {
  297. if (config.getOptionBool(getDomain(), "receive.autoaccept")) {
  298. return false;
  299. } else {
  300. JOptionPane.showMessageDialog(
  301. mainWindow,
  302. "This file has already "
  303. + "been completed, or is longer than the file you are "
  304. + "receiving.\nPlease choose a different file.",
  305. "Problem with selected file",
  306. JOptionPane.ERROR_MESSAGE);
  307. saveFile(nickname, send, parser, reverse, token);
  308. return false;
  309. }
  310. }
  311. return true;
  312. }
  313. /**
  314. * Prompts the user to resume a transfer if required.
  315. *
  316. * @param jc File chooser
  317. *
  318. * @return true if the user wants to continue the transfer false otherwise
  319. */
  320. private boolean handleResume(final JFileChooser jc) {
  321. if (jc.getSelectedFile().exists()) {
  322. if (config.getOptionBool(getDomain(), "receive.autoaccept")) {
  323. return true;
  324. } else {
  325. final int result = JOptionPane.showConfirmDialog(
  326. mainWindow, "This file exists already"
  327. + ", do you want to resume an exisiting download?",
  328. "Resume Download?", JOptionPane.YES_NO_OPTION);
  329. return result == JOptionPane.YES_OPTION;
  330. }
  331. }
  332. return false;
  333. }
  334. /**
  335. * Sets up and display a file chooser.
  336. *
  337. * @param send DCCTransfer object sending the file
  338. * @param jc File chooser
  339. *
  340. * @return the return state of the file chooser on popdown:
  341. * <ul>
  342. * <li>JFileChooser.CANCEL_OPTION
  343. * <li>JFileChooser.APPROVE_OPTION
  344. * <li>JFileChooser.ERROR_OPTION if an error occurs or the dialog is dismissed
  345. * </ul>
  346. */
  347. private int showFileChooser(final DCCTransfer send, final JFileChooser jc) {
  348. jc.setDialogTitle("Save " + send.getShortFileName() + " As - DMDirc");
  349. jc.setFileSelectionMode(JFileChooser.FILES_ONLY);
  350. jc.setMultiSelectionEnabled(false);
  351. jc.setSelectedFile(new File(send.getFileName()));
  352. return jc.showSaveDialog(mainWindow);
  353. }
  354. /**
  355. * Make the given DCC start listening. This will either call dcc.listen() or
  356. * dcc.listen(startPort, endPort) depending on config.
  357. *
  358. * @param dcc DCC to start listening.
  359. *
  360. * @return True if Socket was opened.
  361. */
  362. protected boolean listen(final DCC dcc) {
  363. final boolean usePortRange = config.
  364. getOptionBool(getDomain(), "firewall.ports.usePortRange");
  365. try {
  366. if (usePortRange) {
  367. final int startPort = config.getOptionInt(getDomain(), "firewall.ports.startPort");
  368. final int endPort = config.getOptionInt(getDomain(), "firewall.ports.endPort");
  369. dcc.listen(startPort, endPort);
  370. } else {
  371. dcc.listen();
  372. }
  373. return true;
  374. } catch (IOException ioe) {
  375. return false;
  376. }
  377. }
  378. @Handler
  379. public void handleServerCtctpEvent(final ServerCtcpEvent event) {
  380. final boolean autoAccept = config.getOptionBool(getDomain(), "receive.autoaccept");
  381. final String[] ctcpData = event.getContent().split(" ");
  382. if (!"DCC".equalsIgnoreCase(event.getType())) {
  383. return;
  384. }
  385. switch (event.getType().toLowerCase()) {
  386. case "chat":
  387. if (ctcpData.length > 3) {
  388. handleChat(autoAccept, ctcpData, event.getUser(), event.getConnection());
  389. }
  390. break;
  391. case "send":
  392. if (ctcpData.length > 3) {
  393. handleSend(autoAccept, ctcpData, event.getUser(), event.getConnection());
  394. }
  395. break;
  396. case "resume":
  397. //Fallthrough
  398. case "accept":
  399. if (ctcpData.length > 2) {
  400. handleReceive(ctcpData, event.getUser(), event.getConnection());
  401. }
  402. break;
  403. default:
  404. break;
  405. }
  406. }
  407. /**
  408. * Handles a DCC chat request.
  409. *
  410. * @param dontAsk Don't ask any questions, assume yes.
  411. * @param ctcpData CTCP data bits
  412. * @param client Client receiving DCC
  413. * @param connection Connection DCC received on
  414. */
  415. private void handleChat(final boolean dontAsk, final String[] ctcpData,
  416. final User client, final Connection connection) {
  417. final String nickname = client.getNickname();
  418. if (dontAsk) {
  419. handleDCCChat(connection.getParser().get(), nickname, ctcpData);
  420. } else {
  421. eventBus.publish(new DccChatRequestEvent(connection, nickname));
  422. new StandardQuestionDialog(mainWindow, Dialog.ModalityType.APPLICATION_MODAL,
  423. "DCC Chat Request", "User " + nickname + " on " + connection.getAddress()
  424. + " would like to start a DCC Chat with you.\n\nDo you want to continue?",
  425. () -> handleDCCChat(connection.getParser().get(), nickname, ctcpData)).display();
  426. }
  427. }
  428. void handleDCCChat(final Parser parser, final String nickname, final String[] ctcpData) {
  429. final long ipAddress;
  430. final int port;
  431. try {
  432. ipAddress = Long.parseLong(ctcpData[2]);
  433. port = Integer.parseInt(ctcpData[3]);
  434. } catch (NumberFormatException nfe) {
  435. return;
  436. }
  437. final DCCChat chat = new DCCChat();
  438. chat.setAddress(ipAddress, port);
  439. final String myNickname = parser.getLocalClient().getNickname();
  440. final DCCFrameContainer container = new ChatContainer(
  441. getContainer(),
  442. chat,
  443. config,
  444. backBufferFactory,
  445. commandController,
  446. "Chat: " + nickname,
  447. myNickname,
  448. nickname,
  449. tabCompleterFactory,
  450. eventBus);
  451. windowManager.addWindow(getContainer(), container);
  452. container.getEventBus().publishAsync(new DccChatStartingEvent(
  453. container, nickname, chat.getHost(), chat.getPort()));
  454. chat.connect();
  455. }
  456. /**
  457. * Handles a DCC send request.
  458. *
  459. * @param dontAsk Don't ask any questions, assume yes.
  460. * @param ctcpData CTCP data bits
  461. * @param client Client that received the DCC
  462. * @param connection Connection the DCC was received on
  463. */
  464. private void handleSend(final boolean dontAsk, final String[] ctcpData, final User client,
  465. final Connection connection) {
  466. final String nickname = client.getNickname();
  467. String tmpFilename;
  468. // Clients tend to put files with spaces in the name in ""
  469. final StringBuilder filenameBits = new StringBuilder();
  470. int i;
  471. final boolean quoted = ctcpData[1].startsWith("\"");
  472. if (quoted) {
  473. for (i = 1; i < ctcpData.length; i++) {
  474. String bit = ctcpData[i];
  475. if (i == 1) {
  476. bit = bit.substring(1);
  477. }
  478. if (bit.endsWith("\"")) {
  479. filenameBits.append(' ').append(bit.substring(0, bit.length() - 1));
  480. break;
  481. } else {
  482. filenameBits.append(' ').append(bit);
  483. }
  484. }
  485. tmpFilename = filenameBits.toString().trim();
  486. } else {
  487. tmpFilename = ctcpData[1];
  488. i = 1;
  489. }
  490. // Try to remove path names if sent.
  491. // Change file separatorChar from other OSs first
  492. if (File.separatorChar == '/') {
  493. tmpFilename = tmpFilename.replace('\\', File.separatorChar);
  494. } else {
  495. tmpFilename = tmpFilename.replace('/', File.separatorChar);
  496. }
  497. // Then get just the name of the file.
  498. final String filename = new File(tmpFilename).getName();
  499. final String ip = ctcpData[++i];
  500. final String port = ctcpData[++i];
  501. long size;
  502. if (ctcpData.length + 1 > i) {
  503. try {
  504. size = Integer.parseInt(ctcpData[++i]);
  505. } catch (NumberFormatException nfe) {
  506. size = -1;
  507. }
  508. } else {
  509. size = -1;
  510. }
  511. final String token = ctcpData.length - 1 > i
  512. && !"T".equals(ctcpData[i + 1]) ? ctcpData[++i] : "";
  513. // Ignore incorrect ports, or non-numeric IP/Port
  514. final long ipLong;
  515. final int portInt;
  516. try {
  517. portInt = Integer.parseInt(port);
  518. if (portInt > 65535 || portInt < 0) {
  519. return;
  520. }
  521. ipLong = Long.parseLong(ip);
  522. } catch (NumberFormatException nfe) {
  523. return;
  524. }
  525. if (DCCTransfer.findByToken(token) == null && !dontAsk &&
  526. (token.isEmpty() || "0".equals(port))) {
  527. // Make sure this is not a reverse DCC Send that we no longer care about.
  528. eventBus.publish(new DccSendRequestEvent(connection, nickname, filename));
  529. final long passedSize = size;
  530. new StandardQuestionDialog(mainWindow, Dialog.ModalityType.APPLICATION_MODAL,
  531. "DCC Send Request", "User " + nickname + " on " + connection.getAddress()
  532. + " would like to send you a file over DCC.\n\nFile: "
  533. + filename + "\n\nDo you want to continue?",
  534. () -> handleDCCSend(token, ipLong, portInt, filename, passedSize, nickname,
  535. connection.getParser().get())).display();
  536. }
  537. }
  538. void handleDCCSend(final String token, final long ip, final int port, final String filename,
  539. final long size, final String nickname, final Parser parser) {
  540. DCCTransfer send = DCCTransfer.findByToken(token);
  541. final boolean newSend = send == null;
  542. if (newSend) {
  543. send = new DCCTransfer(config.getOptionInt(getDomain(), "send.blocksize"));
  544. send.setTurbo(config.getOptionBool(getDomain(), "send.forceturbo"));
  545. } else {
  546. return;
  547. }
  548. send.setAddress(ip, port);
  549. send.setFileName(filename);
  550. send.setFileSize(size);
  551. saveFile(nickname, send, parser, port == 0, token);
  552. }
  553. /**
  554. * Handles a DCC chat request.
  555. *
  556. * @param ctcpData CTCP data bits
  557. * @param client Client receiving the DCC
  558. * @param connection Connection the DCC was received on
  559. */
  560. private void handleReceive(final String[] ctcpData, final User client,
  561. final Connection connection) {
  562. final String filename;
  563. // Clients tend to put files with spaces in the name in ""
  564. final StringBuilder filenameBits = new StringBuilder();
  565. int i;
  566. final boolean quoted = ctcpData[1].startsWith("\"");
  567. if (quoted) {
  568. for (i = 1; i < ctcpData.length; i++) {
  569. String bit = ctcpData[i];
  570. if (i == 1) {
  571. bit = bit.substring(1);
  572. }
  573. if (bit.endsWith("\"")) {
  574. filenameBits.append(' ')
  575. .append(bit.substring(0, bit.length() - 1));
  576. break;
  577. } else {
  578. filenameBits.append(' ').append(bit);
  579. }
  580. }
  581. filename = filenameBits.toString().trim();
  582. } else {
  583. filename = ctcpData[1];
  584. i = 1;
  585. }
  586. final int port;
  587. final int position;
  588. try {
  589. port = Integer.parseInt(ctcpData[++i]);
  590. position = Integer.parseInt(ctcpData[++i]);
  591. } catch (NumberFormatException nfe) {
  592. return;
  593. }
  594. final String token = ctcpData.length - 1 > i ? ' '
  595. + ctcpData[++i] : "";
  596. // Now look for a dcc that matches.
  597. for (DCCTransfer send : DCCTransfer.getTransfers()) {
  598. if (send.getPort() == port && new File(send.getFileName())
  599. .getName().equalsIgnoreCase(filename)) {
  600. if (!token.isEmpty() && !send.getToken().isEmpty() &&
  601. !token.equals(send.getToken())) {
  602. continue;
  603. }
  604. final Parser parser = connection.getParser().get();
  605. final String nick = client.getNickname();
  606. if ("resume".equalsIgnoreCase(ctcpData[0])) {
  607. parser.sendCTCP(nick, "DCC", "ACCEPT " + (quoted ? '"'
  608. + filename + '"' : filename) + ' ' + port + ' '
  609. + send.setFileStart(position) + token);
  610. } else {
  611. send.setFileStart(position);
  612. if (port == 0) {
  613. // Reverse dcc
  614. if (listen(send)) {
  615. if (send.getToken().isEmpty()) {
  616. parser.sendCTCP(nick, "DCC", "SEND "
  617. + (quoted ? '"' + filename
  618. + '"' : filename) + ' '
  619. + DCC.ipToLong(send.getHost())
  620. + ' ' + send.getPort()
  621. + ' ' + send.getFileSize());
  622. } else {
  623. parser.sendCTCP(nick, "DCC", "SEND "
  624. + (quoted ? '"' + filename
  625. + '"' : filename)
  626. + ' ' + DCC.ipToLong(send.getHost())
  627. + ' ' + send.getPort()
  628. + ' ' + send.getFileSize() + ' '
  629. + send.getToken());
  630. }
  631. }
  632. } else {
  633. send.connect();
  634. }
  635. }
  636. }
  637. }
  638. }
  639. /**
  640. * Retrieves the container for the placeholder.
  641. *
  642. * @since 0.6.4
  643. * @return This plugin's placeholder container
  644. */
  645. public synchronized PlaceholderContainer getContainer() {
  646. if (container == null) {
  647. createContainer();
  648. }
  649. return container;
  650. }
  651. /**
  652. * Removes the cached container.
  653. *
  654. * @since 0.6.4
  655. */
  656. public synchronized void removeContainer() {
  657. container = null;
  658. }
  659. /**
  660. * Create the container window.
  661. */
  662. protected void createContainer() {
  663. container = new PlaceholderContainer(this, config, backBufferFactory, mainWindow, eventBus);
  664. windowManager.addWindow(container);
  665. }
  666. /**
  667. * Called when the plugin is loaded.
  668. */
  669. public void onLoad() {
  670. final File dir = new File(config.getOption(getDomain(),
  671. "receive.savelocation"));
  672. if (dir.exists()) {
  673. if (!dir.isDirectory()) {
  674. LOG.info(USER_ERROR, "Unable to create download dir (file exists instead)",
  675. new IllegalArgumentException("Directory is really a file"));
  676. }
  677. } else {
  678. try {
  679. dir.mkdirs();
  680. dir.createNewFile();
  681. } catch (IOException ex) {
  682. LOG.info(USER_ERROR, "Unable to create download dir",
  683. new IllegalArgumentException("Unable to create download dir"));
  684. }
  685. }
  686. eventBus.subscribe(this);
  687. }
  688. /**
  689. * Called when this plugin is Unloaded.
  690. */
  691. public synchronized void onUnload() {
  692. eventBus.unsubscribe(this);
  693. if (container != null) {
  694. container.close();
  695. }
  696. }
  697. /**
  698. * Get the IP Address we should send as our listening IP.
  699. *
  700. * @param parser Parser the IRC Parser where this dcc is initiated
  701. *
  702. * @return The IP Address we should send as our listening IP.
  703. */
  704. public String getListenIP(final Parser parser) {
  705. final String configIP = config.getOption(getDomain(), "firewall.ip");
  706. if (!configIP.isEmpty()) {
  707. try {
  708. return InetAddress.getByName(configIP).getHostAddress();
  709. } catch (UnknownHostException ex) { //NOPMD - handled below
  710. //Continue below
  711. }
  712. }
  713. if (parser != null) {
  714. final String myHost = parser.getLocalClient().getHostname();
  715. if (!myHost.isEmpty()) {
  716. try {
  717. return InetAddress.getByName(myHost).getHostAddress();
  718. } catch (UnknownHostException e) { //NOPMD - handled below
  719. //Continue below
  720. }
  721. }
  722. }
  723. try {
  724. return InetAddress.getLocalHost().getHostAddress();
  725. } catch (UnknownHostException e) {
  726. // This is almost certainly not what we want, but we can't work out
  727. // the right one.
  728. return "127.0.0.1"; //NOPMD
  729. }
  730. }
  731. private static class ComponentFrameWindowProvider implements SwingWindowFactory.WindowProvider {
  732. private final String component;
  733. private final ComponentFrameFactory componentFrameFactory;
  734. private final CommandParser commandParser;
  735. private final Supplier<? extends JComponent> componentSupplier;
  736. ComponentFrameWindowProvider(final String component,
  737. final ComponentFrameFactory componentFrameFactory,
  738. final CommandParser commandParser,
  739. final Supplier<? extends JComponent> componentSupplier) {
  740. this.component = component;
  741. this.componentFrameFactory = componentFrameFactory;
  742. this.commandParser = commandParser;
  743. this.componentSupplier = componentSupplier;
  744. }
  745. @Override
  746. public TextFrame getWindow(final WindowModel container) {
  747. return componentFrameFactory.getComponentFrame(container, commandParser,
  748. Collections.singletonList(componentSupplier));
  749. }
  750. @Override
  751. public Set<String> getComponents() {
  752. return Sets.newHashSet(component);
  753. }
  754. }
  755. }