80b421f78d0efd4c920d109298493a0d14d8fb75
[uzbl-mobile] / examples / data / uzbl / scripts / cookie_daemon.py
1 #!/usr/bin/env python
2
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>
7 #
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.
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, see <http://www.gnu.org/licenses/>.
20
21
22 import cookielib
23 import os
24 import sys
25 import urllib2
26 import select
27 import socket
28 import time
29
30 try:
31     import cStringIO as StringIO
32
33 except ImportError:
34     import StringIO
35
36
37 # ============================================================================
38 # ::: Default configuration section ::::::::::::::::::::::::::::::::::::::::::
39 # ============================================================================
40
41 # Location of the uzbl cache directory.
42 if 'XDG_CACHE_HOME' in os.environ.keys() and os.environ['XDG_CACHE_HOME']:
43     cache_dir = os.path.join(os.environ['XDG_CACHE_HOME'], 'uzbl/')
44
45 else:
46     cache_dir = os.path.join(os.environ['HOME'], '.cache/uzbl/')
47
48 # Location of the uzbl data directory.
49 if 'XDG_DATA_HOME' in os.environ.keys() and os.environ['XDG_DATA_HOME']:
50     data_dir = os.path.join(os.environ['XDG_DATA_HOME'], 'uzbl/')
51
52 else:
53     data_dir = os.path.join(os.environ['HOME'], '.local/share/uzbl/')
54
55 # Create cache dir and data dir if they are missing.
56 for path in [data_dir, cache_dir]:
57     if not os.path.exists(path):
58         os.makedirs(path) 
59
60 # Default config
61 cookie_socket = os.path.join(cache_dir, 'cookie_daemon_socket')
62 cookie_jar = os.path.join(data_dir, 'cookies.txt')
63 #daemon_timeout = 360
64 daemon_timeout = 0
65
66 # ============================================================================
67 # ::: End of configuration section :::::::::::::::::::::::::::::::::::::::::::
68 # ============================================================================
69
70
71 class CookieMonster:
72     '''The uzbl cookie daemon class.'''
73
74     def __init__(self, cookie_socket, cookie_jar, daemon_timeout):
75
76         self.cookie_socket = os.path.expandvars(cookie_socket)
77         self.server_socket = None
78         self.cookie_jar = os.path.expandvars(cookie_jar)
79         self.jar = None
80         self.daemon_timeout = daemon_timeout
81         self.last_request = time.time()
82
83     
84     def run(self):
85         '''Start the daemon.'''
86         
87         try:
88             # Daemonize process. 
89             #self.daemonize()
90         
91             # Create cookie_socket 
92             self.create_socket()
93         
94             # Create jar object
95             self.open_cookie_jar()
96
97             # Listen for GET and PULL cookie requests.
98             self.listen()
99
100         except:
101             #raise
102             print "%r" % sys.exc_info()[1]
103         
104         self.quit()
105
106
107     def daemonize(function):
108         '''Daemonize the process using the Stevens' double-fork magic.'''
109
110         try:
111             if os.fork(): sys.exit(0)
112
113         except OSError, e:
114             sys.stderr.write("fork #1 failed: %s\n" % e)
115             sys.exit(1)
116         
117         os.chdir('/')
118         os.setsid()
119         os.umask(0)
120         
121         try:
122             if os.fork(): sys.exit(0)
123
124         except OSError, e:
125             sys.stderr.write("fork #2 failed: %s\n" % e)
126             sys.exit(1)
127         
128         sys.stdout.flush()
129         sys.stderr.flush()
130
131         devnull = '/dev/null'
132         stdin = file(devnull, 'r')
133         stdout = file(devnull, 'a+')
134         stderr = file(devnull, 'a+', 0)
135
136         os.dup2(stdin.fileno(), sys.stdin.fileno())
137         os.dup2(stdout.fileno(), sys.stdout.fileno())
138         os.dup2(stderr.fileno(), sys.stderr.fileno())
139         
140
141     def open_cookie_jar(self):
142         '''Open the cookie jar.'''
143         
144         # Open cookie jar.
145         self.jar = cookielib.MozillaCookieJar(cookie_jar)
146         try:
147             self.jar.load(ignore_discard=True)
148
149         except:
150             pass
151
152
153     def create_socket(self):
154         '''Open socket AF_UNIX socket for uzbl instance <-> daemon
155         communication.'''
156     
157         if os.path.exists(self.cookie_socket):
158             # Don't you just love racetrack conditions! 
159             sys.exit(1)
160             
161         self.server_socket = socket.socket(socket.AF_UNIX,\
162           socket.SOCK_SEQPACKET)
163
164         self.server_socket.bind(self.cookie_socket)
165
166
167     def listen(self):
168         '''Listen for incoming cookie PUT and GET requests.'''
169
170         while True:
171             # If you get broken pipe errors increase this listen number.
172             self.server_socket.listen(1)
173
174             if bool(select.select([self.server_socket],[],[],1)[0]):
175                 client_socket, _ = self.server_socket.accept()
176                 self.handle_request(client_socket)
177                 self.last_request = time.time()
178             
179             if self.daemon_timeout:
180                 idle = time.time() - self.last_request
181                 if idle > self.daemon_timeout: break
182         
183
184     def handle_request(self, client_socket):
185         '''Connection made, now to serve a cookie PUT or GET request.'''
186          
187         # Receive full request from client.
188         data = client_socket.recv(4096)
189
190         argv = data.split("\0")
191         
192         # For debugging:
193         #print argv
194
195         action = argv[0]
196         set_cookie = argv[4] if len(argv) > 3 else None
197         uri = urllib2.urlparse.ParseResult(
198           scheme=argv[1],
199           netloc=argv[2],
200           path=argv[3],
201           params='',
202           query='',
203           fragment='').geturl()
204         
205         req = urllib2.Request(uri)
206
207         if action == "GET":
208             self.jar.add_cookie_header(req)
209             if req.has_header('Cookie'):
210                 client_socket.send(req.get_header('Cookie'))
211
212             else:
213                 client_socket.send("\0")
214
215         elif action == "PUT":
216             hdr = urllib2.httplib.HTTPMessage(\
217               StringIO.StringIO('Set-Cookie: %s' % set_cookie))
218             res = urllib2.addinfourl(StringIO.StringIO(), hdr,\
219               req.get_full_url())
220             self.jar.extract_cookies(res,req)
221             self.jar.save(ignore_discard=True)
222             
223         client_socket.close()
224
225
226     def quit(self):
227         '''Called on exit to make sure all loose ends are tied up.'''
228         
229         # Only one loose end so far.
230         self.del_socket()
231         
232         # And die gracefully.
233         sys.exit(0)
234     
235
236     def del_socket(self):
237         '''Remove the cookie_socket file on exit. In a way the cookie_socket 
238         is the daemons pid file equivalent.'''
239     
240         if self.server_socket:
241             self.server_socket.close()
242
243         if os.path.exists(self.cookie_socket):
244             os.remove(self.cookie_socket)
245
246
247 if __name__ == "__main__":
248     
249     if os.path.exists(cookie_socket):
250         print "Error: cookie socket already exists: %r" % cookie_socket
251         sys.exit(1)
252     
253     CookieMonster(cookie_socket, cookie_jar, daemon_timeout).run()
254