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.

IRCParser.java 74KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066
  1. /*
  2. * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
  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.parser.irc;
  23. import com.dmdirc.parser.irc.outputqueue.OutputQueue;
  24. import com.dmdirc.parser.common.MyInfo;
  25. import com.dmdirc.parser.common.IgnoreList;
  26. import com.dmdirc.parser.common.ParserError;
  27. import com.dmdirc.parser.interfaces.SecureParser;
  28. import com.dmdirc.parser.interfaces.callbacks.ConnectErrorListener;
  29. import com.dmdirc.parser.interfaces.callbacks.DataInListener;
  30. import com.dmdirc.parser.interfaces.callbacks.DataOutListener;
  31. import com.dmdirc.parser.interfaces.callbacks.DebugInfoListener;
  32. import com.dmdirc.parser.interfaces.callbacks.ErrorInfoListener;
  33. import com.dmdirc.parser.interfaces.callbacks.PingFailureListener;
  34. import com.dmdirc.parser.interfaces.callbacks.PingSentListener;
  35. import com.dmdirc.parser.interfaces.callbacks.PingSuccessListener;
  36. import com.dmdirc.parser.interfaces.callbacks.Post005Listener;
  37. import com.dmdirc.parser.interfaces.callbacks.ServerErrorListener;
  38. import com.dmdirc.parser.interfaces.callbacks.SocketCloseListener;
  39. import com.dmdirc.parser.common.CallbackManager;
  40. import com.dmdirc.parser.common.QueuePriority;
  41. import java.io.BufferedReader;
  42. import java.io.IOException;
  43. import java.io.InputStreamReader;
  44. import java.net.InetAddress;
  45. import java.net.InetSocketAddress;
  46. import java.net.Proxy;
  47. import java.net.Socket;
  48. import java.net.UnknownHostException;
  49. import java.security.KeyManagementException;
  50. import java.security.NoSuchAlgorithmException;
  51. import java.security.cert.X509Certificate;
  52. import java.util.Arrays;
  53. import java.util.Collection;
  54. import java.util.Hashtable;
  55. import java.util.LinkedList;
  56. import java.util.List;
  57. import java.util.Map;
  58. import java.util.Timer;
  59. import java.util.Queue;
  60. import java.util.concurrent.Semaphore;
  61. import java.util.concurrent.atomic.AtomicBoolean;
  62. import javax.net.ssl.KeyManager;
  63. import javax.net.ssl.SSLContext;
  64. import javax.net.ssl.SSLSocketFactory;
  65. import javax.net.ssl.TrustManager;
  66. import javax.net.ssl.X509TrustManager;
  67. /**
  68. * IRC Parser.
  69. *
  70. * @author Shane Mc Cormack
  71. */
  72. public class IRCParser implements SecureParser, Runnable {
  73. /** Max length an outgoing line should be (NOT including \r\n). */
  74. public static final int MAX_LINELENGTH = 510;
  75. /** General Debug Information. */
  76. public static final int DEBUG_INFO = 1;
  77. /** Socket Debug Information. */
  78. public static final int DEBUG_SOCKET = 2;
  79. /** Processing Manager Debug Information. */
  80. public static final int DEBUG_PROCESSOR = 4;
  81. /** List Mode Queue Debug Information. */
  82. public static final int DEBUG_LMQ = 8;
  83. // public static final int DEBUG_SOMETHING = 16; //Next thingy
  84. /** Attempt to update user host all the time, not just on Who/Add/NickChange. */
  85. static final boolean ALWAYS_UPDATECLIENT = true;
  86. /** Byte used to show that a non-boolean mode is a list (b). */
  87. static final byte MODE_LIST = 1;
  88. /** Byte used to show that a non-boolean mode is not a list, and requires a parameter to set (lk). */
  89. static final byte MODE_SET = 2;
  90. /** Byte used to show that a non-boolean mode is not a list, and requires a parameter to unset (k). */
  91. static final byte MODE_UNSET = 4;
  92. /**
  93. * This is what the user wants settings to be.
  94. * Nickname here is *not* always accurate.<br><br>
  95. * ClientInfo variable tParser.getMyself() should be used for accurate info.
  96. */
  97. public MyInfo me = new MyInfo();
  98. /** Server Info requested by user. */
  99. public ServerInfo server = new ServerInfo();
  100. /** Should PINGs be sent to the server to check if its alive? */
  101. private boolean checkServerPing = true;
  102. /** Timer for server ping. */
  103. private Timer pingTimer = null;
  104. /** Semaphore for access to pingTimer. */
  105. private Semaphore pingTimerSem = new Semaphore(1);
  106. /** Length of time to wait between ping stuff. */
  107. private long pingTimerLength = 10000;
  108. /** Is a ping needed? */
  109. private volatile AtomicBoolean pingNeeded = new AtomicBoolean(false);
  110. /** Time last ping was sent at. */
  111. private long pingTime;
  112. /** Current Server Lag. */
  113. private long serverLag;
  114. /** Last value sent as a ping argument. */
  115. private String lastPingValue = "";
  116. /**
  117. * Count down to next ping.
  118. * The timer fires every 10 seconds, this value is decreased every time the
  119. * timer fires.<br>
  120. * Once it reaches 0, we send a ping, and reset it to 6, this means we ping
  121. * the server every minute.
  122. *
  123. * @see setPingCountDownLength
  124. */
  125. private int pingCountDown;
  126. /**
  127. * Amount of times the timer has to fire for inactivity before sending a ping.
  128. *
  129. * @see setPingCountDownLength
  130. */
  131. private int pingCountDownLength = 6;
  132. /** Name the server calls itself. */
  133. String serverName;
  134. /** Network name. This is "" if no network name is provided */
  135. String networkName;
  136. /** This is what we think the nickname should be. */
  137. String thinkNickname;
  138. /** When using inbuilt pre-001 NickInUse handler, have we tried our AltNick. */
  139. boolean triedAlt;
  140. /** Have we recieved the 001. */
  141. boolean got001;
  142. /** Have we fired post005? */
  143. boolean post005;
  144. /** Has the thread started execution yet, (Prevents run() being called multiple times). */
  145. boolean hasBegan;
  146. /** Hashtable storing known prefix modes (ohv). */
  147. final Map<Character, Long> prefixModes = new Hashtable<Character, Long>();
  148. /**
  149. * Hashtable maping known prefix modes (ohv) to prefixes (@%+) - Both ways.
  150. * Prefix map contains 2 pairs for each mode. (eg @ => o and o => @)
  151. */
  152. final Map<Character, Character> prefixMap = new Hashtable<Character, Character>();
  153. /** Integer representing the next avaliable integer value of a prefix mode. */
  154. long nextKeyPrefix = 1;
  155. /** Hashtable storing known user modes (owxis etc). */
  156. final Map<Character, Long> userModes = new Hashtable<Character, Long>();
  157. /** Integer representing the next avaliable integer value of a User mode. */
  158. long nNextKeyUser = 1;
  159. /**
  160. * Hashtable storing known boolean chan modes (cntmi etc).
  161. * Valid Boolean Modes are stored as Hashtable.pub('m',1); where 'm' is the mode and 1 is a numeric value.<br><br>
  162. * Numeric values are powers of 2. This allows up to 32 modes at present (expandable to 64)<br><br>
  163. * ChannelInfo/ChannelClientInfo etc provide methods to view the modes in a human way.<br><br>
  164. * <br>
  165. * Channel modes discovered but not listed in 005 are stored as boolean modes automatically (and a ERROR_WARNING Error is called)
  166. */
  167. final Map<Character, Long> chanModesBool = new Hashtable<Character, Long>();
  168. /** Integer representing the next avaliable integer value of a Boolean mode. */
  169. long nextKeyCMBool = 1;
  170. /**
  171. * Hashtable storing known non-boolean chan modes (klbeI etc).
  172. * Non Boolean Modes (for Channels) are stored together in this hashtable, the value param
  173. * is used to show the type of variable. (List (1), Param just for set (2), Param for Set and Unset (2+4=6))<br><br>
  174. * <br>
  175. * see MODE_LIST<br>
  176. * see MODE_SET<br>
  177. * see MODE_UNSET<br>
  178. */
  179. final Map<Character, Byte> chanModesOther = new Hashtable<Character, Byte>();
  180. /** The last line of input recieved from the server */
  181. String lastLine = "";
  182. /** Should the lastline (where given) be appended to the "data" part of any onErrorInfo call? */
  183. boolean addLastLine = false;
  184. /**
  185. * Channel Prefixes (ie # + etc).
  186. * The "value" for these is always true.
  187. */
  188. final Map<Character, Boolean> chanPrefix = new Hashtable<Character, Boolean>();
  189. /** Hashtable storing all known clients based on nickname (in lowercase). */
  190. private final Map<String, IRCClientInfo> clientList = new Hashtable<String, IRCClientInfo>();
  191. /** Hashtable storing all known channels based on chanel name (inc prefix - in lowercase). */
  192. private final Map<String, IRCChannelInfo> channelList = new Hashtable<String, IRCChannelInfo>();
  193. /** Reference to the ClientInfo object that references ourself. */
  194. private IRCClientInfo myself = new IRCClientInfo(this, "myself").setFake(true);
  195. /** Hashtable storing all information gathered from 005. */
  196. final Map<String, String> h005Info = new Hashtable<String, String>();
  197. /** Ignore List. */
  198. IgnoreList myIgnoreList = new IgnoreList();
  199. /** Reference to the callback Manager. */
  200. CallbackManager<IRCParser> myCallbackManager = new IRCCallbackManager(this);
  201. /** Reference to the Processing Manager. */
  202. ProcessingManager myProcessingManager = new ProcessingManager(this);
  203. /** Should we automatically disconnect on fatal errors?. */
  204. private boolean disconnectOnFatal = true;
  205. /** Current Socket State. */
  206. protected SocketState currentSocketState = SocketState.NULL;
  207. /** This is the socket used for reading from/writing to the IRC server. */
  208. private Socket socket;
  209. /** Used for writing to the server. */
  210. private OutputQueue out;
  211. /** Used for reading from the server. */
  212. private BufferedReader in;
  213. /** This is the default TrustManager for SSL Sockets, it trusts all ssl certs. */
  214. private final TrustManager[] trustAllCerts = {
  215. new X509TrustManager() {
  216. @Override
  217. public X509Certificate[] getAcceptedIssuers() { return null; }
  218. @Override
  219. public void checkClientTrusted(final X509Certificate[] certs, final String authType) { }
  220. @Override
  221. public void checkServerTrusted(final X509Certificate[] certs, final String authType) { }
  222. },
  223. };
  224. /** Should fake (channel)clients be created for callbacks where they do not exist? */
  225. boolean createFake = true;
  226. /** Should channels automatically request list modes? */
  227. boolean autoListMode = true;
  228. /** Should part/quit/kick callbacks be fired before removing the user internally? */
  229. boolean removeAfterCallback = true;
  230. /** This is the TrustManager used for SSL Sockets. */
  231. private TrustManager[] myTrustManager = trustAllCerts;
  232. /** The KeyManagers used for client certificates for SSL sockets. */
  233. private KeyManager[] myKeyManagers;
  234. /** This is the IP we want to bind to. */
  235. private String bindIP = "";
  236. /** This is list containing 001 - 005 inclusive. */
  237. private List<String> serverInformationLines = new LinkedList<String>();
  238. /**
  239. * Default constructor, ServerInfo and MyInfo need to be added separately (using IRC.me and IRC.server).
  240. */
  241. public IRCParser() { this(null, null); }
  242. /**
  243. * Constructor with ServerInfo, MyInfo needs to be added separately (using IRC.me).
  244. *
  245. * @param serverDetails Server information.
  246. */
  247. public IRCParser(final ServerInfo serverDetails) { this(null, serverDetails); }
  248. /**
  249. * Constructor with MyInfo, ServerInfo needs to be added separately (using IRC.server).
  250. *
  251. * @param myDetails Client information.
  252. */
  253. public IRCParser(final MyInfo myDetails) { this(myDetails, null); }
  254. /**
  255. * Constructor with ServerInfo and MyInfo.
  256. *
  257. * @param serverDetails Server information.
  258. * @param myDetails Client information.
  259. */
  260. public IRCParser(final MyInfo myDetails, final ServerInfo serverDetails) {
  261. if (myDetails != null) { this.me = myDetails; }
  262. if (serverDetails != null) { this.server = serverDetails; }
  263. resetState();
  264. }
  265. /**
  266. * Get the current Value of bindIP.
  267. *
  268. * @return Value of bindIP ("" for default IP)
  269. */
  270. public String getBindIP() { return bindIP; }
  271. /** {@inheritDoc} */
  272. @Override
  273. public void setBindIP(final String ip) { bindIP = ip; }
  274. /**
  275. * Get the current Value of createFake.
  276. *
  277. * @return Value of createFake (true if fake clients will be added for callbacks, else false)
  278. */
  279. public boolean getCreateFake() { return createFake; }
  280. /**
  281. * Set the current Value of createFake.
  282. *
  283. * @param newValue New value to set createFake
  284. */
  285. public void setCreateFake(final boolean newValue) { createFake = newValue; }
  286. /**
  287. * Get the current Value of autoListMode.
  288. *
  289. * @return Value of autoListMode (true if channels automatically ask for list modes on join, else false)
  290. */
  291. public boolean getAutoListMode() { return autoListMode; }
  292. /**
  293. * Set the current Value of autoListMode.
  294. *
  295. * @param newValue New value to set autoListMode
  296. */
  297. public void setAutoListMode(final boolean newValue) { autoListMode = newValue; }
  298. /**
  299. * Get the current Value of removeAfterCallback.
  300. *
  301. * @return Value of removeAfterCallback (true if kick/part/quit callbacks are fired before internal removal)
  302. */
  303. public boolean getRemoveAfterCallback() { return removeAfterCallback; }
  304. /**
  305. * Get the current Value of removeAfterCallback.
  306. *
  307. * @param newValue New value to set removeAfterCallback
  308. */
  309. public void setRemoveAfterCallback(final boolean newValue) { removeAfterCallback = newValue; }
  310. /**
  311. * Get the current Value of addLastLine.
  312. *
  313. * @return Value of addLastLine (true if lastLine info will be automatically
  314. * added to the errorInfo data line). This should be true if lastLine
  315. * isn't handled any other way.
  316. */
  317. public boolean getAddLastLine() { return addLastLine; }
  318. /**
  319. * Get the current Value of addLastLine.
  320. * This returns "this" and thus can be used in the construction line.
  321. *
  322. * @param newValue New value to set addLastLine
  323. */
  324. public void setAddLastLine(final boolean newValue) { addLastLine = newValue; }
  325. /**
  326. * Get the current socket State.
  327. *
  328. * @since 0.6.3m1
  329. * @return Current {@link SocketState}
  330. */
  331. public SocketState getSocketState() { return currentSocketState; }
  332. /**
  333. * Get a reference to the Processing Manager.
  334. *
  335. * @return Reference to the CallbackManager
  336. */
  337. public ProcessingManager getProcessingManager() { return myProcessingManager; }
  338. /** {@inheritDoc} */
  339. @Override
  340. public CallbackManager<IRCParser> getCallbackManager() { return myCallbackManager; }
  341. /**
  342. * Get a reference to the default TrustManager for SSL Sockets.
  343. *
  344. * @return a reference to trustAllCerts
  345. */
  346. public TrustManager[] getDefaultTrustManager() { return trustAllCerts; }
  347. /**
  348. * Get a reference to the current TrustManager for SSL Sockets.
  349. *
  350. * @return a reference to myTrustManager;
  351. */
  352. public TrustManager[] getTrustManager() { return myTrustManager; }
  353. /** {@inheritDoc} */
  354. @Override
  355. public void setTrustManagers(final TrustManager[] managers) { myTrustManager = managers; }
  356. /** {@inheritDoc} */
  357. @Override
  358. public void setKeyManagers(final KeyManager[] managers) { myKeyManagers = managers; }
  359. /** {@inheritDoc} */
  360. @Override
  361. public IgnoreList getIgnoreList() { return myIgnoreList; }
  362. /** {@inheritDoc} */
  363. @Override
  364. public void setIgnoreList(final IgnoreList ignoreList) { myIgnoreList = ignoreList; }
  365. //---------------------------------------------------------------------------
  366. // Start Callbacks
  367. //---------------------------------------------------------------------------
  368. /**
  369. * Callback to all objects implementing the ServerError Callback.
  370. *
  371. * @see com.dmdirc.parser.irc.callbacks.interfaces.IServerError
  372. * @param message The error message
  373. * @return true if a method was called, false otherwise
  374. */
  375. protected boolean callServerError(final String message) {
  376. return myCallbackManager.getCallbackType(ServerErrorListener.class).call(message);
  377. }
  378. /**
  379. * Callback to all objects implementing the DataIn Callback.
  380. *
  381. * @see com.dmdirc.parser.irc.callbacks.interfaces.IDataIn
  382. * @param data Incomming Line.
  383. * @return true if a method was called, false otherwise
  384. */
  385. protected boolean callDataIn(final String data) {
  386. return myCallbackManager.getCallbackType(DataInListener.class).call(data);
  387. }
  388. /**
  389. * Callback to all objects implementing the DataOut Callback.
  390. *
  391. * @param data Outgoing Data
  392. * @param fromParser True if parser sent the data, false if sent using .sendLine
  393. * @return true if a method was called, false otherwise
  394. * @see com.dmdirc.parser.irc.callbacks.interfaces.IDataOut
  395. */
  396. protected boolean callDataOut(final String data, final boolean fromParser) {
  397. return myCallbackManager.getCallbackType(DataOutListener.class).call(data, fromParser);
  398. }
  399. /**
  400. * Callback to all objects implementing the DebugInfo Callback.
  401. *
  402. * @see com.dmdirc.parser.irc.callbacks.interfaces.IDebugInfo
  403. * @param level Debugging Level (DEBUG_INFO, DEBUG_SOCKET etc)
  404. * @param data Debugging Information as a format string
  405. * @param args Formatting String Options
  406. * @return true if a method was called, false otherwise
  407. */
  408. protected boolean callDebugInfo(final int level, final String data, final Object... args) {
  409. return callDebugInfo(level, String.format(data, args));
  410. }
  411. /**
  412. * Callback to all objects implementing the DebugInfo Callback.
  413. *
  414. * @see com.dmdirc.parser.irc.callbacks.interfaces.IDebugInfo
  415. * @param level Debugging Level (DEBUG_INFO, DEBUG_SOCKET etc)
  416. * @param data Debugging Information
  417. * @return true if a method was called, false otherwise
  418. */
  419. protected boolean callDebugInfo(final int level, final String data) {
  420. return myCallbackManager.getCallbackType(DebugInfoListener.class).call(level, data);
  421. }
  422. /**
  423. * Callback to all objects implementing the IErrorInfo Interface.
  424. *
  425. * @see com.dmdirc.parser.irc.callbacks.interfaces.IErrorInfo
  426. * @param errorInfo ParserError object representing the error.
  427. * @return true if a method was called, false otherwise
  428. */
  429. protected boolean callErrorInfo(final ParserError errorInfo) {
  430. return myCallbackManager.getCallbackType(ErrorInfoListener.class).call(errorInfo);
  431. }
  432. /**
  433. * Callback to all objects implementing the IConnectError Interface.
  434. *
  435. * @see com.dmdirc.parser.irc.callbacks.interfaces.IConnectError
  436. * @param errorInfo ParserError object representing the error.
  437. * @return true if a method was called, false otherwise
  438. */
  439. protected boolean callConnectError(final ParserError errorInfo) {
  440. return myCallbackManager.getCallbackType(ConnectErrorListener.class).call(errorInfo);
  441. }
  442. /**
  443. * Callback to all objects implementing the SocketClosed Callback.
  444. *
  445. * @see com.dmdirc.parser.irc.callbacks.interfaces.ISocketClosed
  446. * @return true if a method was called, false otherwise
  447. */
  448. protected boolean callSocketClosed() {
  449. return myCallbackManager.getCallbackType(SocketCloseListener.class).call();
  450. }
  451. /**
  452. * Callback to all objects implementing the PingFailed Callback.
  453. *
  454. * @see com.dmdirc.parser.irc.callbacks.interfaces.IPingFailed
  455. * @return true if a method was called, false otherwise
  456. */
  457. protected boolean callPingFailed() {
  458. return myCallbackManager.getCallbackType(PingFailureListener.class).call();
  459. }
  460. /**
  461. * Callback to all objects implementing the PingSent Callback.
  462. *
  463. * @see com.dmdirc.parser.irc.callbacks.interfaces.IPingSent
  464. * @return true if a method was called, false otherwise
  465. */
  466. protected boolean callPingSent() {
  467. return myCallbackManager.getCallbackType(PingSentListener.class).call();
  468. }
  469. /**
  470. * Callback to all objects implementing the PingSuccess Callback.
  471. *
  472. * @see com.dmdirc.parser.irc.callbacks.interfaces.IPingSuccess
  473. * @return true if a method was called, false otherwise
  474. */
  475. protected boolean callPingSuccess() {
  476. return myCallbackManager.getCallbackType(PingSuccessListener.class).call();
  477. }
  478. /**
  479. * Callback to all objects implementing the Post005 Callback.
  480. *
  481. * @return true if any callbacks were called.
  482. * @see IPost005
  483. */
  484. protected synchronized boolean callPost005() {
  485. if (post005) { return false; }
  486. post005 = true;
  487. if (!h005Info.containsKey("CHANTYPES")) { parseChanPrefix(); }
  488. if (!h005Info.containsKey("PREFIX")) { parsePrefixModes(); }
  489. if (!h005Info.containsKey("USERMODES")) { parseUserModes(); }
  490. if (!h005Info.containsKey("CHANMODES")) { parseChanModes(); }
  491. return getCallbackManager().getCallbackType(Post005Listener.class).call();
  492. }
  493. //---------------------------------------------------------------------------
  494. // End Callbacks
  495. //---------------------------------------------------------------------------
  496. /** Reset internal state (use before connect). */
  497. private void resetState() {
  498. // Reset General State info
  499. triedAlt = false;
  500. got001 = false;
  501. post005 = false;
  502. // Clear the hash tables
  503. channelList.clear();
  504. clientList.clear();
  505. h005Info.clear();
  506. prefixModes.clear();
  507. prefixMap.clear();
  508. chanModesOther.clear();
  509. chanModesBool.clear();
  510. userModes.clear();
  511. chanPrefix.clear();
  512. // Clear output queue.
  513. if (out != null) { out.clearQueue(); }
  514. // Reset the mode indexes
  515. nextKeyPrefix = 1;
  516. nextKeyCMBool = 1;
  517. nNextKeyUser = 1;
  518. serverName = "";
  519. networkName = "";
  520. lastLine = "";
  521. myself = new IRCClientInfo(this, "myself").setFake(true);
  522. synchronized (serverInformationLines) {
  523. serverInformationLines.clear();
  524. }
  525. stopPingTimer();
  526. currentSocketState = SocketState.CLOSED;
  527. // Char Mapping
  528. updateCharArrays((byte)4);
  529. }
  530. /**
  531. * Called after other error callbacks.
  532. * CallbackOnErrorInfo automatically calls this *AFTER* any registered callbacks
  533. * for it are called.
  534. *
  535. * @param errorInfo ParserError object representing the error.
  536. * @param called True/False depending on the the success of other callbacks.
  537. */
  538. public void onPostErrorInfo(final ParserError errorInfo, final boolean called) {
  539. if (errorInfo.isFatal() && disconnectOnFatal) {
  540. disconnect("Fatal Parser Error");
  541. }
  542. }
  543. /**
  544. * Get the current Value of disconnectOnFatal.
  545. *
  546. * @return Value of disconnectOnFatal (true if the parser automatically disconnects on fatal errors, else false)
  547. */
  548. public boolean getDisconnectOnFatal() { return disconnectOnFatal; }
  549. /**
  550. * Set the current Value of disconnectOnFatal.
  551. *
  552. * @param newValue New value to set disconnectOnFatal
  553. */
  554. public void setDisconnectOnFatal(final boolean newValue) { disconnectOnFatal = newValue; }
  555. /**
  556. * Connect to IRC.
  557. *
  558. * @throws IOException if the socket can not be connected
  559. * @throws UnknownHostException if the hostname can not be resolved
  560. * @throws NoSuchAlgorithmException if SSL is not available
  561. * @throws KeyManagementException if the trustManager is invalid
  562. */
  563. private void connect() throws UnknownHostException, IOException, NoSuchAlgorithmException, KeyManagementException {
  564. resetState();
  565. callDebugInfo(DEBUG_SOCKET, "Connecting to " + server.getHost() + ":" + server.getPort());
  566. if (server.getPort() > 65535 || server.getPort() <= 0) {
  567. throw new IOException("Server port ("+server.getPort()+") is invalid.");
  568. }
  569. if (server.getUseSocks()) {
  570. callDebugInfo(DEBUG_SOCKET, "Using Proxy");
  571. if (bindIP != null && !bindIP.isEmpty()) {
  572. callDebugInfo(DEBUG_SOCKET, "IP Binding is not possible when using a proxy.");
  573. }
  574. if (server.getProxyPort() > 65535 || server.getProxyPort() <= 0) {
  575. throw new IOException("Proxy port ("+server.getProxyPort()+") is invalid.");
  576. }
  577. final Proxy.Type proxyType = Proxy.Type.SOCKS;
  578. socket = new Socket(new Proxy(proxyType, new InetSocketAddress(server.getProxyHost(), server.getProxyPort())));
  579. currentSocketState = SocketState.OPEN;
  580. if (server.getProxyUser() != null && !server.getProxyUser().isEmpty()) {
  581. IRCAuthenticator.getIRCAuthenticator().addAuthentication(server);
  582. }
  583. socket.connect(new InetSocketAddress(server.getHost(), server.getPort()));
  584. } else {
  585. callDebugInfo(DEBUG_SOCKET, "Not using Proxy");
  586. if (!server.getSSL()) {
  587. socket = new Socket();
  588. if (bindIP != null && !bindIP.isEmpty()) {
  589. callDebugInfo(DEBUG_SOCKET, "Binding to IP: "+bindIP);
  590. try {
  591. socket.bind(new InetSocketAddress(InetAddress.getByName(bindIP), 0));
  592. } catch (IOException e) {
  593. callDebugInfo(DEBUG_SOCKET, "Binding failed: "+e.getMessage());
  594. }
  595. }
  596. currentSocketState = SocketState.OPEN;
  597. socket.connect(new InetSocketAddress(server.getHost(), server.getPort()));
  598. }
  599. }
  600. if (server.getSSL()) {
  601. callDebugInfo(DEBUG_SOCKET, "Server is SSL.");
  602. if (myTrustManager == null) { myTrustManager = trustAllCerts; }
  603. final SSLContext sc = SSLContext.getInstance("SSL");
  604. sc.init(myKeyManagers, myTrustManager, new java.security.SecureRandom());
  605. final SSLSocketFactory socketFactory = sc.getSocketFactory();
  606. if (server.getUseSocks()) {
  607. socket = socketFactory.createSocket(socket, server.getHost(), server.getPort(), false);
  608. } else {
  609. if (bindIP == null || bindIP.isEmpty()) {
  610. socket = socketFactory.createSocket(server.getHost(), server.getPort());
  611. } else {
  612. callDebugInfo(DEBUG_SOCKET, "Binding to IP: "+bindIP);
  613. try {
  614. socket = socketFactory.createSocket(server.getHost(), server.getPort(), InetAddress.getByName(bindIP), 0);
  615. } catch (UnknownHostException e) {
  616. callDebugInfo(DEBUG_SOCKET, "Bind failed: "+e.getMessage());
  617. socket = socketFactory.createSocket(server.getHost(), server.getPort());
  618. }
  619. }
  620. }
  621. currentSocketState = SocketState.OPEN;
  622. }
  623. callDebugInfo(DEBUG_SOCKET, "\t-> Opening socket output stream PrintWriter");
  624. out = new OutputQueue(socket.getOutputStream());
  625. out.setQueueEnabled(true);
  626. callDebugInfo(DEBUG_SOCKET, "\t-> Opening socket input stream BufferedReader");
  627. in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  628. callDebugInfo(DEBUG_SOCKET, "\t-> Socket Opened");
  629. }
  630. /**
  631. * Send server connection strings (NICK/USER/PASS).
  632. */
  633. protected void sendConnectionStrings() {
  634. if (!server.getPassword().isEmpty()) {
  635. sendString("PASS " + server.getPassword());
  636. }
  637. sendString("NICK " + me.getNickname());
  638. thinkNickname = me.getNickname();
  639. String localhost;
  640. try {
  641. localhost = InetAddress.getLocalHost().getHostAddress();
  642. } catch (UnknownHostException uhe) {
  643. localhost = "*";
  644. }
  645. sendString("USER " + me.getUsername() + " "+localhost+" "+server.getHost()+" :" + me.getRealname());
  646. }
  647. /**
  648. * Handle an onConnect error.
  649. *
  650. * @param e Exception to handle
  651. */
  652. private void handleConnectException(final Exception e) {
  653. callDebugInfo(DEBUG_SOCKET, "Error Connecting (" + e.getMessage() + "), Aborted");
  654. final ParserError ei = new ParserError(ParserError.ERROR_ERROR, "Exception with server socket", getLastLine());
  655. ei.setException(e);
  656. callConnectError(ei);
  657. if (currentSocketState != SocketState.CLOSED) {
  658. currentSocketState = SocketState.CLOSED;
  659. callSocketClosed();
  660. }
  661. resetState();
  662. }
  663. /**
  664. * Begin execution.
  665. * Connect to server, and start parsing incomming lines
  666. */
  667. @Override
  668. public void run() {
  669. callDebugInfo(DEBUG_INFO, "Begin Thread Execution");
  670. if (hasBegan) { return; } else { hasBegan = true; }
  671. try {
  672. connect();
  673. } catch (UnknownHostException e) {
  674. handleConnectException(e);
  675. return;
  676. } catch (IOException e) {
  677. handleConnectException(e);
  678. return;
  679. } catch (NoSuchAlgorithmException e) {
  680. handleConnectException(e);
  681. return;
  682. } catch (KeyManagementException e) {
  683. handleConnectException(e);
  684. return;
  685. }
  686. callDebugInfo(DEBUG_SOCKET, "Socket Connected");
  687. sendConnectionStrings();
  688. while (true) {
  689. try {
  690. lastLine = in.readLine(); // Blocking :/
  691. if (lastLine == null) {
  692. if (currentSocketState != SocketState.CLOSED) {
  693. currentSocketState = SocketState.CLOSED;
  694. callSocketClosed();
  695. }
  696. resetState();
  697. break;
  698. } else if (currentSocketState != SocketState.CLOSING) {
  699. processLine(lastLine);
  700. }
  701. } catch (IOException e) {
  702. callDebugInfo(DEBUG_SOCKET, "Exception in main loop (" + e.getMessage() + "), Aborted");
  703. if (currentSocketState != SocketState.CLOSED) {
  704. currentSocketState = SocketState.CLOSED;
  705. callSocketClosed();
  706. }
  707. resetState();
  708. break;
  709. }
  710. }
  711. callDebugInfo(DEBUG_INFO, "End Thread Execution");
  712. }
  713. /** {@inheritDoc} */
  714. @Override
  715. public int getLocalPort() {
  716. if (currentSocketState == SocketState.OPEN) {
  717. return socket.getLocalPort();
  718. } else {
  719. return 0;
  720. }
  721. }
  722. /** Close socket on destroy. */
  723. @Override
  724. protected void finalize() throws Throwable {
  725. try { socket.close(); }
  726. catch (IOException e) {
  727. callDebugInfo(DEBUG_SOCKET, "Could not close socket");
  728. }
  729. super.finalize();
  730. }
  731. /**
  732. * Get the trailing parameter for a line.
  733. * The parameter is everything after the first occurance of " :" ot the last token in the line after a space.
  734. *
  735. * @param line Line to get parameter for
  736. * @return Parameter of the line
  737. */
  738. public static String getParam(final String line) {
  739. String[] params = null;
  740. params = line.split(" :", 2);
  741. return params[params.length - 1];
  742. }
  743. /**
  744. * Tokenise a line.
  745. * splits by " " up to the first " :" everything after this is a single token
  746. *
  747. * @param line Line to tokenise
  748. * @return Array of tokens
  749. */
  750. public static String[] tokeniseLine(final String line) {
  751. if (line == null) {
  752. return new String[]{"", }; // Return empty string[]
  753. }
  754. final int lastarg = line.indexOf(" :");
  755. String[] tokens;
  756. if (lastarg > -1) {
  757. final String[] temp = line.substring(0, lastarg).split(" ");
  758. tokens = new String[temp.length + 1];
  759. System.arraycopy(temp, 0, tokens, 0, temp.length);
  760. tokens[temp.length] = line.substring(lastarg + 2);
  761. } else {
  762. tokens = line.split(" ");
  763. }
  764. return tokens;
  765. }
  766. /** {@inheritDoc} */
  767. @Override
  768. public IRCClientInfo getClient(final String details) {
  769. final String sWho = getStringConverter().toLowerCase(IRCClientInfo.parseHost(details));
  770. if (clientList.containsKey(sWho)) { return clientList.get(sWho); }
  771. else { return new IRCClientInfo(this, details).setFake(true); }
  772. }
  773. public boolean isKnownClient(final String host) {
  774. final String sWho = getStringConverter().toLowerCase(IRCClientInfo.parseHost(host));
  775. return clientList.containsKey(sWho);
  776. }
  777. /** {@inheritDoc} */
  778. @Override
  779. public IRCChannelInfo getChannel(String channel) {
  780. synchronized (channelList) {
  781. channel = getStringConverter().toLowerCase(channel);
  782. if (channelList.containsKey(channel)) { return channelList.get(channel); } else { return null; }
  783. }
  784. }
  785. /** {@inheritDoc} */
  786. @Override
  787. public void sendRawMessage(final String message) { doSendString(message, QueuePriority.NORMAL, false); }
  788. /** {@inheritDoc} */
  789. @Override
  790. public void sendRawMessage(final String message, final QueuePriority priority) { doSendString(message, priority, false); }
  791. /**
  792. * Send a line to the server and add proper line ending.
  793. *
  794. * @param line Line to send (\r\n termination is added automatically)
  795. * @return True if line was sent, else false.
  796. */
  797. protected boolean sendString(final String line) { return doSendString(line, QueuePriority.NORMAL, true); }
  798. /**
  799. * Send a line to the server and add proper line ending.
  800. *
  801. * @param line Line to send (\r\n termination is added automatically)
  802. * @param priority Priority of this line.
  803. * @return True if line was sent, else false.
  804. */
  805. protected boolean sendString(final String line, final QueuePriority priority) { return doSendString(line, priority, true); }
  806. /**
  807. * Send a line to the server and add proper line ending.
  808. *
  809. * @param line Line to send (\r\n termination is added automatically)
  810. * @param priority Priority of this line.
  811. * @param fromParser is this line from the parser? (used for callDataOut)
  812. * @return True if line was sent, else false.
  813. */
  814. protected boolean doSendString(final String line, final QueuePriority priority, final boolean fromParser) {
  815. if (out == null || getSocketState() != SocketState.OPEN) { return false; }
  816. callDataOut(line, fromParser);
  817. out.sendLine(line, priority);
  818. final String[] newLine = tokeniseLine(line);
  819. if (newLine[0].equalsIgnoreCase("away") && newLine.length > 1) {
  820. myself.setAwayReason(newLine[newLine.length-1]);
  821. } else if (newLine[0].equalsIgnoreCase("mode") && newLine.length == 3) {
  822. // This makes sure we don't add the same item to the LMQ twice, even if its requested twice,
  823. // as the ircd will only reply once.
  824. final LinkedList<Character> foundModes = new LinkedList<Character>();
  825. final IRCChannelInfo channel = getChannel(newLine[1]);
  826. if (channel != null) {
  827. final Queue<Character> listModeQueue = channel.getListModeQueue();
  828. for (int i = 0; i < newLine[2].length() ; ++i) {
  829. final Character mode = newLine[2].charAt(i);
  830. callDebugInfo(DEBUG_LMQ, "Intercepted mode request for "+channel+" for mode "+mode);
  831. if (chanModesOther.containsKey(mode) && chanModesOther.get(mode) == MODE_LIST) {
  832. if (foundModes.contains(mode)) {
  833. callDebugInfo(DEBUG_LMQ, "Already added to LMQ");
  834. } else {
  835. listModeQueue.offer(mode);
  836. foundModes.offer(mode);
  837. callDebugInfo(DEBUG_LMQ, "Added to LMQ");
  838. }
  839. }
  840. }
  841. }
  842. }
  843. return true;
  844. }
  845. /** {@inheritDoc} */
  846. @Override
  847. public String getNetworkName() {
  848. return networkName;
  849. }
  850. /** {@inheritDoc} */
  851. @Override
  852. public String getServerName() {
  853. return serverName;
  854. }
  855. /** {@inheritDoc} */
  856. @Override
  857. public String getLastLine() {
  858. return lastLine;
  859. }
  860. /**
  861. * Get the list of lines the server gave from 001 - 005.
  862. *
  863. * @return List of lines sent by the server between 001 and 005 inclusive.
  864. */
  865. public List<String> getServerInformationLines() {
  866. synchronized (serverInformationLines) {
  867. return new LinkedList<String>(serverInformationLines);
  868. }
  869. }
  870. /**
  871. * Process a line and call relevent methods for handling.
  872. *
  873. * @param line IRC Line to process
  874. */
  875. protected void processLine(final String line) {
  876. callDataIn(line);
  877. final String[] token = tokeniseLine(line);
  878. int nParam;
  879. setPingNeeded(false);
  880. // pingCountDown = pingCountDownLength;
  881. if (token.length < 2) {
  882. return;
  883. }
  884. try {
  885. final String sParam = token[1];
  886. if (token[0].equalsIgnoreCase("PING") || token[1].equalsIgnoreCase("PING")) {
  887. sendString("PONG :" + sParam, QueuePriority.HIGH);
  888. } else if (token[0].equalsIgnoreCase("PONG") || token[1].equalsIgnoreCase("PONG")) {
  889. if (!lastPingValue.isEmpty() && lastPingValue.equals(token[token.length-1])) {
  890. lastPingValue = "";
  891. serverLag = System.currentTimeMillis() - pingTime;
  892. callPingSuccess();
  893. }
  894. } else if (token[0].equalsIgnoreCase("ERROR")) {
  895. final StringBuilder errorMessage = new StringBuilder();
  896. for (int i = 1; i < token.length; ++i) { errorMessage.append(token[i]); }
  897. callServerError(errorMessage.toString());
  898. } else {
  899. if (got001) {
  900. // Freenode sends a random notice in a stupid place, others might do aswell
  901. // These shouldn't cause post005 to be fired, so handle them here.
  902. if (token[0].equalsIgnoreCase("NOTICE") || (token.length > 2 && token[2].equalsIgnoreCase("NOTICE"))) {
  903. try { myProcessingManager.process("Notice Auth", token); } catch (ProcessorNotFoundException e) { }
  904. return;
  905. }
  906. if (!post005) {
  907. try { nParam = Integer.parseInt(token[1]); } catch (NumberFormatException e) { nParam = -1; }
  908. if (nParam < 0 || nParam > 5) {
  909. callPost005();
  910. } else {
  911. // Store 001 - 005 for informational purposes.
  912. synchronized (serverInformationLines) {
  913. serverInformationLines.add(line);
  914. }
  915. }
  916. }
  917. // After 001 we potentially care about everything!
  918. try { myProcessingManager.process(sParam, token); }
  919. catch (ProcessorNotFoundException e) { }
  920. } else {
  921. // Before 001 we don't care about much.
  922. try { nParam = Integer.parseInt(token[1]); } catch (NumberFormatException e) { nParam = -1; }
  923. switch (nParam) {
  924. case 1: // 001 - Welcome to IRC
  925. case 464: // Password Required
  926. case 433: // Nick In Use
  927. try { myProcessingManager.process(sParam, token); } catch (ProcessorNotFoundException e) { }
  928. break;
  929. default: // Unknown - Send to Notice Auth
  930. // Some networks send a CTCP during the auth process, handle it
  931. if (token.length > 3 && !token[3].isEmpty() && token[3].charAt(0) == (char)1 && token[3].charAt(token[3].length()-1) == (char)1) {
  932. try { myProcessingManager.process(sParam, token); } catch (ProcessorNotFoundException e) { }
  933. break;
  934. }
  935. // Some networks may send a NICK message if you nick change before 001
  936. // Eat it up so that it isn't treated as a notice auth.
  937. if (token[1].equalsIgnoreCase("NICK")) { break; }
  938. // Otherwise, send to Notice Auth
  939. try { myProcessingManager.process("Notice Auth", token); } catch (ProcessorNotFoundException e) { }
  940. break;
  941. }
  942. }
  943. }
  944. } catch (Exception e) {
  945. final ParserError ei = new ParserError(ParserError.ERROR_FATAL, "Fatal Exception in Parser.", getLastLine());
  946. ei.setException(e);
  947. callErrorInfo(ei);
  948. }
  949. }
  950. /** The IRCStringConverter for this parser */
  951. private IRCStringConverter stringConverter = null;
  952. /** {@inheritDoc} */
  953. @Override
  954. public IRCStringConverter getStringConverter() {
  955. if (stringConverter == null) {
  956. stringConverter = new IRCStringConverter((byte)4);
  957. }
  958. return stringConverter;
  959. }
  960. /**
  961. * Update the character arrays.
  962. *
  963. * @param limit Number of post-alphabetical characters to convert
  964. * 0 = ascii encoding
  965. * 3 = strict-rfc1459 encoding
  966. * 4 = rfc1459 encoding
  967. */
  968. protected void updateCharArrays(final byte limit) {
  969. stringConverter = new IRCStringConverter(limit);
  970. }
  971. /**
  972. * Get the known boolean chanmodes in 005 order.
  973. * Modes are returned in the order that the ircd specifies the modes in 005
  974. * with any newly-found modes (mode being set that wasn't specified in 005)
  975. * being added at the end.
  976. *
  977. * @return All the currently known boolean modes
  978. */
  979. public String getBoolChanModes005() {
  980. // This code isn't the nicest, as Hashtable's don't lend themselves to being
  981. // ordered.
  982. // Order isn't really important, and this code only takes 3 lines of we
  983. // don't care about it but ordered guarentees that on a specific ircd this
  984. // method will ALWAYs return the same value.
  985. final char[] modes = new char[chanModesBool.size()];
  986. long nTemp;
  987. double pos;
  988. for (char cTemp : chanModesBool.keySet()) {
  989. nTemp = chanModesBool.get(cTemp);
  990. // nTemp should never be less than 0
  991. if (nTemp > 0) {
  992. pos = Math.log(nTemp) / Math.log(2);
  993. modes[(int)pos] = cTemp;
  994. }
  995. /* // Is there an easier way to find out the power of 2 value for a number?
  996. // ie 1024 = 10, 512 = 9 ?
  997. for (int i = 0; i < modes.length; i++) {
  998. if (Math.pow(2, i) == (double) nTemp) {
  999. modes[i] = cTemp;
  1000. break;
  1001. }
  1002. }*/
  1003. }
  1004. return new String(modes);
  1005. }
  1006. /**
  1007. * Process CHANMODES from 005.
  1008. */
  1009. public void parseChanModes() {
  1010. final StringBuilder sDefaultModes = new StringBuilder("b,k,l,");
  1011. String[] bits = null;
  1012. String modeStr;
  1013. if (h005Info.containsKey("USERCHANMODES")) {
  1014. if (getIRCD(true).equalsIgnoreCase("dancer")) {
  1015. sDefaultModes.insert(0, "dqeI");
  1016. } else if (getIRCD(true).equalsIgnoreCase("austirc")) {
  1017. sDefaultModes.insert(0, "e");
  1018. }
  1019. modeStr = h005Info.get("USERCHANMODES");
  1020. char mode;
  1021. for (int i = 0; i < modeStr.length(); ++i) {
  1022. mode = modeStr.charAt(i);
  1023. if (!prefixModes.containsKey(mode) && sDefaultModes.indexOf(Character.toString(mode)) < 0) {
  1024. sDefaultModes.append(mode);
  1025. }
  1026. }
  1027. } else {
  1028. sDefaultModes.append("imnpstrc");
  1029. }
  1030. if (h005Info.containsKey("CHANMODES")) {
  1031. modeStr = h005Info.get("CHANMODES");
  1032. } else {
  1033. modeStr = sDefaultModes.toString();
  1034. h005Info.put("CHANMODES", modeStr);
  1035. }
  1036. bits = modeStr.split(",", 5);
  1037. if (bits.length < 4) {
  1038. modeStr = sDefaultModes.toString();
  1039. callErrorInfo(new ParserError(ParserError.ERROR_ERROR, "CHANMODES String not valid. Using default string of \"" + modeStr + "\"", getLastLine()));
  1040. h005Info.put("CHANMODES", modeStr);
  1041. bits = modeStr.split(",", 5);
  1042. }
  1043. // resetState
  1044. chanModesOther.clear();
  1045. chanModesBool.clear();
  1046. nextKeyCMBool = 1;
  1047. // List modes.
  1048. for (int i = 0; i < bits[0].length(); ++i) {
  1049. final Character cMode = bits[0].charAt(i);
  1050. callDebugInfo(DEBUG_INFO, "Found List Mode: %c", cMode);
  1051. if (!chanModesOther.containsKey(cMode)) { chanModesOther.put(cMode, MODE_LIST); }
  1052. }
  1053. // Param for Set and Unset.
  1054. final Byte nBoth = MODE_SET + MODE_UNSET;
  1055. for (int i = 0; i < bits[1].length(); ++i) {
  1056. final Character cMode = bits[1].charAt(i);
  1057. callDebugInfo(DEBUG_INFO, "Found Set/Unset Mode: %c", cMode);
  1058. if (!chanModesOther.containsKey(cMode)) { chanModesOther.put(cMode, nBoth); }
  1059. }
  1060. // Param just for Set
  1061. for (int i = 0; i < bits[2].length(); ++i) {
  1062. final Character cMode = bits[2].charAt(i);
  1063. callDebugInfo(DEBUG_INFO, "Found Set Only Mode: %c", cMode);
  1064. if (!chanModesOther.containsKey(cMode)) { chanModesOther.put(cMode, MODE_SET); }
  1065. }
  1066. // Boolean Mode
  1067. for (int i = 0; i < bits[3].length(); ++i) {
  1068. final Character cMode = bits[3].charAt(i);
  1069. callDebugInfo(DEBUG_INFO, "Found Boolean Mode: %c [%d]", cMode, nextKeyCMBool);
  1070. if (!chanModesBool.containsKey(cMode)) {
  1071. chanModesBool.put(cMode, nextKeyCMBool);
  1072. nextKeyCMBool = nextKeyCMBool * 2;
  1073. }
  1074. }
  1075. }
  1076. /** {@inheritDoc} */
  1077. @Override
  1078. public String getChannelUserModes() {
  1079. if (h005Info.containsKey("PREFIXSTRING")) {
  1080. return h005Info.get("PREFIXSTRING");
  1081. } else {
  1082. return "";
  1083. }
  1084. }
  1085. /** {@inheritDoc} */
  1086. @Override
  1087. public String getBooleanChannelModes() {
  1088. final char[] modes = new char[chanModesBool.size()];
  1089. int i = 0;
  1090. for (char mode : chanModesBool.keySet()) {
  1091. modes[i++] = mode;
  1092. }
  1093. // Alphabetically sort the array
  1094. Arrays.sort(modes);
  1095. return new String(modes);
  1096. }
  1097. /** {@inheritDoc} */
  1098. @Override
  1099. public String getListChannelModes() {
  1100. return getOtherModeString(MODE_LIST);
  1101. }
  1102. /** {@inheritDoc} */
  1103. @Override
  1104. public String getParameterChannelModes() {
  1105. return getOtherModeString(MODE_SET);
  1106. }
  1107. /** {@inheritDoc} */
  1108. @Override
  1109. public String getDoubleParameterChannelModes() {
  1110. return getOtherModeString((byte) (MODE_SET + MODE_UNSET));
  1111. }
  1112. /**
  1113. * Get modes from hChanModesOther that have a specific value.
  1114. * Modes are returned in alphabetical order
  1115. *
  1116. * @param value Value mode must have to be included
  1117. * @return All the currently known Set-Unset modes
  1118. */
  1119. protected String getOtherModeString(final byte value) {
  1120. final char[] modes = new char[chanModesOther.size()];
  1121. Byte nTemp;
  1122. int i = 0;
  1123. for (char cTemp : chanModesOther.keySet()) {
  1124. nTemp = chanModesOther.get(cTemp);
  1125. if (nTemp == value) { modes[i++] = cTemp; }
  1126. }
  1127. // Alphabetically sort the array
  1128. Arrays.sort(modes);
  1129. return new String(modes).trim();
  1130. }
  1131. /** {@inheritDoc} */
  1132. @Override
  1133. public String getUserModes() {
  1134. if (h005Info.containsKey("USERMODES")) {
  1135. return h005Info.get("USERMODES");
  1136. } else {
  1137. return "";
  1138. }
  1139. }
  1140. /**
  1141. * Process USERMODES from 004.
  1142. */
  1143. protected void parseUserModes() {
  1144. final String sDefaultModes = "nwdoi";
  1145. String modeStr;
  1146. if (h005Info.containsKey("USERMODES")) {
  1147. modeStr = h005Info.get("USERMODES");
  1148. } else {
  1149. modeStr = sDefaultModes;
  1150. h005Info.put("USERMODES", sDefaultModes);
  1151. }
  1152. // resetState
  1153. userModes.clear();
  1154. nNextKeyUser = 1;
  1155. // Boolean Mode
  1156. for (int i = 0; i < modeStr.length(); ++i) {
  1157. final Character cMode = modeStr.charAt(i);
  1158. callDebugInfo(DEBUG_INFO, "Found User Mode: %c [%d]", cMode, nNextKeyUser);
  1159. if (!userModes.containsKey(cMode)) {
  1160. userModes.put(cMode, nNextKeyUser);
  1161. nNextKeyUser = nNextKeyUser * 2;
  1162. }
  1163. }
  1164. }
  1165. /**
  1166. * Process CHANTYPES from 005.
  1167. */
  1168. protected void parseChanPrefix() {
  1169. final String sDefaultModes = "#&";
  1170. String modeStr;
  1171. if (h005Info.containsKey("CHANTYPES")) {
  1172. modeStr = h005Info.get("CHANTYPES");
  1173. } else {
  1174. modeStr = sDefaultModes;
  1175. h005Info.put("CHANTYPES", sDefaultModes);
  1176. }
  1177. // resetState
  1178. chanPrefix.clear();
  1179. // Boolean Mode
  1180. for (int i = 0; i < modeStr.length(); ++i) {
  1181. final Character cMode = modeStr.charAt(i);
  1182. callDebugInfo(DEBUG_INFO, "Found Chan Prefix: %c", cMode);
  1183. if (!chanPrefix.containsKey(cMode)) { chanPrefix.put(cMode, true); }
  1184. }
  1185. }
  1186. /**
  1187. * Process PREFIX from 005.
  1188. */
  1189. public void parsePrefixModes() {
  1190. final String sDefaultModes = "(ohv)@%+";
  1191. String[] bits;
  1192. String modeStr;
  1193. if (h005Info.containsKey("PREFIX")) {
  1194. modeStr = h005Info.get("PREFIX");
  1195. } else {
  1196. modeStr = sDefaultModes;
  1197. }
  1198. if (modeStr.substring(0, 1).equals("(")) {
  1199. modeStr = modeStr.substring(1);
  1200. } else {
  1201. modeStr = sDefaultModes.substring(1);
  1202. h005Info.put("PREFIX", sDefaultModes);
  1203. }
  1204. bits = modeStr.split("\\)", 2);
  1205. if (bits.length != 2 || bits[0].length() != bits[1].length()) {
  1206. modeStr = sDefaultModes;
  1207. callErrorInfo(new ParserError(ParserError.ERROR_ERROR, "PREFIX String not valid. Using default string of \"" + modeStr + "\"", getLastLine()));
  1208. h005Info.put("PREFIX", modeStr);
  1209. modeStr = modeStr.substring(1);
  1210. bits = modeStr.split("\\)", 2);
  1211. }
  1212. // resetState
  1213. prefixModes.clear();
  1214. prefixMap.clear();
  1215. nextKeyPrefix = 1;
  1216. for (int i = bits[0].length() - 1; i > -1; --i) {
  1217. final Character cMode = bits[0].charAt(i);
  1218. final Character cPrefix = bits[1].charAt(i);
  1219. callDebugInfo(DEBUG_INFO, "Found Prefix Mode: %c => %c [%d]", cMode, cPrefix, nextKeyPrefix);
  1220. if (!prefixModes.containsKey(cMode)) {
  1221. prefixModes.put(cMode, nextKeyPrefix);
  1222. prefixMap.put(cMode, cPrefix);
  1223. prefixMap.put(cPrefix, cMode);
  1224. nextKeyPrefix = nextKeyPrefix * 2;
  1225. }
  1226. }
  1227. h005Info.put("PREFIXSTRING", bits[0]);
  1228. }
  1229. /**
  1230. * Check if server is ready.
  1231. *
  1232. * @return true if 001 has been recieved, false otherwise.
  1233. */
  1234. public boolean isReady() { return got001; }
  1235. /** {@inheritDoc} */
  1236. @Override
  1237. public void joinChannel(final String channel) {
  1238. joinChannel(channel, "", true);
  1239. }
  1240. /**
  1241. * Join a Channel.
  1242. *
  1243. * @param sChannelName Name of channel to join
  1244. * @param autoPrefix Automatically prepend the first channel prefix defined
  1245. * in 005 if sChannelName is an invalid channel.
  1246. * **This only applies to the first channel if given a list**
  1247. */
  1248. public void joinChannel(final String sChannelName, final boolean autoPrefix) {
  1249. joinChannel(sChannelName, "", autoPrefix);
  1250. }
  1251. /** {@inheritDoc} */
  1252. @Override
  1253. public void joinChannel(final String channel, final String key) {
  1254. joinChannel(channel, key, true);
  1255. }
  1256. /**
  1257. * Join a Channel with a key.
  1258. *
  1259. * @param channel Name of channel to join
  1260. * @param key Key to use to try and join the channel
  1261. * @param autoPrefix Automatically prepend the first channel prefix defined
  1262. * in 005 if sChannelName is an invalid channel.
  1263. * **This only applies to the first channel if given a list**
  1264. */
  1265. public void joinChannel(final String channel, final String key, final boolean autoPrefix) {
  1266. final String channelName;
  1267. if (isValidChannelName(channel)) {
  1268. channelName = channel;
  1269. } else {
  1270. if (autoPrefix) {
  1271. if (h005Info.containsKey("CHANTYPES")) {
  1272. final String chantypes = h005Info.get("CHANTYPES");
  1273. if (chantypes.isEmpty()) {
  1274. channelName = "#" + channel;
  1275. } else {
  1276. channelName = chantypes.charAt(0) + channel;
  1277. }
  1278. } else {
  1279. return;
  1280. }
  1281. } else {
  1282. return;
  1283. }
  1284. }
  1285. if (key.isEmpty()) {
  1286. sendString("JOIN " + channelName);
  1287. } else {
  1288. sendString("JOIN " + channelName + " " + key);
  1289. }
  1290. }
  1291. /**
  1292. * Leave a Channel.
  1293. *
  1294. * @param channel Name of channel to part
  1295. * @param reason Reason for leaving (Nothing sent if sReason is "")
  1296. */
  1297. public void partChannel(final String channel, final String reason) {
  1298. if (getChannel(channel) == null) { return; }
  1299. if (reason.isEmpty()) {
  1300. sendString("PART " + channel);
  1301. } else {
  1302. sendString("PART " + channel + " :" + reason);
  1303. }
  1304. }
  1305. /**
  1306. * Set Nickname.
  1307. *
  1308. * @param nickname New nickname wanted.
  1309. */
  1310. public void setNickname(final String nickname) {
  1311. if (getSocketState() == SocketState.OPEN) {
  1312. if (!myself.isFake() && myself.getRealNickname().equals(nickname)) {
  1313. return;
  1314. }
  1315. sendString("NICK " + nickname);
  1316. } else {
  1317. me.setNickname(nickname);
  1318. }
  1319. thinkNickname = nickname;
  1320. }
  1321. /** {@inheritDoc} */
  1322. @Override
  1323. public int getMaxLength(final String type, final String target) {
  1324. // If my host is "nick!user@host" and we are sending "#Channel"
  1325. // a "PRIVMSG" this will find the length of ":nick!user@host PRIVMSG #channel :"
  1326. // and subtract it from the MAX_LINELENGTH. This should be sufficient in most cases.
  1327. // Lint = the 2 ":" at the start and end and the 3 separating " "s
  1328. int length = 0;
  1329. if (type != null) { length = length + type.length(); }
  1330. if (target != null) { length = length + target.length(); }
  1331. return getMaxLength(length);
  1332. }
  1333. /**
  1334. * Get the max length a message can be.
  1335. *
  1336. * @param length Length of stuff. (Ie "PRIVMSG"+"#Channel")
  1337. * @return Max Length message should be.
  1338. */
  1339. public int getMaxLength(final int length) {
  1340. final int lineLint = 5;
  1341. if (myself.isFake()) {
  1342. callErrorInfo(new ParserError(ParserError.ERROR_ERROR + ParserError.ERROR_USER, "getMaxLength() called, but I don't know who I am?", getLastLine()));
  1343. return MAX_LINELENGTH - length - lineLint;
  1344. } else {
  1345. return MAX_LINELENGTH - myself.toString().length() - length - lineLint;
  1346. }
  1347. }
  1348. /** {@inheritDoc} */
  1349. @Override
  1350. public int getMaxListModes(final char mode) {
  1351. // MAXLIST=bdeI:50
  1352. // MAXLIST=b:60,e:60,I:60
  1353. // MAXBANS=30
  1354. int result = -2;
  1355. callDebugInfo(DEBUG_INFO, "Looking for maxlistmodes for: "+mode);
  1356. // Try in MAXLIST
  1357. if (h005Info.get("MAXLIST") != null) {
  1358. if (h005Info.get("MAXBANS") == null) {
  1359. result = 0;
  1360. }
  1361. final String maxlist = h005Info.get("MAXLIST");
  1362. callDebugInfo(DEBUG_INFO, "Found maxlist ("+maxlist+")");
  1363. final String[] bits = maxlist.split(",");
  1364. for (String bit : bits) {
  1365. final String[] parts = bit.split(":", 2);
  1366. callDebugInfo(DEBUG_INFO, "Bit: "+bit+" | parts.length = "+parts.length+" ("+parts[0]+" -> "+parts[0].indexOf(mode)+")");
  1367. if (parts.length == 2 && parts[0].indexOf(mode) > -1) {
  1368. callDebugInfo(DEBUG_INFO, "parts[0] = '"+parts[0]+"' | parts[1] = '"+parts[1]+"'");
  1369. try {
  1370. result = Integer.parseInt(parts[1]);
  1371. break;
  1372. } catch (NumberFormatException nfe) { result = -1; }
  1373. }
  1374. }
  1375. }
  1376. // If not in max list, try MAXBANS
  1377. if (result == -2 && h005Info.get("MAXBANS") != null) {
  1378. callDebugInfo(DEBUG_INFO, "Trying max bans");
  1379. try {
  1380. result = Integer.parseInt(h005Info.get("MAXBANS"));
  1381. } catch (NumberFormatException nfe) { result = -1; }
  1382. } else if (result == -2 && getIRCD(true).equalsIgnoreCase("weircd")) {
  1383. // -_-
  1384. result = 50;
  1385. } else if (result == -2) {
  1386. result = -1;
  1387. callDebugInfo(DEBUG_INFO, "Failed");
  1388. callErrorInfo(new ParserError(ParserError.ERROR_ERROR, "Unable to discover max list modes.", getLastLine()));
  1389. }
  1390. callDebugInfo(DEBUG_INFO, "Result: "+result);
  1391. return result;
  1392. }
  1393. /** {@inheritDoc} */
  1394. @Override
  1395. public void sendMessage(final String target, final String message) {
  1396. if (target == null || message == null) { return; }
  1397. if (target.isEmpty()/* || sMessage.isEmpty()*/) { return; }
  1398. sendString("PRIVMSG " + target + " :" + message);
  1399. }
  1400. /** {@inheritDoc} */
  1401. @Override
  1402. public void sendNotice(final String target, final String message) {
  1403. if (target == null || message == null) { return; }
  1404. if (target.isEmpty()/* || sMessage.isEmpty()*/) { return; }
  1405. sendString("NOTICE " + target + " :" + message);
  1406. }
  1407. /** {@inheritDoc} */
  1408. @Override
  1409. public void sendAction(final String target, final String message) {
  1410. sendCTCP(target, "ACTION", message);
  1411. }
  1412. /** {@inheritDoc} */
  1413. @Override
  1414. public void sendCTCP(final String target, final String type, final String message) {
  1415. if (target == null || message == null) { return; }
  1416. if (target.isEmpty() || type.isEmpty()) { return; }
  1417. final char char1 = (char) 1;
  1418. sendString("PRIVMSG " + target + " :" + char1 + type.toUpperCase() + " " + message + char1);
  1419. }
  1420. /** {@inheritDoc} */
  1421. @Override
  1422. public void sendCTCPReply(final String target, final String type, final String message) {
  1423. if (target == null || message == null) { return; }
  1424. if (target.isEmpty() || type.isEmpty()) { return; }
  1425. final char char1 = (char) 1;
  1426. sendString("NOTICE " + target + " :" + char1 + type.toUpperCase() + " " + message + char1);
  1427. }
  1428. /**
  1429. * Quit IRC.
  1430. * This method will wait for the server to close the socket.
  1431. *
  1432. * @param reason Reason for quitting.
  1433. */
  1434. public void quit(final String reason) {
  1435. if (reason.isEmpty()) {
  1436. sendString("QUIT");
  1437. } else {
  1438. sendString("QUIT :" + reason);
  1439. }
  1440. }
  1441. /** {@inheritDoc} */
  1442. @Override
  1443. public void disconnect(final String message) {
  1444. if (currentSocketState == SocketState.OPEN) {
  1445. currentSocketState = SocketState.CLOSING;
  1446. if (got001) { quit(message); }
  1447. }
  1448. try {
  1449. if (socket != null) { socket.close(); }
  1450. } catch (IOException e) {
  1451. /* Do Nothing */
  1452. } finally {
  1453. if (currentSocketState != SocketState.CLOSED) {
  1454. currentSocketState = SocketState.CLOSED;
  1455. callSocketClosed();
  1456. }
  1457. resetState();
  1458. }
  1459. }
  1460. /** {@inheritDoc}
  1461. *
  1462. * - Before channel prefixes are known (005/noMOTD/MOTDEnd), this checks
  1463. * that the first character is either #, &amp;, ! or +
  1464. * - Assumes that any channel that is already known is valid, even if
  1465. * 005 disagrees.
  1466. */
  1467. @Override
  1468. public boolean isValidChannelName(final String name) {
  1469. // Check sChannelName is not empty or null
  1470. if (name == null || name.isEmpty()) { return false; }
  1471. // Check its not ourself (PM recieved before 005)
  1472. if (getStringConverter().equalsIgnoreCase(getMyNickname(), name)) { return false; }
  1473. // Check if we are already on this channel
  1474. if (getChannel(name) != null) { return true; }
  1475. // Check if we know of any valid chan prefixes
  1476. if (chanPrefix.isEmpty()) {
  1477. // We don't. Lets check against RFC2811-Specified channel types
  1478. final char first = name.charAt(0);
  1479. return first == '#' || first == '&' || first == '!' || first == '+';
  1480. }
  1481. // Otherwise return true if:
  1482. // Channel equals "0"
  1483. // first character of the channel name is a valid channel prefix.
  1484. return chanPrefix.containsKey(name.charAt(0)) || name.equals("0");
  1485. }
  1486. /** {@inheritDoc} */
  1487. @Override
  1488. public boolean isUserSettable(final char mode) {
  1489. String validmodes;
  1490. if (h005Info.containsKey("USERCHANMODES")) {
  1491. validmodes = h005Info.get("USERCHANMODES");
  1492. } else {
  1493. validmodes = "bklimnpstrc";
  1494. }
  1495. return validmodes.matches(".*" + mode + ".*");
  1496. }
  1497. /**
  1498. * Get the 005 info.
  1499. *
  1500. * @return 005Info hashtable.
  1501. */
  1502. public Map<String, String> get005() { return h005Info; }
  1503. /**
  1504. * Get the name of the ircd.
  1505. *
  1506. * @param getType if this is false the string from 004 is returned. Else a guess of the type (ircu, hybrid, ircnet)
  1507. * @return IRCD Version or Type
  1508. */
  1509. public String getIRCD(final boolean getType) {
  1510. if (h005Info.containsKey("004IRCD")) {
  1511. final String version = h005Info.get("004IRCD");
  1512. if (getType) {
  1513. // This ilst is vaugly based on http://searchirc.com/ircd-versions,
  1514. // but keeping groups of ircd's together (ie hybrid-based, ircu-based)
  1515. if (version.matches("(?i).*unreal[^4-9].*")) { return "unreal"; }
  1516. else if (version.matches("(?i).*unreal[4-9].*")) { return "unreal4"; }
  1517. else if (version.matches("(?i).*bahamut.*")) { return "bahamut"; }
  1518. else if (version.matches("(?i).*nefarious.*")) { return "nefarious"; }
  1519. else if (version.matches("(?i).*asuka.*")) { return "asuka"; }
  1520. else if (version.matches("(?i).*snircd.*")) { return "snircd"; }
  1521. else if (version.matches("(?i).*beware.*")) { return "bircd"; }
  1522. else if (version.matches("(?i).*u2\\.[0-9]+\\.H\\..*")) { return "irchispano"; }
  1523. else if (version.matches("(?i).*u2\\.[0-9]+\\..*")) { return "ircu"; }
  1524. else if (version.matches("(?i).*ircu.*")) { return "ircu"; }
  1525. else if (version.matches("(?i).*plexus.*")) { return "plexus"; }
  1526. else if (version.matches("(?i).*hybrid.*oftc.*")) { return "oftc-hybrid"; }
  1527. else if (version.matches("(?i).*ircd.hybrid.*")) { return "hybrid7"; }
  1528. else if (version.matches("(?i).*hybrid.*")) { return "hybrid"; }
  1529. else if (version.matches("(?i).*charybdis.*")) { return "charybdis"; }
  1530. else if (version.matches("(?i).*inspircd.*")) { return "inspircd"; }
  1531. else if (version.matches("(?i).*ultimateircd.*")) { return "ultimateircd"; }
  1532. else if (version.matches("(?i).*critenircd.*")) { return "critenircd"; }
  1533. else if (version.matches("(?i).*fqircd.*")) { return "fqircd"; }
  1534. else if (version.matches("(?i).*conferenceroom.*")) { return "conferenceroom"; }
  1535. else if (version.matches("(?i).*hyperion.*")) { return "hyperion"; }
  1536. else if (version.matches("(?i).*dancer.*")) { return "dancer"; }
  1537. else if (version.matches("(?i).*austhex.*")) { return "austhex"; }
  1538. else if (version.matches("(?i).*austirc.*")) { return "austirc"; }
  1539. else if (version.matches("(?i).*ratbox.*")) { return "ratbox"; }
  1540. else if (version.matches("(?i).*euircd.*")) { return "euircd"; }
  1541. else if (version.matches("(?i).*weircd.*")) { return "weircd"; }
  1542. else if (version.matches("(?i).*swiftirc.*")) { return "swiftirc"; }
  1543. else {
  1544. // Stupid networks/ircds go here...
  1545. if (networkName.equalsIgnoreCase("ircnet")) { return "ircnet"; }
  1546. else if (networkName.equalsIgnoreCase("starchat")) { return "starchat"; }
  1547. else if (networkName.equalsIgnoreCase("bitlbee")) { return "bitlbee"; }
  1548. else if (h005Info.containsKey("003IRCD") && h005Info.get("003IRCD").matches("(?i).*bitlbee.*")) { return "bitlbee"; } // Older bitlbee
  1549. else if (h005Info.containsKey("002IRCD") && h005Info.get("002IRCD").matches("(?i).*pastiche.*")) { return "ircd-pastiche"; }
  1550. else { return "generic"; }
  1551. }
  1552. } else {
  1553. return version;
  1554. }
  1555. } else {
  1556. if (getType) { return "generic"; }
  1557. else { return ""; }
  1558. }
  1559. }
  1560. /** {@inheritDoc} */
  1561. @Override
  1562. public String getServerSoftware() {
  1563. return getIRCD(false);
  1564. }
  1565. /** {@inheritDoc} */
  1566. @Override
  1567. public String getServerSoftwareType() {
  1568. return getIRCD(true);
  1569. }
  1570. /**
  1571. * Get the value of checkServerPing.
  1572. *
  1573. * @return value of checkServerPing.
  1574. * @see setCheckServerPing
  1575. */
  1576. public boolean getCheckServerPing() { return checkServerPing; }
  1577. /**
  1578. * Set the value of checkServerPing.
  1579. *
  1580. * @param newValue New value to use.
  1581. * @see setCheckServerPing
  1582. */
  1583. public void setCheckServerPing(final boolean newValue) {
  1584. checkServerPing = newValue;
  1585. if (checkServerPing) {
  1586. startPingTimer();
  1587. } else {
  1588. stopPingTimer();
  1589. }
  1590. }
  1591. /** {@inheritDoc} */
  1592. @Override
  1593. public long getPingTimerInterval() { return pingTimerLength; }
  1594. /** {@inheritDoc} */
  1595. @Override
  1596. public void setPingTimerInterval(final long newValue) {
  1597. pingTimerLength = newValue;
  1598. startPingTimer();
  1599. }
  1600. /** {@inheritDoc} */
  1601. @Override
  1602. public int getPingTimerFraction() { return pingCountDownLength; }
  1603. /** {@inheritDoc} */
  1604. @Override
  1605. public void setPingTimerFraction(final int newValue) {
  1606. pingCountDownLength = newValue;
  1607. }
  1608. /**
  1609. * Start the pingTimer.
  1610. */
  1611. protected void startPingTimer() {
  1612. pingTimerSem.acquireUninterruptibly();
  1613. setPingNeeded(false);
  1614. if (pingTimer != null) { pingTimer.cancel(); }
  1615. pingTimer = new Timer("IRCParser pingTimer");
  1616. pingTimer.schedule(new PingTimer(this, pingTimer), 0, pingTimerLength);
  1617. pingCountDown = 1;
  1618. pingTimerSem.release();
  1619. }
  1620. /**
  1621. * Stop the pingTimer.
  1622. */
  1623. protected void stopPingTimer() {
  1624. pingTimerSem.acquireUninterruptibly();
  1625. if (pingTimer != null) {
  1626. pingTimer.cancel();
  1627. pingTimer = null;
  1628. }
  1629. pingTimerSem.release();
  1630. }
  1631. /**
  1632. * This is called when the ping Timer has been executed.
  1633. * As the timer is restarted on every incomming message, this will only be
  1634. * called when there has been no incomming line for 10 seconds.
  1635. *
  1636. * @param timer The timer that called this.
  1637. */
  1638. protected void pingTimerTask(final Timer timer) {
  1639. if (!getCheckServerPing()) {
  1640. pingTimerSem.acquireUninterruptibly();
  1641. if (pingTimer != null && pingTimer.equals(timer)) {
  1642. pingTimer.cancel();
  1643. }
  1644. pingTimerSem.release();
  1645. return;
  1646. }
  1647. if (getPingNeeded()) {
  1648. if (!callPingFailed()) {
  1649. pingTimerSem.acquireUninterruptibly();
  1650. if (pingTimer != null && pingTimer.equals(timer)) {
  1651. pingTimer.cancel();
  1652. }
  1653. pingTimerSem.release();
  1654. disconnect("Server not responding.");
  1655. }
  1656. } else {
  1657. --pingCountDown;
  1658. if (pingCountDown < 1) {
  1659. pingTime = System.currentTimeMillis();
  1660. setPingNeeded(true);
  1661. pingCountDown = pingCountDownLength;
  1662. lastPingValue = String.valueOf(System.currentTimeMillis());
  1663. if (sendString("PING " + lastPingValue)) {
  1664. callPingSent();
  1665. }
  1666. }
  1667. }
  1668. }
  1669. /** {@inheritDoc} */
  1670. @Override
  1671. public long getServerLatency() {
  1672. return serverLag;
  1673. }
  1674. /**
  1675. * Get the current server lag.
  1676. *
  1677. * @param actualTime if True the value returned will be the actual time the ping was sent
  1678. * else it will be the amount of time sinse the last ping was sent.
  1679. * @return Time last ping was sent
  1680. */
  1681. public long getPingTime(final boolean actualTime) {
  1682. if (actualTime) { return pingTime; }
  1683. else { return System.currentTimeMillis() - pingTime; }
  1684. }
  1685. /** {@inheritDoc} */
  1686. @Override
  1687. public long getPingTime() {
  1688. return getPingTime(false);
  1689. }
  1690. /**
  1691. * Set if a ping is needed or not.
  1692. *
  1693. * @param newStatus new value to set pingNeeded to.
  1694. */
  1695. private void setPingNeeded(final boolean newStatus) {
  1696. pingNeeded.set(newStatus);
  1697. }
  1698. /**
  1699. * Get if a ping is needed or not.
  1700. *
  1701. * @return value of pingNeeded.
  1702. */
  1703. private boolean getPingNeeded() {
  1704. return pingNeeded.get();
  1705. }
  1706. /** {@inheritDoc} */
  1707. @Override
  1708. public IRCClientInfo getLocalClient() { return myself; }
  1709. /**
  1710. * Get the current nickname.
  1711. * After 001 this returns the exact same as getLocalClient().getRealNickname();
  1712. * Before 001 it returns the nickname that the parser Thinks it has.
  1713. *
  1714. * @return Current nickname.
  1715. */
  1716. public String getMyNickname() {
  1717. if (myself.isFake()) {
  1718. return thinkNickname;
  1719. } else {
  1720. return myself.getRealNickname();
  1721. }
  1722. }
  1723. /**
  1724. * Get the current username (Specified in MyInfo on construction).
  1725. * Get the username given in MyInfo
  1726. *
  1727. * @return My username.
  1728. */
  1729. public String getMyUsername() {
  1730. return me.getUsername();
  1731. }
  1732. /**
  1733. * Add a client to the ClientList.
  1734. *
  1735. * @param client Client to add
  1736. */
  1737. public void addClient(final IRCClientInfo client) {
  1738. clientList.put(getStringConverter().toLowerCase(client.getRealNickname()),client);
  1739. }
  1740. /**
  1741. * Remove a client from the ClientList.
  1742. * This WILL NOT allow cMyself to be removed from the list.
  1743. *
  1744. * @param client Client to remove
  1745. */
  1746. public void removeClient(final IRCClientInfo client) {
  1747. if (client != myself) {
  1748. forceRemoveClient(client);
  1749. }
  1750. }
  1751. /**
  1752. * Remove a client from the ClientList.
  1753. . * This WILL allow cMyself to be removed from the list
  1754. *
  1755. * @param client Client to remove
  1756. */
  1757. protected void forceRemoveClient(final IRCClientInfo client) {
  1758. clientList.remove(getStringConverter().toLowerCase(client.getRealNickname()));
  1759. }
  1760. /**
  1761. * Get the number of known clients.
  1762. *
  1763. * @return Count of known clients
  1764. */
  1765. public int knownClients() {
  1766. return clientList.size();
  1767. }
  1768. /**
  1769. * Get the known clients as a collection.
  1770. *
  1771. * @return Known clients as a collection
  1772. */
  1773. public Collection<IRCClientInfo> getClients() {
  1774. return clientList.values();
  1775. }
  1776. /**
  1777. * Clear the client list.
  1778. */
  1779. public void clearClients() {
  1780. clientList.clear();
  1781. addClient(getLocalClient());
  1782. }
  1783. /**
  1784. * Add a channel to the ChannelList.
  1785. *
  1786. * @param channel Channel to add
  1787. */
  1788. public void addChannel(final IRCChannelInfo channel) {
  1789. synchronized (channelList) {
  1790. channelList.put(getStringConverter().toLowerCase(channel.getName()), channel);
  1791. }
  1792. }
  1793. /**
  1794. * Remove a channel from the ChannelList.
  1795. *
  1796. * @param channel Channel to remove
  1797. */
  1798. public void removeChannel(final IRCChannelInfo channel) {
  1799. synchronized (channelList) {
  1800. channelList.remove(getStringConverter().toLowerCase(channel.getName()));
  1801. }
  1802. }
  1803. /**
  1804. * Get the number of known channel.
  1805. *
  1806. * @return Count of known channel
  1807. */
  1808. public int knownChannels() {
  1809. synchronized (channelList) {
  1810. return channelList.size();
  1811. }
  1812. }
  1813. /** {@inheritDoc} */
  1814. @Override
  1815. public Collection<IRCChannelInfo> getChannels() {
  1816. synchronized (channelList) {
  1817. return channelList.values();
  1818. }
  1819. }
  1820. /**
  1821. * Clear the channel list.
  1822. */
  1823. public void clearChannels() {
  1824. synchronized (channelList) {
  1825. channelList.clear();
  1826. }
  1827. }
  1828. /** {@inheritDoc} */
  1829. @Override
  1830. public String[] parseHostmask(final String hostmask) {
  1831. return IRCClientInfo.parseHostFull(hostmask);
  1832. }
  1833. /** {@inheritDoc} */
  1834. @Override
  1835. public int getMaxTopicLength() {
  1836. if (h005Info.containsKey("TOPICLEN")) {
  1837. try {
  1838. return Integer.parseInt(h005Info.get("TOPICLEN"));
  1839. } catch (NumberFormatException ex) {
  1840. // Do nothing
  1841. }
  1842. }
  1843. return 0;
  1844. }
  1845. /** {@inheritDoc} */
  1846. @Override
  1847. public int getMaxLength() {
  1848. return MAX_LINELENGTH;
  1849. }
  1850. }