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