3 # Uzbl tabbing wrapper using a fifo socket interface
4 # Copyright (c) 2009, Tom Adams <tom@holizz.com>
5 # Copyright (c) 2009, Dieter Plaetinck <dieter AT plaetinck.be>
6 # Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com>
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
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.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # - Setup some option parsing so the daemon can take optional command line
35 from signal import signal, SIGTERM
38 import cStringIO as StringIO
44 # ============================================================================
45 # ::: Default configuration section ::::::::::::::::::::::::::::::::::::::::::
46 # ============================================================================
48 # Location of the uzbl cache directory.
49 if 'XDG_CACHE_HOME' in os.environ.keys() and os.environ['XDG_CACHE_HOME']:
50 cache_dir = os.path.join(os.environ['XDG_CACHE_HOME'], 'uzbl/')
53 cache_dir = os.path.join(os.environ['HOME'], '.cache/uzbl/')
55 # Location of the uzbl data directory.
56 if 'XDG_DATA_HOME' in os.environ.keys() and os.environ['XDG_DATA_HOME']:
57 data_dir = os.path.join(os.environ['XDG_DATA_HOME'], 'uzbl/')
60 data_dir = os.path.join(os.environ['HOME'], '.local/share/uzbl/')
62 # Create cache dir and data dir if they are missing.
63 for path in [data_dir, cache_dir]:
64 if not os.path.exists(path):
68 cookie_socket = os.path.join(cache_dir, 'cookie_daemon_socket')
69 cookie_jar = os.path.join(data_dir, 'cookies.txt')
71 # Time out after x seconds of inactivity (set to 0 for never time out).
72 # Set to 0 by default until talk_to_socket is doing the spawning.
75 # Enable/disable daemonizing the process (useful when debugging).
76 # Set to False by default until talk_to_socket is doing the spawning.
79 # Set true to print helpful debugging messages to the terminal.
82 # ============================================================================
83 # ::: End of configuration section :::::::::::::::::::::::::::::::::::::::::::
84 # ============================================================================
87 _scriptname = os.path.basename(sys.argv[0])
90 print "%s: %s" % (_scriptname, msg)
94 '''The uzbl cookie daemon class.'''
96 def __init__(self, cookie_socket, cookie_jar, daemon_timeout,\
99 self.cookie_socket = os.path.expandvars(cookie_socket)
100 self.server_socket = None
101 self.cookie_jar = os.path.expandvars(cookie_jar)
102 self.daemon_mode = daemon_mode
104 self.daemon_timeout = daemon_timeout
105 self.last_request = time.time()
109 '''Start the daemon.'''
111 # Check if another daemon is running. The reclaim_socket function will
112 # exit if another daemon is detected listening on the cookie socket
113 # and remove the abandoned socket if there isnt.
114 if os.path.exists(self.cookie_socket):
115 self.reclaim_socket()
119 echo("entering daemon mode.")
122 # Register a function to cleanup on exit.
123 atexit.register(self.quit)
125 # Make SIGTERM act orderly.
126 signal(SIGTERM, lambda signum, stack_frame: sys.exit(1))
128 # Create cookie daemon socket.
131 # Create cookie jar object from file.
132 self.open_cookie_jar()
135 # Listen for incoming cookie puts/gets.
138 except KeyboardInterrupt:
149 def reclaim_socket(self):
150 '''Check if another process (hopefully a cookie_daemon.py) is listening
151 on the cookie daemon socket. If another process is found to be
152 listening on the socket exit the daemon immediately and leave the
153 socket alone. If the connect fails assume the socket has been abandoned
154 and delete it (to be re-created in the create socket function).'''
157 sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
158 sock.connect(self.cookie_socket)
162 # Failed to connect to cookie_socket so assume it has been
163 # abandoned by another cookie daemon process.
164 echo("reclaiming abandoned cookie_socket %r." % self.cookie_socket)
165 if os.path.exists(self.cookie_socket):
166 os.remove(self.cookie_socket)
170 echo("detected another process listening on %r." % self.cookie_socket)
172 # Use os._exit() to avoid tripping the atexit cleanup function.
176 def daemonize(function):
177 '''Daemonize the process using the Stevens' double-fork magic.'''
180 if os.fork(): os._exit(0)
183 sys.stderr.write("fork #1 failed: %s\n" % e)
191 if os.fork(): os._exit(0)
194 sys.stderr.write("fork #2 failed: %s\n" % e)
200 devnull = '/dev/null'
201 stdin = file(devnull, 'r')
202 stdout = file(devnull, 'a+')
203 stderr = file(devnull, 'a+', 0)
205 os.dup2(stdin.fileno(), sys.stdin.fileno())
206 os.dup2(stdout.fileno(), sys.stdout.fileno())
207 os.dup2(stderr.fileno(), sys.stderr.fileno())
210 def open_cookie_jar(self):
211 '''Open the cookie jar.'''
213 # Create cookie jar object from file.
214 self.jar = cookielib.MozillaCookieJar(self.cookie_jar)
217 # Attempt to load cookies from the cookie jar.
218 self.jar.load(ignore_discard=True)
220 # Ensure restrictive permissions are set on the cookie jar
221 # to prevent other users on the system from hi-jacking your
222 # authenticated sessions simply by copying your cookie jar.
223 os.chmod(self.cookie_jar, 0600)
229 def create_socket(self):
230 '''Create AF_UNIX socket for interprocess uzbl instance <-> cookie
231 daemon communication.'''
233 self.server_socket = socket.socket(socket.AF_UNIX,\
234 socket.SOCK_SEQPACKET)
236 if os.path.exists(self.cookie_socket):
237 # Accounting for super-rare super-fast racetrack condition.
238 self.reclaim_socket()
240 self.server_socket.bind(self.cookie_socket)
242 # Set restrictive permissions on the cookie socket to prevent other
243 # users on the system from data-mining your cookies.
244 os.chmod(self.cookie_socket, 0600)
248 '''Listen for incoming cookie PUT and GET requests.'''
251 # This line tells the socket how many pending incoming connections
252 # to enqueue. I haven't had any broken pipe errors so far while
253 # using the non-obvious value of 1 under heavy load conditions.
254 self.server_socket.listen(1)
256 if bool(select.select([self.server_socket],[],[],1)[0]):
257 client_socket, _ = self.server_socket.accept()
258 self.handle_request(client_socket)
259 self.last_request = time.time()
261 if self.daemon_timeout:
262 idle = time.time() - self.last_request
263 if idle > self.daemon_timeout: break
266 def handle_request(self, client_socket):
267 '''Connection made, now to serve a cookie PUT or GET request.'''
269 # Receive cookie request from client.
270 data = client_socket.recv(8192)
273 # Cookie argument list in packet is null separated.
274 argv = data.split("\0")
276 # Determine whether or not to print cookie data to terminal.
277 print_cookie = (verbose and not self.daemon_mode)
278 if print_cookie: print ' '.join(argv[:4])
282 uri = urllib2.urlparse.ParseResult(
288 fragment='').geturl()
290 req = urllib2.Request(uri)
293 self.jar.add_cookie_header(req)
294 if req.has_header('Cookie'):
295 cookie = req.get_header('Cookie')
296 client_socket.send(cookie)
297 if print_cookie: print cookie
300 client_socket.send("\0")
302 elif action == "PUT":
305 if print_cookie: print set_cookie
310 hdr = urllib2.httplib.HTTPMessage(\
311 StringIO.StringIO('Set-Cookie: %s' % set_cookie))
312 res = urllib2.addinfourl(StringIO.StringIO(), hdr,\
314 self.jar.extract_cookies(res,req)
315 self.jar.save(ignore_discard=True)
317 if print_cookie: print
319 client_socket.close()
322 def quit(self, *args):
323 '''Called on exit to make sure all loose ends are tied up.'''
325 # Only one loose end so far.
331 def del_socket(self):
332 '''Remove the cookie_socket file on exit. In a way the cookie_socket
333 is the daemons pid file equivalent.'''
335 if self.server_socket:
336 self.server_socket.close()
338 if os.path.exists(self.cookie_socket):
339 os.remove(self.cookie_socket)
342 if __name__ == "__main__":
344 CookieMonster(cookie_socket, cookie_jar, daemon_timeout,\