1#!@PYTHON@
2# -*-python-*-
3# $Id$ 
4
5# This file is part of avahi.
6#
7# avahi is free software; you can redistribute it and/or modify it
8# under the terms of the GNU Lesser General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# avahi is distributed in the hope that it will be useful, but WITHOUT
13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15# License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with avahi; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20# USA.
21
22import sys, getopt, os
23
24try:
25    import avahi, gobject, dbus
26except ImportError:
27    print "Sorry, to use this tool you need to install Avahi and python-dbus."
28    sys.exit(1)
29
30try:
31    import dbus.glib
32except ImportError:
33    pass
34
35urlproto = { "_http._tcp" : "http",  "_https._tcp" : "https", "_ftp._tcp" : "ftp" }
36
37port = 8080
38address = "127.0.0.1"
39use_host_names = None
40use_CGI = None
41domain = "local"
42timeout = 3000
43
44class AvahiBookmarks:
45    services = {}
46
47    def __init__(self, use_host_names):
48
49        self.bus = dbus.SystemBus()
50        self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
51
52        self.version_string = self.server.GetVersionString()
53
54        self.browse_service_type("_http._tcp")
55        self.browse_service_type("_https._tcp")
56        self.browse_service_type("_ftp._tcp")
57
58        if use_host_names is None:
59            try: 
60                self.use_host_names = self.server.IsNSSSupportAvailable()
61            except:
62                self.use_host_names = False
63        else:
64            self.use_host_names = use_host_names
65
66    def browse_service_type(self, stype):
67
68        global domain, use_CGI
69
70        browser = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, stype, domain, dbus.UInt32(0))), avahi.DBUS_INTERFACE_SERVICE_BROWSER)
71        browser.connect_to_signal('ItemNew', self.new_service)
72        browser.connect_to_signal('ItemRemove', self.remove_service)
73        if use_CGI:
74            browser.connect_to_signal('AllForNow', self.all_for_now)
75
76    def find_path(self, txt):
77
78        l = avahi.txt_array_to_string_array(txt)
79
80        for k in l:
81            if k[:5] == "path=":
82                if k[5:].startswith("/"):
83                    return k[5:]
84                else:
85                    return "/" + k[5:]
86
87        return "/"
88
89    def render_html(self):
90
91        global domain
92
93        t = '<html><head><title>%s Zeroconf Bookmarks</title></head><body><h1>%s Zeroconf Bookmarks</h1>' % (domain, domain)
94
95        if len(self.services) == 0:
96            t += '<p>Sorry, no Zeroconf web services have been registered on the %s domain.</p>' % domain
97        else:
98            t += '<ul style="padding: 0px; margin: 20px; list-style-type: none">'
99
100            for k, v in self.services.iteritems():
101            
102                if v[3] == 80:
103                    port = ''
104                else:
105                    port = ':%i' % v[3]
106
107                path = self.find_path(v[4])
108                t += '<li><a href="%s://%s%s%s">%s</a></li>' % (urlproto[k[3]], v[2], port, path, k[2])
109                
110            t += '</ul>'
111        
112        t += '<hr noshade/><p style="font-size: 8; font-family: sans-serif">Served by %s</p></body></html>' % self.version_string
113
114        return str(t)
115
116
117    def new_service(self, interface, protocol, name, type, domain, flags):
118
119        interface, protocol, name, type, domain, host, aprotocol, address, port, txt, flags = self.server.ResolveService(interface, protocol, name, type, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0))
120
121        if self.use_host_names:
122            h = host
123        else:
124            if aprotocol == avahi.PROTO_INET6:
125                h = "[" + address + "]"
126            else:
127                h = address
128
129        self.services[(interface, protocol, name, type, domain)] = (host, aprotocol, h, port, txt)
130
131    def remove_service(self, interface, protocol, name, type, domain):
132
133        del self.services[(interface, protocol, name, type, domain)]
134
135
136    # Only reachable with use_CGI
137    def all_for_now(self):
138
139        mainloop.quit()
140
141def usage(retval = 0):
142
143    print "%s [options]\n" % sys.argv[0]
144    print "   -h --help             Show this help"
145    print "   -c --cgi              Run as a CGI instead of as a server (default to server"
146    print "                         unless environment variable GATEWAY_INTERFACE is set)"
147    print "   -t --timeout MS       Specify the max time for CGI browsing (default %u)" % timeout
148    print "   -p --port PORT        Specify the port to use (default %u)" % port
149    print "   -a --address ADDRESS  Specify the address to bind to (default %s)" % address
150    print "   -H --host-names       Show links with real hostnames"
151    print "   -A --addresses        Show links with numeric IP addresses"
152    print "   -d --domain DOMAIN    Specify the domain to browse" 
153    sys.exit(retval)
154
155try:
156    opts, args = getopt.getopt(sys.argv[1:], "hct:p:a:HAd:", ["help", "cgi", "port=", "timeout=", "address=", "host-names", "addresses", "domain="])
157except getopt.GetoptError:
158    usage(2)
159
160for o, a in opts:
161    if o in ("-h", "--help"):
162        usage()
163
164    if o in ("-c", "--cgi"):
165        use_CGI = True
166
167    if o in ("-t", "--timeout"):
168        timeout = int(a)
169
170    if o in ("-p", "--port"):
171        port = int(a)
172
173    if o in ("-a", "--address"):
174        address = a
175
176    if o in ("-H", "--host-names"):
177        use_host_names = True
178
179    if o in ("-A", "--addresses"):
180        use_host_names = False
181
182    if o in ("-d", "--domain"):
183        domain = a
184
185if use_CGI is None:
186    use_CGI = os.environ.has_key("GATEWAY_INTERFACE")
187
188if use_CGI:
189    cgi = AvahiBookmarks(use_host_names)
190
191    mainloop = gobject.MainLoop()
192    gobject.timeout_add(timeout, mainloop.quit)
193
194    try:
195        mainloop.run()
196    except KeyboardInterrupt:
197        pass
198        
199    print 'Content-type: text/html\n\n' + cgi.render_html()
200
201else:
202    try:
203        from twisted.internet import glib2reactor
204        glib2reactor.install()
205        from twisted.internet import reactor
206        from twisted.web import server, resource
207    except ImportError:
208        print "Sorry, to use this tool as a server you need to install twisted and twisted.web.\n"
209	sys.exit(1)
210
211    class AvahiBookmarksServer(AvahiBookmarks, resource.Resource):
212        isLeaf = True
213
214        def __init__(self, use_host_names):
215            resource.Resource.__init__(self)
216            AvahiBookmarks.__init__(self, use_host_names)
217
218        def render_GET(self, request):
219            return self.render_html()
220
221    site = server.Site(AvahiBookmarksServer(use_host_names))
222    reactor.listenTCP(port, site, interface=address)
223
224    print "Now point your web browser to http://%s:%u/!" % (address, port)
225
226    try:
227        reactor.run()
228    except KeyboardInterrupt:
229        pass
230