001: /*
002: * EditServer.java - jEdit server
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1999, 2003 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit;
024:
025: //{{{ Imports
026: import org.gjt.sp.jedit.bsh.NameSpace;
027: import javax.swing.SwingUtilities;
028: import java.io.*;
029: import java.net.*;
030: import java.util.Random;
031: import org.gjt.sp.jedit.io.FileVFS;
032: import org.gjt.sp.util.Log;
033:
034: //}}}
035:
036: /**
037: * Inter-process communication.<p>
038: *
039: * The edit server protocol is very simple. <code>$HOME/.jedit/server</code>
040: * is an ASCII file containing two lines, the first being the port number,
041: * the second being the authorization key.<p>
042: *
043: * You connect to that port on the local machine, sending the authorization
044: * key as four bytes in network byte order, followed by the length of the
045: * BeanShell script as two bytes in network byte order, followed by the
046: * script in UTF8 encoding. After the socked is closed, the BeanShell script
047: * will be executed by jEdit.<p>
048: *
049: * The snippet is executed in the AWT thread. None of the usual BeanShell
050: * variables (view, buffer, textArea, editPane) are set so the script has to
051: * figure things out by itself.<p>
052: *
053: * In most cases, the script will call the static
054: * {@link #handleClient(boolean,String,String[])} method, but of course more
055: * complicated stuff can be done too.
056: *
057: * @author Slava Pestov
058: * @version $Id: EditServer.java 10827 2007-10-06 22:03:50Z ezust $
059: */
060: public class EditServer extends Thread {
061: //{{{ EditServer constructor
062: EditServer(String portFile) {
063: super ("jEdit server daemon [" + portFile + "]");
064: setDaemon(true);
065: this .portFile = portFile;
066:
067: try {
068: // On Unix, set permissions of port file to rw-------,
069: // so that on broken Unices which give everyone read
070: // access to user home dirs, people can't see your
071: // port file (and hence send arbitriary BeanShell code
072: // your way. Nasty.)
073: if (OperatingSystem.isUnix()) {
074: new File(portFile).createNewFile();
075: FileVFS.setPermissions(portFile, 0600);
076: }
077:
078: // Bind to any port on localhost; accept 2 simultaneous
079: // connection attempts before rejecting connections
080: socket = new ServerSocket(0, 2, InetAddress
081: .getByName("127.0.0.1"));
082: authKey = Math.abs(new Random().nextInt());
083: int port = socket.getLocalPort();
084:
085: FileWriter out = new FileWriter(portFile);
086:
087: try {
088: out.write("b\n");
089: out.write(String.valueOf(port));
090: out.write("\n");
091: out.write(String.valueOf(authKey));
092: out.write("\n");
093: } finally {
094: out.close();
095: }
096:
097: ok = true;
098:
099: Log.log(Log.DEBUG, this , "jEdit server started on port "
100: + socket.getLocalPort());
101: Log.log(Log.DEBUG, this , "Authorization key is " + authKey);
102: } catch (IOException io) {
103: /* on some Windows versions, connections to localhost
104: * fail if the network is not running. To avoid
105: * confusing newbies with weird error messages, log
106: * errors that occur while starting the server
107: * as NOTICE, not ERROR */
108: Log.log(Log.NOTICE, this , io);
109: }
110: } //}}}
111:
112: //{{{ run() method
113: public void run() {
114: for (;;) {
115: if (abort)
116: return;
117:
118: Socket client = null;
119: try {
120: client = socket.accept();
121:
122: // Stop script kiddies from opening the edit
123: // server port and just leaving it open, as a
124: // DoS
125: client.setSoTimeout(1000);
126:
127: Log.log(Log.MESSAGE, this , client + ": connected");
128:
129: DataInputStream in = new DataInputStream(client
130: .getInputStream());
131:
132: if (!handleClient(client, in))
133: abort = true;
134: } catch (Exception e) {
135: if (!abort)
136: Log.log(Log.ERROR, this , e);
137: abort = true;
138: } finally {
139: /* if(client != null)
140: {
141: try
142: {
143: client.close();
144: }
145: catch(Exception e)
146: {
147: Log.log(Log.ERROR,this,e);
148: }
149:
150: client = null;
151: } */
152: }
153: }
154: } //}}}
155:
156: //{{{ handleClient() method
157: /**
158: * @param restore Ignored unless no views are open
159: * @param parent The client's parent directory
160: * @param args A list of files. Null entries are ignored, for convinience
161: * @since jEdit 3.2pre7
162: */
163: public static void handleClient(boolean restore, String parent,
164: String[] args) {
165: handleClient(restore, false, false, parent, args);
166: } //}}}
167:
168: //{{{ handleClient() method
169: /**
170: * @param restore Ignored unless no views are open
171: * @param newView Open a new view?
172: * @param newPlainView Open a new plain view?
173: * @param parent The client's parent directory
174: * @param args A list of files. Null entries are ignored, for convinience
175: * @since jEdit 4.2pre1
176: */
177: public static Buffer handleClient(boolean restore, boolean newView,
178: boolean newPlainView, String parent, String[] args) {
179: // we have to deal with a huge range of possible border cases here.
180: if (jEdit.getFirstView() == null) {
181: // coming out of background mode.
182: // no views open.
183: // no buffers open if args empty.
184:
185: Buffer buffer = jEdit.openFiles(null, parent, args);
186:
187: if (jEdit.getBufferCount() == 0)
188: jEdit.newFile(null);
189:
190: boolean restoreFiles = restore
191: && jEdit.getBooleanProperty("restore")
192: && (buffer == null || jEdit
193: .getBooleanProperty("restore.cli"));
194:
195: View view = PerspectiveManager
196: .loadPerspective(restoreFiles);
197:
198: if (view == null) {
199: if (buffer == null)
200: buffer = jEdit.getFirstBuffer();
201: view = jEdit.newView(null, buffer);
202: } else if (buffer != null)
203: view.setBuffer(buffer, false);
204:
205: return buffer;
206: } else if (newPlainView) {
207: // no background mode, and opening a new view
208: Buffer buffer = jEdit.openFiles(null, parent, args);
209: if (buffer == null)
210: buffer = jEdit.getFirstBuffer();
211: jEdit.newView(null, buffer, true);
212: return buffer;
213: } else if (newView) {
214: // no background mode, and opening a new view
215: Buffer buffer = jEdit.openFiles(null, parent, args);
216: if (buffer == null)
217: buffer = jEdit.getFirstBuffer();
218: jEdit.newView(jEdit.getActiveView(), buffer, false);
219: return buffer;
220: } else {
221: // no background mode, and reusing existing view
222: View view = jEdit.getActiveView();
223:
224: Buffer buffer = jEdit.openFiles(view, parent, args);
225:
226: // Hack done to fix bringing the window to the front.
227: // At least on windows, Frame.toFront() doesn't cut it.
228: // Remove the isWindows check if it's broken under other
229: // OSes too.
230: if (jEdit.getBooleanProperty("server.brokenToFront"))
231: view.setState(java.awt.Frame.ICONIFIED);
232:
233: // un-iconify using JDK 1.3 API
234: view.setState(java.awt.Frame.NORMAL);
235: view.requestFocus();
236: view.toFront();
237:
238: return buffer;
239: }
240: } //}}}
241:
242: //{{{ isOK() method
243: boolean isOK() {
244: return ok;
245: } //}}}
246:
247: //{{{ getPort method
248: public int getPort() {
249: return socket.getLocalPort();
250: } //}}}
251:
252: //{{{ stopServer() method
253: void stopServer() {
254: abort = true;
255: try {
256: socket.close();
257: } catch (IOException io) {
258: }
259:
260: new File(portFile).delete();
261: } //}}}
262:
263: //{{{ Private members
264:
265: //{{{ Instance variables
266: private String portFile;
267: private ServerSocket socket;
268: private int authKey;
269: private boolean ok;
270: private boolean abort;
271:
272: //}}}
273:
274: //{{{ handleClient() method
275: private boolean handleClient(final Socket client, DataInputStream in)
276: throws Exception {
277: int key = in.readInt();
278: if (key != authKey) {
279: Log.log(Log.ERROR, this , client + ": wrong"
280: + " authorization key (got " + key + ", expected "
281: + authKey + ")");
282: in.close();
283: client.close();
284:
285: return false;
286: } else {
287: // Reset the timeout
288: client.setSoTimeout(0);
289:
290: Log.log(Log.DEBUG, this , client + ": authenticated"
291: + " successfully");
292:
293: final String script = in.readUTF();
294: Log.log(Log.DEBUG, this , script);
295:
296: SwingUtilities.invokeLater(new Runnable() {
297: public void run() {
298: try {
299: NameSpace ns = new NameSpace(BeanShell
300: .getNameSpace(), "EditServer namespace");
301: ns.setVariable("socket", client);
302: BeanShell.eval(null, ns, script);
303: } catch (org.gjt.sp.jedit.bsh.UtilEvalError e) {
304: Log.log(Log.ERROR, this , e);
305: } finally {
306: try {
307: BeanShell.getNameSpace().setVariable(
308: "socket", null);
309: } catch (org.gjt.sp.jedit.bsh.UtilEvalError e) {
310: Log.log(Log.ERROR, this , e);
311: }
312: }
313: }
314: });
315:
316: return true;
317: }
318: } //}}}
319:
320: //}}}
321: }
|