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 # ============================================================================
80 # ::: End of configuration section :::::::::::::::::::::::::::::::::::::::::::
81 # ============================================================================
85 '''The uzbl cookie daemon class.'''
87 def __init__(self, cookie_socket, cookie_jar, daemon_timeout,\
90 self.cookie_socket = os.path.expandvars(cookie_socket)
91 self.server_socket = None
92 self.cookie_jar = os.path.expandvars(cookie_jar)
93 self.daemon_mode = daemon_mode
95 self.daemon_timeout = daemon_timeout
96 self.last_request = time.time()
100 '''Start the daemon.'''
106 # Register a function to cleanup on exit.
107 atexit.register(self.quit)
109 # Tell SIGTERM to act orderly.
110 signal(SIGTERM, lambda signum, stack_frame: sys.exit(1))
113 # Create cookie_socket
117 self.open_cookie_jar()
119 # Listen for GET and PULL cookie requests.
122 except KeyboardInterrupt:
133 def daemonize(function):
134 '''Daemonize the process using the Stevens' double-fork magic.'''
137 if os.fork(): os._exit(0)
140 sys.stderr.write("fork #1 failed: %s\n" % e)
148 if os.fork(): os._exit(0)
151 sys.stderr.write("fork #2 failed: %s\n" % e)
157 devnull = '/dev/null'
158 stdin = file(devnull, 'r')
159 stdout = file(devnull, 'a+')
160 stderr = file(devnull, 'a+', 0)
162 os.dup2(stdin.fileno(), sys.stdin.fileno())
163 os.dup2(stdout.fileno(), sys.stdout.fileno())
164 os.dup2(stderr.fileno(), sys.stderr.fileno())
167 def open_cookie_jar(self):
168 '''Open the cookie jar.'''
171 self.jar = cookielib.MozillaCookieJar(self.cookie_jar)
173 # Load cookies from the cookie_jar file.
174 self.jar.load(ignore_discard=True)
176 # Check cookie_jar only readable and writable by this user.
177 os.chmod(self.cookie_jar, 0600)
183 def create_socket(self):
184 '''Open socket AF_UNIX socket for uzbl instance <-> daemon
187 if os.path.exists(self.cookie_socket):
188 # Don't you just love racetrack conditions!
191 self.server_socket = socket.socket(socket.AF_UNIX,\
192 socket.SOCK_SEQPACKET)
194 self.server_socket.bind(self.cookie_socket)
196 # Only allow the current user to read and write to the socket.
197 os.chmod(self.cookie_socket, 0600)
201 '''Listen for incoming cookie PUT and GET requests.'''
204 # If you get broken pipe errors increase this listen number.
205 self.server_socket.listen(1)
207 if bool(select.select([self.server_socket],[],[],1)[0]):
208 client_socket, _ = self.server_socket.accept()
209 self.handle_request(client_socket)
210 self.last_request = time.time()
212 if self.daemon_timeout:
213 idle = time.time() - self.last_request
214 if idle > self.daemon_timeout: break
217 def handle_request(self, client_socket):
218 '''Connection made, now to serve a cookie PUT or GET request.'''
220 # Receive cookie request from client.
221 data = client_socket.recv(8192)
222 argv = data.split("\0")
225 print ' '.join(argv[:4])
229 uri = urllib2.urlparse.ParseResult(
235 fragment='').geturl()
237 req = urllib2.Request(uri)
240 self.jar.add_cookie_header(req)
241 if req.has_header('Cookie'):
242 cookie = req.get_header('Cookie')
243 client_socket.send(cookie)
247 client_socket.send("\0")
249 elif action == "PUT":
257 hdr = urllib2.httplib.HTTPMessage(\
258 StringIO.StringIO('Set-Cookie: %s' % set_cookie))
259 res = urllib2.addinfourl(StringIO.StringIO(), hdr,\
261 self.jar.extract_cookies(res,req)
262 self.jar.save(ignore_discard=True)
266 client_socket.close()
269 def quit(self, *args):
270 '''Called on exit to make sure all loose ends are tied up.'''
272 # Only one loose end so far.
278 def del_socket(self):
279 '''Remove the cookie_socket file on exit. In a way the cookie_socket
280 is the daemons pid file equivalent.'''
282 if self.server_socket:
283 self.server_socket.close()
285 if os.path.exists(self.cookie_socket):
286 os.remove(self.cookie_socket)
289 if __name__ == "__main__":
291 if os.path.exists(cookie_socket):
292 print "Error: cookie socket already exists: %r" % cookie_socket
295 CookieMonster(cookie_socket, cookie_jar, daemon_timeout,\