Source code: org/gjt/sp/jedit/EditServer.java
1 /*
2 * EditServer.java - jEdit server
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 1999, 2003 Slava Pestov
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 package org.gjt.sp.jedit;
24
25 //{{{ Imports
26 import bsh.NameSpace;
27 import javax.swing.SwingUtilities;
28 import java.io.*;
29 import java.net.*;
30 import java.util.Random;
31 import org.gjt.sp.jedit.io.FileVFS;
32 import org.gjt.sp.util.Log;
33 //}}}
34
35 /**
36 * Inter-process communication.<p>
37 *
38 * The edit server protocol is very simple. <code>$HOME/.jedit/server</code>
39 * is an ASCII file containing two lines, the first being the port number,
40 * the second being the authorization key.<p>
41 *
42 * You connect to that port on the local machine, sending the authorization
43 * key as four bytes in network byte order, followed by the length of the
44 * BeanShell script as two bytes in network byte order, followed by the
45 * script in UTF8 encoding. After the socked is closed, the BeanShell script
46 * will be executed by jEdit.<p>
47 *
48 * The snippet is executed in the AWT thread. None of the usual BeanShell
49 * variables (view, buffer, textArea, editPane) are set so the script has to
50 * figure things out by itself.<p>
51 *
52 * In most cases, the script will call the static
53 * {@link #handleClient(boolean,String,String[])} method, but of course more
54 * complicated stuff can be done too.
55 *
56 * @author Slava Pestov
57 * @version $Id: EditServer.java,v 1.19 2003/05/05 23:11:47 spestov Exp $
58 */
59 public class EditServer extends Thread
60 {
61 //{{{ EditServer constructor
62 EditServer(String portFile)
63 {
64 super("jEdit server daemon [" + portFile + "]");
65 setDaemon(true);
66 this.portFile = portFile;
67
68 try
69 {
70 // On Unix, set permissions of port file to rw-------,
71 // so that on broken Unices which give everyone read
72 // access to user home dirs, people can't see your
73 // port file (and hence send arbitriary BeanShell code
74 // your way. Nasty.)
75 if(OperatingSystem.isUnix())
76 {
77 new File(portFile).createNewFile();
78 FileVFS.setPermissions(portFile,0600);
79 }
80
81 // Bind to any port on localhost; accept 2 simultaneous
82 // connection attempts before rejecting connections
83 socket = new ServerSocket(0, 2,
84 InetAddress.getByName("127.0.0.1"));
85 authKey = Math.abs(new Random().nextInt());
86 int port = socket.getLocalPort();
87
88 FileWriter out = new FileWriter(portFile);
89 out.write("b\n");
90 out.write(String.valueOf(port));
91 out.write("\n");
92 out.write(String.valueOf(authKey));
93 out.write("\n");
94 out.close();
95
96 Log.log(Log.DEBUG,this,"jEdit server started on port "
97 + socket.getLocalPort());
98 Log.log(Log.DEBUG,this,"Authorization key is "
99 + authKey);
100
101 ok = true;
102 }
103 catch(IOException io)
104 {
105 /* on some Windows versions, connections to localhost
106 * fail if the network is not running. To avoid
107 * confusing newbies with weird error messages, log
108 * errors that occur while starting the server
109 * as NOTICE, not ERROR */
110 Log.log(Log.NOTICE,this,io);
111 }
112 } //}}}
113
114 //{{{ run() method
115 public void run()
116 {
117 for(;;)
118 {
119 if(abort)
120 return;
121
122 Socket client = null;
123 try
124 {
125 client = socket.accept();
126
127 // Stop script kiddies from opening the edit
128 // server port and just leaving it open, as a
129 // DoS
130 client.setSoTimeout(1000);
131
132 Log.log(Log.MESSAGE,this,client + ": connected");
133
134 DataInputStream in = new DataInputStream(
135 client.getInputStream());
136 OutputStream out = client.getOutputStream();
137
138 if(!handleClient(client,in))
139 abort = true;
140 }
141 catch(Exception e)
142 {
143 if(!abort)
144 Log.log(Log.ERROR,this,e);
145 abort = true;
146 }
147 finally
148 {
149 /* if(client != null)
150 {
151 try
152 {
153 client.close();
154 }
155 catch(Exception e)
156 {
157 Log.log(Log.ERROR,this,e);
158 }
159
160 client = null;
161 } */
162 }
163 }
164 } //}}}
165
166 //{{{ handleClient() method
167 /**
168 * @param restore Ignored unless no views are open
169 * @param parent The client's parent directory
170 * @param args A list of files. Null entries are ignored, for convinience
171 * @since jEdit 3.2pre7
172 */
173 public static void handleClient(boolean restore, String parent,
174 String[] args)
175 {
176 handleClient(restore,false,false,parent,args);
177 } //}}}
178
179 //{{{ handleClient() method
180 /**
181 * @param restore Ignored unless no views are open
182 * @param newView Open a new view?
183 * @param newPlainView Open a new plain view?
184 * @param parent The client's parent directory
185 * @param args A list of files. Null entries are ignored, for convinience
186 * @since jEdit 4.2pre1
187 */
188 public static Buffer handleClient(boolean restore,
189 boolean newView, boolean newPlainView, String parent,
190 String[] args)
191 {
192 // we have to deal with a huge range of possible border cases here.
193 if(jEdit.getFirstView() == null)
194 {
195 // coming out of background mode.
196 // no views open.
197 // no buffers open if args empty.
198
199 Buffer buffer = jEdit.openFiles(null,parent,args);
200
201 boolean restoreFiles = restore && jEdit.getBooleanProperty("restore")
202 && (buffer == null || jEdit.getBooleanProperty("restore.cli"));
203
204 View view = PerspectiveManager.loadPerspective(restoreFiles);
205
206 if(view == null)
207 {
208 if(buffer == null)
209 buffer = jEdit.getFirstBuffer();
210 view = jEdit.newView(null,buffer);
211 }
212 else if(buffer != null)
213 view.setBuffer(buffer);
214
215 return buffer;
216 }
217 else if(newPlainView)
218 {
219 // no background mode, and opening a new view
220 Buffer buffer = jEdit.openFiles(null,parent,args);
221 if(buffer == null)
222 buffer = jEdit.getFirstBuffer();
223 jEdit.newView(null,buffer,true);
224 return buffer;
225 }
226 else if(newView)
227 {
228 // no background mode, and opening a new view
229 Buffer buffer = jEdit.openFiles(null,parent,args);
230 if(buffer == null)
231 buffer = jEdit.getFirstBuffer();
232 jEdit.newView(jEdit.getActiveView(),buffer,false);
233 return buffer;
234 }
235 else
236 {
237 // no background mode, and reusing existing view
238 View view = jEdit.getActiveView();
239
240 Buffer buffer = jEdit.openFiles(view,parent,args);
241
242 // Hack done to fix bringing the window to the front.
243 // At least on windows, Frame.toFront() doesn't cut it.
244 // Remove the isWindows check if it's broken under other
245 // OSes too.
246 if (jEdit.getBooleanProperty("server.brokenToFront"))
247 view.setState(java.awt.Frame.ICONIFIED);
248
249 // un-iconify using JDK 1.3 API
250 view.setState(java.awt.Frame.NORMAL);
251 view.requestFocus();
252 view.toFront();
253
254 return buffer;
255 }
256 } //}}}
257
258 //{{{ isOK() method
259 boolean isOK()
260 {
261 return ok;
262 } //}}}
263
264 // stopServer() method
265 void stopServer()
266 {
267 abort = true;
268 try
269 {
270 socket.close();
271 }
272 catch(IOException io)
273 {
274 }
275
276 new File(portFile).delete();
277 } //}}}
278
279 //{{{ Private members
280
281 //{{{ Instance variables
282 private String portFile;
283 private ServerSocket socket;
284 private int authKey;
285 private boolean ok;
286 private boolean abort;
287 //}}}
288
289 //{{{ handleClient() method
290 private boolean handleClient(final Socket client, DataInputStream in)
291 throws Exception
292 {
293 int key = in.readInt();
294 if(key != authKey)
295 {
296 Log.log(Log.ERROR,this,client + ": wrong"
297 + " authorization key (got " + key
298 + ", expected " + authKey + ")");
299 in.close();
300 client.close();
301
302 return false;
303 }
304 else
305 {
306 // Reset the timeout
307 client.setSoTimeout(0);
308
309 Log.log(Log.DEBUG,this,client + ": authenticated"
310 + " successfully");
311
312 final String script = in.readUTF();
313 Log.log(Log.DEBUG,this,script);
314
315 SwingUtilities.invokeLater(new Runnable()
316 {
317 public void run()
318 {
319 try
320 {
321 NameSpace ns = new NameSpace(
322 BeanShell.getNameSpace(),
323 "EditServer namespace");
324 ns.setVariable("socket",client);
325 BeanShell.eval(null,ns,script);
326 }
327 catch(bsh.UtilEvalError e)
328 {
329 Log.log(Log.ERROR,this,e);
330 }
331 finally
332 {
333 try
334 {
335 BeanShell.getNameSpace().setVariable("socket",null);
336 }
337 catch(bsh.UtilEvalError e)
338 {
339 Log.log(Log.ERROR,this,e);
340 }
341 }
342 }
343 });
344
345 return true;
346 }
347 } //}}}
348
349 //}}}
350 }