#!/usr/bin/env python # $Id: standalone.py,v 1.36 2005/02/03 13:08:59 rey4 Exp $ # vim:sw=4:ts=4:et:nowrap # [Emacs: -*- python -*-] # # Copyright (C) 1999-2002 The ViewCVS Group. All Rights Reserved. # # By using this file, you agree to the terms and conditions set forth in # the LICENSE.html file which can be found at the top level of the ViewCVS # distribution or at http://viewcvs.sourceforge.net/license-1.html. # # Contact information: # This file: Peter Funk, Oldenburger Str.86, 27777 Ganderkesee, Germany # ViewCVS project: Greg Stein, PO Box 760, Palo Alto, CA, 94302 # gstein@lyra.org, http://viewcvs.sourceforge.net/ # # Note: this module is designed to deploy instantly and run under any # version of Python from 1.5 and up. That's why some 2.0 features # (like string methods) are conspicuously avoided. # XXX Security issues? """Run "standalone.py -p " to start an HTTP server on a given port on the local machine to generate ViewCVS web pages. """ __author__ = "Peter Funk " __date__ = "11 November 2001" __version__ = "$Revision: 1.36 $" __credits__ = """Guido van Rossum, for an excellent programming language. Greg Stein, for writing ViewCVS in the first place. Ka-Ping Yee, for the GUI code and the framework stolen from pydoc.py. """ # INSTALL-TIME CONFIGURATION # # This value will be set during the installation process. During # development, it will remain None. # LIBRARY_DIR = None import sys import os import stat import string import urllib import rfc822 import socket import select import BaseHTTPServer if LIBRARY_DIR: sys.path.insert(0, LIBRARY_DIR) else: sys.path[:0] = ['lib'] import sapi import viewcvs import compat; compat.for_standalone() if viewcvs.CONF_PATHNAME is None: viewcvs.g_install_dir = '' class Options: port = 7467 # default TCP/IP port used for the server start_gui = 0 # No GUI unless requested. repositories = {} # use default repositories specified in config if sys.platform == 'mac': host = '127.0.0.1' else: host = 'localhost' # --- web browser interface: ---------------------------------------------- class StandaloneServer(sapi.CgiServer): def __init__(self, handler): sapi.CgiServer.__init__(self, inheritableOut = sys.platform != "win32") self.handler = handler def header(self, content_type='text/html', status=None): if not self.headerSent: self.headerSent = 1 if status is None: statusCode = 200 statusText = 'OK' else: p = string.find(status, ' ') if p < 0: statusCode = int(status) statusText = '' else: statusCode = int(status[:p]) statusText = status[p+1:] self.handler.send_response(statusCode, statusText) self.handler.send_header("Content-type", content_type) for (name, value) in self.headers: self.handler.send_header(name, value) self.handler.end_headers() def serve(host, port, callback=None): """start a HTTP server on the given port. call 'callback' when the server is ready to serve""" class ViewCVS_Handler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): """Serve a GET request.""" if not self.path or self.path == "/": self.redirect() elif self.is_viewcvs(): try: self.run_viewcvs() except IOError: # ignore IOError: [Errno 32] Broken pipe pass else: self.send_error(404) def do_POST(self): """Serve a POST request.""" if self.is_viewcvs(): self.run_viewcvs() else: self.send_error(501, "Can only POST to viewcvs") def is_viewcvs(self): """Check whether self.path matches the hardcoded ScriptAlias /viewcvs""" if self.path[:8] == "/viewcvs": return 1 return 0 def redirect(self): """redirect the browser to the viewcvs URL""" self.send_response(301, "moved (redirection follows)") self.send_header("Content-type", "text/html") self.send_header("Location", self.server.url + 'viewcvs/') self.end_headers() self.wfile.write("""

Redirection to ViewCVS

Wait a second. You will be automatically redirected to ViewCVS. If this doesn't work, please click on the link above. """ % tuple([self.server.url + "viewcvs/"]*2)) def run_viewcvs(self): """This is a quick and dirty cut'n'rape from Pythons standard library module CGIHTTPServer.""" scriptname = "/viewcvs" assert self.path[:8] == scriptname viewcvs_url, rest = self.server.url[:-1]+scriptname, self.path[8:] i = string.rfind(rest, '?') if i >= 0: rest, query = rest[:i], rest[i+1:] else: query = '' # sys.stderr.write("Debug: '"+scriptname+"' '"+rest+"' '"+query+"'\n") env = os.environ # Since we're going to modify the env in the parent, provide empty # values to override previously set values for k in env.keys(): if k[:5] == 'HTTP_': del env[k] for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH', 'HTTP_USER_AGENT', 'HTTP_COOKIE'): if env.has_key(k): env[k] = "" # XXX Much of the following could be prepared ahead of time! env['SERVER_SOFTWARE'] = self.version_string() env['SERVER_NAME'] = self.server.server_name env['GATEWAY_INTERFACE'] = 'CGI/1.1' env['SERVER_PROTOCOL'] = self.protocol_version env['SERVER_PORT'] = str(self.server.server_port) env['REQUEST_METHOD'] = self.command uqrest = urllib.unquote(rest) env['PATH_INFO'] = uqrest env['SCRIPT_NAME'] = scriptname if query: env['QUERY_STRING'] = query host = self.address_string() if host != self.client_address[0]: env['REMOTE_HOST'] = host env['REMOTE_ADDR'] = self.client_address[0] # AUTH_TYPE # REMOTE_USER # REMOTE_IDENT if self.headers.typeheader is None: env['CONTENT_TYPE'] = self.headers.type else: env['CONTENT_TYPE'] = self.headers.typeheader length = self.headers.getheader('content-length') if length: env['CONTENT_LENGTH'] = length accept = [] for line in self.headers.getallmatchingheaders('accept'): if line[:1] in string.whitespace: accept.append(string.strip(line)) else: accept = accept + string.split(line[7:], ',') env['HTTP_ACCEPT'] = string.joinfields(accept, ',') ua = self.headers.getheader('user-agent') if ua: env['HTTP_USER_AGENT'] = ua modified = self.headers.getheader('if-modified-since') if modified: env['HTTP_IF_MODIFIED_SINCE'] = modified etag = self.headers.getheader('if-none-match') if etag: env['HTTP_IF_NONE_MATCH'] = etag # XXX Other HTTP_* headers decoded_query = string.replace(query, '+', ' ') # Preserve state, because we execute script in current process: save_argv = sys.argv save_stdin = sys.stdin save_stdout = sys.stdout save_stderr = sys.stderr # For external tools like enscript we also need to redirect # the real stdout file descriptor. (On windows, reassigning the # sys.stdout variable is sufficient because pipe_cmds makes it # the standard output for child processes.) if sys.platform != "win32": save_realstdout = os.dup(1) try: try: sys.stdout = self.wfile if sys.platform != "win32": os.dup2(self.wfile.fileno(), 1) sys.stdin = self.rfile viewcvs.main(StandaloneServer(self)) finally: sys.argv = save_argv sys.stdin = save_stdin sys.stdout.flush() if sys.platform != "win32": os.dup2(save_realstdout, 1) os.close(save_realstdout) sys.stdout = save_stdout sys.stderr = save_stderr except SystemExit, status: self.log_error("ViewCVS exit status %s", str(status)) else: self.log_error("ViewCVS exited ok") class ViewCVS_Server(BaseHTTPServer.HTTPServer): def __init__(self, host, port, callback): self.address = (host, port) self.url = 'http://%s:%d/' % (host, port) self.callback = callback BaseHTTPServer.HTTPServer.__init__(self, self.address, self.handler) def serve_until_quit(self): self.quit = 0 while not self.quit: rd, wr, ex = select.select([self.socket.fileno()], [], [], 1) if rd: self.handle_request() def server_activate(self): BaseHTTPServer.HTTPServer.server_activate(self) if self.callback: self.callback(self) def server_bind(self): # set SO_REUSEADDR (if available on this platform) if hasattr(socket, 'SOL_SOCKET') \ and hasattr(socket, 'SO_REUSEADDR'): self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) BaseHTTPServer.HTTPServer.server_bind(self) ViewCVS_Server.handler = ViewCVS_Handler try: # XXX Move this code out of this function. # Early loading of configuration here. Used to # allow tinkering with some configuration settings: viewcvs.handle_config() if options.repositories: viewcvs.cfg.general.default_root = "Development" viewcvs.cfg.general.cvs_roots.update(options.repositories) elif viewcvs.cfg.general.cvs_roots.has_key("Development") and \ not os.path.isdir(viewcvs.cfg.general.cvs_roots["Development"]): sys.stderr.write("*** No repository found. Please use the -r option.\n") sys.stderr.write(" Use --help for more info.\n") raise KeyboardInterrupt # Hack! os.close(0) # To avoid problems with shell job control # always use default docroot location viewcvs.cfg.options.docroot = None # if cvsnt isn't found, fall back to rcs if (viewcvs.cfg.conf_path is None and viewcvs.cfg.general.cvsnt_exe_path): import popen cvsnt_works = 0 try: fp = popen.popen(viewcvs.cfg.general.cvsnt_exe_path, ['--version'], 'rt') try: while 1: line = fp.readline() if not line: break if string.find(line, "Concurrent Versions System (CVSNT)")>=0: cvsnt_works = 1 while fp.read(4096): pass break finally: fp.close() except: pass if not cvsnt_works: viewcvs.cfg.cvsnt_exe_path = None ViewCVS_Server(host, port, callback).serve_until_quit() except (KeyboardInterrupt, select.error): pass print 'server stopped' # --- graphical interface: -------------------------------------------------- def nogui(missing_module): sys.stderr.write( "Sorry! Your Python was compiled without the %s module"%missing_module+ " enabled.\nI'm unable to run the GUI part. Please omit the '-g'\n"+ "and '--gui' options or install another Python interpreter.\n") raise SystemExit, 1 def gui(host, port): """Graphical interface (starts web server and pops up a control window).""" class GUI: def __init__(self, window, host, port): self.window = window self.server = None self.scanner = None try: import Tkinter except ImportError: nogui("Tkinter") self.server_frm = Tkinter.Frame(window) self.title_lbl = Tkinter.Label(self.server_frm, text='Starting server...\n ') self.open_btn = Tkinter.Button(self.server_frm, text='open browser', command=self.open, state='disabled') self.quit_btn = Tkinter.Button(self.server_frm, text='quit serving', command=self.quit, state='disabled') self.window.title('ViewCVS standalone') self.window.protocol('WM_DELETE_WINDOW', self.quit) self.title_lbl.pack(side='top', fill='x') self.open_btn.pack(side='left', fill='x', expand=1) self.quit_btn.pack(side='right', fill='x', expand=1) # Early loading of configuration here. Used to # allow tinkering with configuration settings through the gui: viewcvs.handle_config() if not LIBRARY_DIR: viewcvs.cfg.options.cvsgraph_conf = "../cgi/cvsgraph.conf.dist" self.options_frm = Tkinter.Frame(window) # cvsgraph toggle: self.cvsgraph_ivar = Tkinter.IntVar() self.cvsgraph_ivar.set(viewcvs.cfg.options.use_cvsgraph) self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm, text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar, command=self.toggle_use_cvsgraph) self.cvsgraph_toggle.pack(side='top', anchor='w') # enscript toggle: self.enscript_ivar = Tkinter.IntVar() self.enscript_ivar.set(viewcvs.cfg.options.use_enscript) self.enscript_toggle = Tkinter.Checkbutton(self.options_frm, text="enable enscript (needs binary)", var=self.enscript_ivar, command=self.toggle_use_enscript) self.enscript_toggle.pack(side='top', anchor='w') # show_subdir_lastmod toggle: self.subdirmod_ivar = Tkinter.IntVar() self.subdirmod_ivar.set(viewcvs.cfg.options.show_subdir_lastmod) self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm, text="show subdir last mod (dir view)", var=self.subdirmod_ivar, command=self.toggle_subdirmod) self.subdirmod_toggle.pack(side='top', anchor='w') # use_re_search toggle: self.useresearch_ivar = Tkinter.IntVar() self.useresearch_ivar.set(viewcvs.cfg.options.use_re_search) self.useresearch_toggle = Tkinter.Checkbutton(self.options_frm, text="allow regular expr search", var=self.useresearch_ivar, command=self.toggle_useresearch) self.useresearch_toggle.pack(side='top', anchor='w') # use_localtime toggle: self.use_localtime_ivar = Tkinter.IntVar() self.use_localtime_ivar.set(viewcvs.cfg.options.use_localtime) self.use_localtime_toggle = Tkinter.Checkbutton(self.options_frm, text="use localtime (instead of UTC)", var=self.use_localtime_ivar, command=self.toggle_use_localtime) self.use_localtime_toggle.pack(side='top', anchor='w') # use_pagesize integer var: self.usepagesize_lbl = Tkinter.Label(self.options_frm, text='Paging (number of items per page, 0 disables):') self.usepagesize_lbl.pack(side='top', anchor='w') self.use_pagesize_ivar = Tkinter.IntVar() self.use_pagesize_ivar.set(viewcvs.cfg.options.use_pagesize) self.use_pagesize_entry = Tkinter.Entry(self.options_frm, width=10, textvariable=self.use_pagesize_ivar) self.use_pagesize_entry.bind('', self.set_use_pagesize) self.use_pagesize_entry.pack(side='top', anchor='w') # directory view template: self.dirtemplate_lbl = Tkinter.Label(self.options_frm, text='Chooose HTML Template for the Directory pages:') self.dirtemplate_lbl.pack(side='top', anchor='w') self.dirtemplate_svar = Tkinter.StringVar() self.dirtemplate_svar.set(viewcvs.cfg.templates.directory) self.dirtemplate_entry = Tkinter.Entry(self.options_frm, width = 40, textvariable=self.dirtemplate_svar) self.dirtemplate_entry.bind('', self.set_templates_directory) self.dirtemplate_entry.pack(side='top', anchor='w') self.templates_dir = Tkinter.Radiobutton(self.options_frm, text="directory.ezt", value="templates/directory.ezt", var=self.dirtemplate_svar, command=self.set_templates_directory) self.templates_dir.pack(side='top', anchor='w') self.templates_dir_alt = Tkinter.Radiobutton(self.options_frm, text="dir_alternate.ezt", value="templates/dir_alternate.ezt", var=self.dirtemplate_svar, command=self.set_templates_directory) self.templates_dir_alt.pack(side='top', anchor='w') # log view template: self.logtemplate_lbl = Tkinter.Label(self.options_frm, text='Chooose HTML Template for the Log pages:') self.logtemplate_lbl.pack(side='top', anchor='w') self.logtemplate_svar = Tkinter.StringVar() self.logtemplate_svar.set(viewcvs.cfg.templates.log) self.logtemplate_entry = Tkinter.Entry(self.options_frm, width = 40, textvariable=self.logtemplate_svar) self.logtemplate_entry.bind('', self.set_templates_log) self.logtemplate_entry.pack(side='top', anchor='w') self.templates_log = Tkinter.Radiobutton(self.options_frm, text="log.ezt", value="templates/log.ezt", var=self.logtemplate_svar, command=self.set_templates_log) self.templates_log.pack(side='top', anchor='w') self.templates_log_table = Tkinter.Radiobutton(self.options_frm, text="log_table.ezt", value="templates/log_table.ezt", var=self.logtemplate_svar, command=self.set_templates_log) self.templates_log_table.pack(side='top', anchor='w') # query view template: self.querytemplate_lbl = Tkinter.Label(self.options_frm, text='Template for the database query page:') self.querytemplate_lbl.pack(side='top', anchor='w') self.querytemplate_svar = Tkinter.StringVar() self.querytemplate_svar.set(viewcvs.cfg.templates.query) self.querytemplate_entry = Tkinter.Entry(self.options_frm, width = 40, textvariable=self.querytemplate_svar) self.querytemplate_entry.bind('', self.set_templates_query) self.querytemplate_entry.pack(side='top', anchor='w') self.templates_query = Tkinter.Radiobutton(self.options_frm, text="query.ezt", value="templates/query.ezt", var=self.querytemplate_svar, command=self.set_templates_query) self.templates_query.pack(side='top', anchor='w') # pack and set window manager hints: self.server_frm.pack(side='top', fill='x') self.options_frm.pack(side='top', fill='x') self.window.update() self.minwidth = self.window.winfo_width() self.minheight = self.window.winfo_height() self.expanded = 0 self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight)) self.window.wm_minsize(self.minwidth, self.minheight) try: import threading except ImportError: nogui("thread") threading.Thread(target=serve, args=(host, port, self.ready)).start() def toggle_use_cvsgraph(self, event=None): viewcvs.cfg.options.use_cvsgraph = self.cvsgraph_ivar.get() def toggle_use_enscript(self, event=None): viewcvs.cfg.options.use_enscript = self.enscript_ivar.get() def toggle_use_localtime(self, event=None): viewcvs.cfg.options.use_localtime = self.use_localtime_ivar.get() def toggle_subdirmod(self, event=None): viewcvs.cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get() def toggle_useresearch(self, event=None): viewcvs.cfg.options.use_re_search = self.useresearch_ivar.get() def set_use_pagesize(self, event=None): viewcvs.cfg.options.use_pagesize = self.use_pagesize_ivar.get() def set_templates_log(self, event=None): viewcvs.cfg.templates.log = self.logtemplate_svar.get() def set_templates_directory(self, event=None): viewcvs.cfg.templates.directory = self.dirtemplate_svar.get() def set_templates_query(self, event=None): viewcvs.cfg.templates.query = self.querytemplate_svar.get() def ready(self, server): """used as callback parameter to the serve() function""" self.server = server self.title_lbl.config( text='ViewCVS standalone server at\n' + server.url) self.open_btn.config(state='normal') self.quit_btn.config(state='normal') def open(self, event=None, url=None): """opens a browser window on the local machine""" url = url or self.server.url try: import webbrowser webbrowser.open(url) except ImportError: # pre-webbrowser.py compatibility if sys.platform == 'win32': os.system('start "%s"' % url) elif sys.platform == 'mac': try: import ic ic.launchurl(url) except ImportError: pass else: rc = os.system('netscape -remote "openURL(%s)" &' % url) if rc: os.system('netscape "%s" &' % url) def quit(self, event=None): if self.server: self.server.quit = 1 self.window.quit() import Tkinter try: gui = GUI(Tkinter.Tk(), host, port) Tkinter.mainloop() except KeyboardInterrupt: pass # --- command-line interface: ---------------------------------------------- def cli(argv): """Command-line interface (looks at argv to decide what to do).""" import getopt class BadUsage(Exception): pass try: opts, args = getopt.getopt(argv[1:], 'gp:r:h:', ['gui', 'port=', 'repository=']) for opt, val in opts: if opt in ('-g', '--gui'): options.start_gui = 1 elif opt in ('-r', '--repository'): if options.repositories: # option may be used more than once: num = len(options.repositories.keys())+1 symbolic_name = "Repository"+str(num) options.repositories[symbolic_name] = val else: options.repositories["Development"] = val elif opt in ('-p', '--port'): try: options.port = int(val) except ValueError: raise BadUsage elif opt in ('-h', '--host'): options.host = val if options.start_gui: gui(options.host, options.port) return elif options.port: def ready(server): print 'server ready at %s' % server.url serve(options.host, options.port, ready) return raise BadUsage except (getopt.error, BadUsage): cmd = sys.argv[0] port = options.port host = options.host print """ViewCVS standalone - a simple standalone HTTP-Server Usage: %(cmd)s [ ] Available Options: -h or --host= Start the HTTP server listening on . Defaults to %(host)s. You need to provide the hostname, if you want to access the standalone server from remote. -p or --port= Start an HTTP server on the given port. Default port is %(port)d. -r or --repository= Specify path for a CVS repository. May be used more than once. If you don't have a CVS repository at /home/cvsroot you will need to use this option or you have to install first and edit viewcvs.conf. -g or --gui Pop up a graphical interface for serving and testing ViewCVS. Note: This requires you start %(cmd)s with a valid X11 display connection on Unix/Linux systems. """ % locals() if __name__ == '__main__': options = Options() cli(sys.argv)