Javier, On Fri, Jan 21, 2011 at 12:27 PM, Javier Andalia <javier_anda...@rapid7.com> wrote: > Andres Riancho wrote: >> >> Javier, >> >> When you finish implementing the auto-update feature in the >> w3af-console, could you please send us an email with one file >> containing all the changes you've made? It will be easier to review >> your changes like that. Thanks! >> >> Regards, >> > > File attached!
These are some comments about the code you sent: - Please add a new section to the users' guide in order to explain how this new feature works, how to use it, why its cool, where the startup.conf file lives, etc. - You're still missing the check in dependencyCheck for pysvn; or something is being imported before running the dependencyCheck.py module. """ ariancho@toreng-0557:/mnt/big-fs/w3af/branches/auto-upd$ sudo ./w3af_console Traceback (most recent call last): File "./w3af_console", line 126, in <module> errCode = main() File "./w3af_console", line 102, in main from core.ui.consoleUi.consoleUi import consoleUi """ - Replace "Checking if a new version is available in our code repo. Please wait..." with "Checking if a new version is available in our SVN repository. Please wait..." - Show a message that indicated that the user version is up to date! If we keep it as is: """ ariancho@toreng-0557:/mnt/big-fs/w3af/branches/auto-upd$ sudo ./w3af_console Checking if a new version is available in our code repo. Please wait... w3af>>> """ The user doesn't know what happened. - I run the same command (sudo ./w3af_console) twice, and both times it said "Checking if a new version is available in our code repo. Please wait..." shouldn't that be done once a day based on startup.conf configuration? - Are my tests going to work with this setting? +REPO_URL = 'http://localhost/svn/w3af' - Missed this TODO +#### TODO: REMOVE ME BEFORE COMMIT!!! ## +import sys +sys.path.append('/home/jandalia/workspace2/w3af') - Not sure if this is going to work on my system: +LOCAL_PATH = '/home/user/w3af' - Have you tried this code in a w3af install that doesn't have svn metadata? (no .svn files). In the first roll-out of this release, some users might not have the metadata. - Add the d to encouraged "Child classes are encourage to" - I prefer w3af in all cases, even at the start of a sentence (W3af version manager class.) - Please also add the second dependency check file (there is one for the gtkui) to this section: depcheck_fpath = 'core/controllers/misc/dependencyCheck.py' - What if I'm adding an import that I know will work (import os at the beginning of the file) instead of adding an import that I think will fail? I would try to find " import" + newlineswithimport = \ + [nl for nl in diff_str.split('\n') \ + if nl.startswith('-') and nl.find('import') != -1] - Lets try to use underscore for most things: self._start_section = 'StartConfig' - Real header! +''' +Created on Jan 10, 2011 + +@author: jandalia +''' - I would move these lines: self._w3af = core.controllers.w3afCore.w3afCore() self._w3af.setPlugins(['console'], 'output') Further below, in order to perform the update first and don't have any issues with updates over modules that were already loaded. - Use print: log = om.out.console - 100% sure that this works on all cases? vmgr = VersionMgr(localpath=os.getcwd(), log=log) What if w3af is in the PATH and the user is standing in "/tmp/"? All in all, we have some details to polish, but the feature seems to be almost finished for the console UI, congrats on a job well executed :) Regards, > Thanks, > > -- > Javier Andalia - Python Developer > > Gorostiaga 2355 - Office 203 > Buenos Aires. Argentina > > Rapid7 Recipient of Highest Ranking in Vulnerability Management from Gartner > and Forrester: > http://www.rapid7.com/resources/gartner_marketscope.jsp > http://www.rapid7.com/resources/forrester-wave.jsp > > Index: w3af_console > =================================================================== > --- w3af_console (revision 3894) > +++ w3af_console (revision 3977) > @@ -23,45 +23,80 @@ > except w3afException, w3: > print 'Something went wrong, w3af failed to init the output manager. > Exception: ', str(w3) > sys.exit(-9) > + > + > +usage_doc = ''' > +w3af - Web Application Attack and Audit Framework > + > +Usage: > + > + ./w3af_console -h > + ./w3af_console -t > + ./w3af_console [-s <script_file>] > + > +Options: > + > + -h or --help > + Display this help message. > + > + -t or --test-all > + Runs all test scripts containing an 'assert' sentence. > > + -s <script_file> or --script=<script_file> > + Run <script_file> script. > > + -n or --no-update > + No update check will be made when starting. This option takes > + precedence over the 'auto-update' setting in 'startup.conf' file. > + > + -f or --force-update > + An update check will be made when starting. This option takes > + precedence over the 'auto-update' setting in 'startup.conf' file. > + > + -p <profile> or --profile=<profile> > + Run with the selecte <profile> > + > +For more info visit http://w3af.sourceforge.net/ > +''' > + > def usage(): > - om.out.information('w3af - Web Application Attack and Audit Framework') > - om.out.information('') > - om.out.information('Options:') > - om.out.information(' -h Print this help message.') > - om.out.information(' -s <file> Execute a script file.') > - om.out.information(' -p <profile> Run with the selected profile') > - om.out.information('') > - om.out.information('http://w3af.sourceforge.net/') > + om.out.information(usage_doc) > > def main(): > try: > - opts, args = getopt.getopt(sys.argv[1:], "p:i:hs:get", [] ) > - except getopt.GetoptError: > + long_options = ['script=', 'help', 'test-all', 'no-update', > + 'force-update', 'profile='] > + opts, args = getopt.getopt(sys.argv[1:], "ehts:nfp:", long_options) > + except getopt.GetoptError, e: > # print help information and exit: > usage() > return -3 > scriptFile = None > profile = None > + doupdate = None > + > for o, a in opts: > - if o in ( "-e" ): > + if o == "-e": > # easter egg > import base64 > - om.out.information( base64.b64decode( > 'R3JhY2lhcyBFdWdlIHBvciBiYW5jYXJtZSB0YW50YXMgaG9yYXMgZGUgZGVzYXJyb2xsbywgdGUgYW1vIGdvcmRhIQ==' > ) ) > - if o in ( "-t" ): > + om.out.information( > base64.b64decode('R3JhY2lhcyBFdWdlIHBvciBiYW5jYXJtZSB0YW50YXMgaG9yYXMgZGUgZGVzYXJyb2xsbywgdGUgYW1vIGdvcmRhIQ==')) > + if o in ('-t', '--test-all'): > # Test all scripts that have an assert call > from core.controllers.misc.w3afTest import w3afTest > w3afTest() > return 0 > - if o == "-s": > + if o in ('-s', '--script'): > scriptFile = a > if o in ('-p', '--profile'): > # selected profile > profile = a > - if o == "-h": > + if o in ('-h', '--help'): > usage() > return 0 > + if o in ('-f', '--force-update'): > + doupdate = True > + elif o in ('-n', '--no-update'): > + doupdate = False > > # console > from core.ui.consoleUi.consoleUi import consoleUi > @@ -77,13 +112,13 @@ > commandsToRun = [] > for line in fd: > line = line.strip() > - if line != '' and line[0] != '#': # if not a comment.. > + if line != '' and line[0] != '#': # if not a comment.. > commandsToRun.append( line ) > fd.close() > elif profile is not None: > commandsToRun = ["profiles use %s" % profile] > > - console = consoleUi(commands=commandsToRun) > + console = consoleUi(commands=commandsToRun, do_upd=doupdate) > console.sh() > > > @@ -91,4 +126,3 @@ > errCode = main() > backToCurrentDir() > sys.exit(errCode) > - > Index: core/controllers/misc/dependencyCheck.py > =================================================================== > --- core/controllers/misc/dependencyCheck.py (revision 3894) > +++ core/controllers/misc/dependencyCheck.py (revision 3977) > @@ -22,7 +22,6 @@ > > import core.controllers.outputManager as om > import sys > -import subprocess > > # Use w3af's 'extlib' modules first. By doing this we ensure that modules > # in 'w3af/extlib/' are first imported over python installation > 'site-packages/' > @@ -95,5 +94,13 @@ > msg += ' - On Debian based distributions: apt-get install > python-lxml' > print msg > sys.exit( 1 ) > + > + try: > + import pysvn > + except: > + msg = 'You have to install python pysvn lib. \n' > + msg += ' - On Debian based distributions: apt-get install > python-svn' > + print msg > + sys.exit( 1 ) > > > Index: core/controllers/auto_update/auto_update.py > =================================================================== > --- core/controllers/auto_update/auto_update.py (revision 0) > +++ core/controllers/auto_update/auto_update.py (revision 3977) > @@ -0,0 +1,657 @@ > +''' > +auto_update.py > + > +Copyright 2011 Andres Riancho > + > +This file is part of w3af, w3af.sourceforge.net . > + > +w3af is free software; you can redistribute it and/or modify > +it under the terms of the GNU General Public License as published by > +the Free Software Foundation version 2 of the License. > + > +w3af is distributed in the hope that it will be useful, > +but WITHOUT ANY WARRANTY; without even the implied warranty of > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +GNU General Public License for more details. > + > +You should have received a copy of the GNU General Public License > +along with w3af; if not, write to the Free Software > +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA > +''' > + > +from __future__ import with_statement > +from datetime import datetime, date, timedelta > +import os > +import time > +import ConfigParser > +import threading > + > + > +class SVNError(Exception): > + pass > + > + > +class SVNUpdateError(SVNError): > + pass > + > + > +class SVNCommitError(): > + pass > + > + > +class SVNClient(object): > + ''' > + Typically an abstract class. Intended to define behaviour. Not to be > + instantiated. > + SVN implementations should extend from this class. > + Implemented methods in child classes should potentially raise SVNError > + exception (or descendant) when a condition error is found. > + ''' > + > + def __init__(self, localpath): > + self._localpath = localpath > + self._repourl = self._get_repourl() > + # Action Locker! > + self._actionlock = threading.RLock() > + > + def _get_repourl(self): > + ''' > + Get repo's URL. To be implemented by subclasses. > + ''' > + raise NotImplementedError > + > + def update(self): > + ''' > + Update local repo to last revision. > + ''' > + raise NotImplementedError > + > + def commit(self): > + ''' > + Commit to remote repo changes in local repo. > + ''' > + raise NotImplementedError > + > + def status(self, path=None): > + ''' > + Return a SVNFilesList object. > + > + @param localpath: Path to get the status from. If None use > project's > + root. > + ''' > + raise NotImplementedError > + > + def list(self, path_or_url=None): > + ''' > + Return a SVNFilesList. Elements are tuples containing the path and > + the status for all files in `path_or_url` at the provided revision. > + ''' > + raise NotImplementedError > + > + def diff(self, localpath, rev=None): > + ''' > + Return string with the differences between `rev` and HEAD revision > for > + `localpath` > + ''' > + raise NotImplementedError > + > + > + > +import pysvn > + > +# Actions on files > +FILE_UPD = 'UPD' # Updated > +FILE_NEW = 'NEW' # New > +FILE_DEL = 'DEL' # Removed > + > +wcna = pysvn.wc_notify_action > +pysvn_action_translator = { > + wcna.update_add: FILE_NEW, > + wcna.update_delete: FILE_DEL, > + wcna.update_update: FILE_UPD > +} > + > +# Files statuses > +ST_CONFLICT = 'C' > +ST_NORMAL = 'N' > +ST_UNVERSIONED = 'U' > +ST_MODIFIED = 'M' > +ST_UNKNOWN = '?' > + > +wcsk = pysvn.wc_status_kind > +pysvn_status_translator = { > + wcsk.conflicted: ST_CONFLICT, > + wcsk.normal: ST_NORMAL, > + wcsk.unversioned: ST_UNVERSIONED, > + wcsk.modified: ST_MODIFIED > +} > + > + > +class W3afSVNClient(SVNClient): > + ''' > + Our wrapper for pysvn.Client class. > + ''' > + > + UPD_ACTIONS = (wcna.update_add, wcna.update_delete, wcna.update_update) > + > + def __init__(self, localpath): > + self._svnclient = pysvn.Client() > + # Call parent's __init__ > + super(W3afSVNClient, self).__init__(localpath) > + # Set callback function > + self._svnclient.callback_notify = self._register > + # Events occurred in current action > + self._events = [] > + > + def __getattribute__(self, name): > + ''' > + Wrap all methods on order to be able to respond to Ctrl+C signals. > + This implementation was added due to limitations in pysvn. > + ''' > + def new_meth(*args, **kwargs): > + def wrapped_meth(*args, **kwargs): > + try: > + self._result = meth(*args, **kwargs) > + self._exc = None > + except Exception, exc: > + if isinstance(exc, pysvn.ClientError): > + exc = SVNError(*exc.args) > + self._exc = exc > + # Run wrapped_meth in new thread. > + th = threading.Thread(target=wrapped_meth, args=args, > + kwargs=kwargs) > + th.setDaemon(True) > + try: > + th.start() > + while th.isAlive(): > + time.sleep(0.2) > + try: > + if self._exc: > + raise self._exc > + return self._result > + finally: > + self._exc = self._result = None > + except KeyboardInterrupt: > + raise > + > + attr = SVNClient.__getattribute__(self, name) > + if callable(attr): > + meth = attr > + return new_meth > + return attr > + > + @property > + def URL(self): > + return self._repourl > + > + def update(self): > + with self._actionlock: > + self._events = [] > + try: > + pysvn_rev = self._svnclient.update(self._localpath)[0] > + except pysvn.ClientError, ce: > + raise SVNUpdateError(*ce.args) > + else: > + updfiles = self._filter_files(self.UPD_ACTIONS) > + updfiles.rev = Revision(pysvn_rev.number, pysvn_rev.date) > + return updfiles > + > + def status(self, localpath=None): > + with self._actionlock: > + path = localpath or self._localpath > + entries = self._svnclient.status(path, recurse=False) > + res = [(ent.path, pysvn_status_translator.get(ent.text_status, > + ST_UNKNOWN)) for ent in entries] > + return SVNFilesList(res) > + > + def list(self, path_or_url=None): > + with self._actionlock: > + if not path_or_url: > + path_or_url = self._localpath > + entries = self._svnclient.list(path_or_url, recurse=False) > + res = [(ent.path, None) for ent, _ in entries] > + return SVNFilesList(res) > + > + def diff(self, localpath, rev=None): > + with self._actionlock: > + path = os.path.join(self._localpath, localpath) > + # If no rev is passed the compare to HEAD > + if rev is None: > + rev = pysvn.Revision(pysvn.opt_revision_kind.head) > + tempfile = os.tempnam() > + diff_str = self._svnclient.diff(tempfile, path, revision1=rev) > + return diff_str > + > + def log(self, start_rev, end_rev): > + ''' > + Return SVNLogList of log messages between `start_rev` and > `end_rev` > + revisions. > + > + @param start_rev: Revision object > + @param end_rev: Revision object > + ''' > + with self._actionlock: > + # Expected by pysvn.Client.log method > + pysvnstartrev = pysvn.Revision(pysvn.opt_revision_kind.number, > + start_rev.number) > + pysvnendrev = pysvn.Revision(pysvn.opt_revision_kind.number, > + end_rev.number) > + logs = (l.message for l in self._svnclient.log( > + self._localpath, > + > revision_start=pysvnstartrev, > + revision_end=pysvnendrev)) > + rev = end_rev if (end_rev.number > start_rev.number) else > start_rev > + return SVNLogList(logs, rev) > + > + > + def _get_repourl(self): > + ''' > + Get repo's URL. > + ''' > + svninfo = self._get_svn_info(self._localpath) > + return svninfo.URL > + > + def _get_svn_info(self, path_or_url): > + try: > + return self._svnclient.info2(path_or_url, recurse=False)[0][1] > + except pysvn.ClientError, ce: > + raise SVNUpdateError(*ce.args) > + > + def get_revision(self, local=True): > + ''' > + Return Revision object. > + > + @param local: If true return local's revision data; otherwise use > + repo's. > + ''' > + path_or_url = self._localpath if local else self._repourl > + _rev = self._get_svn_info(path_or_url).rev > + return Revision(_rev.number, _rev.date) > + > + def _filter_files(self, filterbyactions=()): > + ''' > + Filter... Return files-actions > + @param filterby: > + ''' > + files = SVNFilesList() > + for ev in self._events: > + action = ev['action'] > + if action in filterbyactions: > + path = ev['path'] > + # We're not interested on reporting directories unless a > + # 'delete' has been performed on them > + if not os.path.isdir(path) or action == wcna.update_delete: > + files.append(path, pysvn_action_translator[action]) > + return files > + > + def _register(self, event): > + ''' > + Callback method. Registers all events taking place during this > action. > + ''' > + self._events.append(event) > + > + > +class Revision(object): > + ''' > + Our own class for revisions. > + ''' > + > + def __init__(self, number, date): > + self._number = number > + self._date = date > + > + def __eq__(self, rev): > + return self._number == rev.number and \ > + self._date == rev.date > + > + def __lt__(self, rev): > + return self._number < rev.number > + > + @property > + def date(self): > + return self._date > + > + @property > + def number(self): > + return self._number > + > + > +# Limit of lines to SVNList types. To be used in __str__ method > re-definition. > +PRINT_LINES = 20 > + > +class SVNList(list): > + > + ''' > + Wrapper for python list type. It may contain the number of the current > + revision and do a custom list print. Child classes are encourage to > + redefine the __str__ method. > + ''' > + > + def __init__(self, seq=(), rev=None): > + ''' > + @param rev: Revision object > + ''' > + list.__init__(self, seq) > + self._rev = rev > + self._sorted = True > + > + def _getrev(self): > + return self._rev > + > + def _setrev(self, rev): > + self._rev = rev > + > + # TODO: Cannot use *full* decorators as we're still on py2.5 > + rev = property(_getrev, _setrev) > + > + def __eq__(self, olist): > + return list.__eq__(self, olist) and self._rev == olist.rev > + > + > +class SVNFilesList(SVNList): > + ''' > + Custom SVN files list holder. > + ''' > + > + def __init__(self, seq=(), rev=None): > + SVNList.__init__(self, seq, rev) > + self._sorted = True > + > + def append(self, path, status): > + list.append(self, (path, status)) > + self._sorted = False > + > + def __str__(self): > + # First sort by status > + sortfunc = lambda x, y: cmp(x[1], y[1]) > + self.sort(cmp=sortfunc) > + lines, rest = self[:PRINT_LINES], max(len(self) - PRINT_LINES, 0) > + print_list = ['%s %s' % (f, s) for s, f in lines] > + if rest: > + print_list.append('and %d files more.' % rest) > + if self._rev: > + print_list.append('At revision %s.' % self._rev.number) > + return os.linesep.join(print_list) > + > + > +class SVNLogList(SVNList): > + ''' > + Provides a custom way to print a SVN logs list. > + ''' > + def __str__(self): > + print_list = [] > + if self._rev: > + print_list.append('Revision %s:' % self._rev.number) > + lines, rest = self[:PRINT_LINES], max(len(self) - PRINT_LINES, 0) > + print_list += ['%3d. %s' % (n + 1, ln) for n, ln in > enumerate(lines)] > + if rest: > + print_list.append('and %d commit logs more.' % rest) > + return os.linesep.join(print_list) > + > + > +# Use this class to perform svn actions on code > +SVNClientClass = W3afSVNClient > + > + > +# Facade class. Intended to be used to to interact with the module > +class VersionMgr(object): #TODO: Make it singleton? > + > + # Events constants > + ON_UPDATE = 1 > + ON_CONFIRM_UPDATE = 2 > + ON_UPDATE_CHECK = 3 > + ON_COMMIT = 4 > + > + def __init__(self, localpath, log): > + ''' > + W3af version manager class. Handles the logic concerning the > + automatic update/commit process of the code. > + > + @param localpath: Working directory > + @param log: Default output function > + ''' > + self._localpath = localpath > + > + self._log = log > + self._client = SVNClientClass(localpath) > + # Registered functions > + self._reg_funcs = {} > + # Startup configuration > + self._start_cfg = StartUpConfig() > + > + > + def update(self, force=False, askvalue=None, print_result=False, > + show_log=False): > + ''' > + Perform code update if necessary. > + > + @param askvalue: Callback function that will output the update > + confirmation response. > + @param print_result: If True print the result files using > instance's > + log function. > + @param show_log: If True interact with the user through `askvalue` > and > + show a summary of the log messages. > + ''' > + client = self._client > + lrev = client.get_revision(local=True) > + files = SVNFilesList(rev=lrev) > + > + if force or self._has_to_update(): > + self._notify(VersionMgr.ON_UPDATE) > + rrev = client.get_revision(local=False) > + > + # If local rev is not lt repo's then we got nothing to update. > + if not (lrev < rrev): > + return files > + > + proceed_upd = True > + # Call callback function > + if askvalue: > + proceed_upd = askvalue(\ > + 'Your current w3af installation is r%s. Do you want to ' \ > + 'update to r%s [y/N]? ' % (lrev.number, rrev.number)) > + proceed_upd = (proceed_upd.lower() == 'y') > + > + if proceed_upd: > + msg = 'w3af is updating from the official SVN server...' > + self._notify(VersionMgr.ON_UPDATE, msg) > + # Find new deps. > + newdeps = self._added_new_dependencies() > + if newdeps: > + msg = 'At least one new dependency (%s) was included in > ' \ > + 'w3af. Please update manually.' % str(', > '.join(newdeps)) > + self._notify(VersionMgr.ON_UPDATE, msg) > + else: > + # Finally do the update! > + files = client.update() > + # Now save today as last-update date and persist it. > + self._start_cfg.last_upd = date.today() > + self._start_cfg.save() > + > + # Before returning perform some interaction with the user if > + # requested. > + if print_result: > + self._log(str(files)) > + > + if show_log: > + show_log = askvalue('Do you want to see a summary of the ' > \ > + 'new code commits log messages? [y/N]? ').lower() == 'y' > + if show_log: > + self._log(str(self._client.log(lrev, rrev))) > + return files > + > + def status(self, path=None): > + return self._client.status(path) > + > + def register(self, eventname, func, msg): > + funcs = self._reg_funcs.setdefault(eventname, []) > + funcs.append((func, msg)) > + > + def _notify(self, event, msg=None): > + ''' > + Call registered functions for event. If `msg` is not None then > force > + to call the registered functions with `msg`. > + ''' > + for f, _msg in self._reg_funcs.get(event, []): > + f(msg or _msg) > + > + def _added_new_dependencies(self): > + ''' > + Return tuple with the dependencies added to extlib/ in the repo if > + any. Basically it compares local dirs under extlib/ to those in the > + repo as well as checks if at least a new sentence containing the > + import keyword was added to the dependencyCheck.py file. > + ''' > + # > + # Check if a new directory was added to repo's extlib > + # > + client = self._client > + ospath = os.path > + join = ospath.join > + # Find dirs in repo > + repourl = self._client.URL + '/' + 'extlib' > + # In repo we distinguish dirs from files by the dot (.) presence > + repodirs = (ospath.basename(d) for d, _ in client.list(repourl)[1:] > \ > + if ospath.basename(d).find('.') == > -1) > + # Get local dirs > + extliblocaldir = join(self._localpath, 'extlib') > + extlibcontent = (join(extliblocaldir, f) for f in \ > + os.listdir(extliblocaldir)) > + localdirs = (ospath.basename(d) for d in extlibcontent \ > + if ospath.isdir(d)) > + # New dependencies > + deps = tuple(set(repodirs).difference(localdirs)) > + > + # > + # Additional constraint: We should verify that at least an import > + # sentence was added to the dependencyCheck.py file > + # > + if deps: > + depcheck_fpath = 'core/controllers/misc/dependencyCheck.py' > + diff_str = client.diff(depcheck_fpath) > + # SVN shows HEAD rev's new lines preceeded by a '-' char. > + newlineswithimport = \ > + [nl for nl in diff_str.split('\n') \ > + if nl.startswith('-') and nl.find('import') != -1] > + # Ok, no import sentence was detected so no dep. was *really* > + # added. > + if not newlineswithimport: > + deps = () > + > + return deps > + > + def _has_to_update(self): > + ''' > + Helper method that figures out if an update should be performed > + according to the startup cfg file. > + Some rules: > + 1) IF auto_upd is False THEN return False > + 2) IF last_upd == 'yesterday' and freq == 'D' THEN return True > + 3) IF last_upd == 'two_days_ago' and freq == 'W' THEN return > False. > + @return: Boolean value. > + ''' > + startcfg = self._start_cfg > + # That's it! > + if not startcfg.auto_upd: > + return False > + else: > + freq = startcfg.freq > + diff_days = max((date.today()-startcfg.last_upd).days, 0) > + > + if (freq == StartUpConfig.FREQ_DAILY and diff_days > 0) or \ > + (freq == StartUpConfig.FREQ_WEEKLY and diff_days > 6) or \ > + (freq == StartUpConfig.FREQ_MONTHLY and diff_days > 29): > + return True > + return False > + > + > +from core.controllers.misc.homeDir import get_home_dir > + > +class StartUpConfig(object): > + ''' > + Wrapper class for ConfigParser.ConfigParser. > + Holds the configuration for the VersionMgr update/commit process > + ''' > + > + ISO_DATE_FMT = '%Y-%m-%d' > + # Frequency constants > + FREQ_DAILY = 'D' # [D]aily > + FREQ_WEEKLY = 'W' # [W]eekly > + FREQ_MONTHLY = 'M' # [M]onthly > + > + def __init__(self): > + > + self._start_cfg_file = os.path.join(get_home_dir(), 'startup.conf') > + self._start_section = 'StartConfig' > + defaults = {'auto-update': 'true', 'frequency': 'D', > + 'last-update': 'None'} > + self._config = ConfigParser.ConfigParser(defaults) > + self._autoupd, self._freq, self._lastupd = self._load_cfg() > + > + ### PROPERTIES # > + > + def _get_last_upd(self): > + ''' > + Getter method. > + ''' > + return self._lastupd > + > + def _set_last_upd(self, datevalue): > + ''' > + @param datevalue: datetime.date value > + ''' > + self._lastupd = datevalue > + self._config.set(self._start_section, 'last-update', > + datevalue.isoformat()) > + > + # TODO: Cannot use *full* decorators as we're still on py2.5 > + # Read/Write property > + last_upd = property(_get_last_upd, _set_last_upd) > + > + @property > + def freq(self): > + return self._freq > + > + @property > + def auto_upd(self): > + return self._autoupd > + > + ### METHODS # > + > + def _load_cfg(self): > + ''' > + Loads configuration from config file. > + ''' > + config = self._config > + startsection = self._start_section > + if not config.has_section(startsection): > + config.add_section(startsection) > + > + # Read from file > + config.read(self._start_cfg_file) > + > + auto_upd = config.get(startsection, 'auto-update', raw=True) > + boolvals = {'false': 0, 'off': 0, 'no': 0, > + 'true': 1, 'on': 1, 'yes': 1} > + auto_upd = bool(boolvals.get(auto_upd.lower(), False)) > + > + freq = config.get(startsection, 'frequency', raw=True).upper() > + if freq not in (StartUpConfig.FREQ_DAILY, > StartUpConfig.FREQ_WEEKLY, > + StartUpConfig.FREQ_MONTHLY): > + freq = StartUpConfig.FREQ_DAILY > + > + lastupdstr = config.get(startsection, 'last-update', > raw=True).upper() > + # Try to parse it > + try: > + lastupd = datetime.strptime(lastupdstr, > self.ISO_DATE_FMT).date() > + except: > + # Provide default value that enforces the update to happen > + lastupd = date.today() - timedelta(days=31) > + return (auto_upd, freq, lastupd) > + > + def save(self): > + ''' > + Saves current values to cfg file > + ''' > + with open(self._start_cfg_file, 'wb') as configfile: > + self._config.write(configfile) > Index: core/controllers/auto_update/tests/test_auto_update.py > =================================================================== > --- core/controllers/auto_update/tests/test_auto_update.py (revision 0) > +++ core/controllers/auto_update/tests/test_auto_update.py (revision > 3977) > @@ -0,0 +1,193 @@ > +''' > +Created on Jan 10, 2011 > + > +@author: jandalia > +''' > + > +#### TODO: REMOVE ME BEFORE COMMIT!!! ## > +import sys > +sys.path.append('/home/jandalia/workspace2/w3af') > +######################################## > + > +from pymock import PyMockTestCase, method, override, dontcare, set_count > + > +from core.controllers.auto_update.auto_update import W3afSVNClient, > Revision, \ > + VersionMgr, SVNFilesList, StartUpConfig, FILE_UPD, FILE_NEW, FILE_DEL, > \ > + ST_CONFLICT, ST_MODIFIED, ST_UNKNOWN > + > +del W3afSVNClient.__getattribute__ > + > +REPO_URL = 'http://localhost/svn/w3af' > +LOCAL_PATH = '/home/user/w3af' > + > + > +def dummy(*args, **kwargs): > + pass > + > + > +class TestW3afSVNClient(PyMockTestCase): > + > + rev = Revision(112, None) > + upd_files = SVNFilesList( > + [('file1.txt', FILE_NEW), > + ('file2.py', FILE_UPD), > + ('file3.jpg', FILE_DEL)], > + rev > + ) > + > + def setUp(self): > + PyMockTestCase.setUp(self) > + > + W3afSVNClient._get_repourl = dummy > + self.client = W3afSVNClient(LOCAL_PATH) > + self.client._repourl = REPO_URL > + self.client._svnclient = self.mock() > + > + def test_has_repourl(self): > + self.assertTrue(self.client._repourl is not None) > + > + def test_has_svn_client(self): > + self.assertTrue(self.client._svnclient is not None) > + > + def test_has_localpath(self): > + self.assertTrue(self.client._localpath is not None) > + > + def test_upd(self): > + client = self.client > + method(client._svnclient, > 'update').expects(LOCAL_PATH).returns([self.rev]) > + override(client, '_filter_files').expects(client.UPD_ACTIONS) > + self.returns(self.upd_files) > + ## Stop recording. Play! > + self.replay() > + self.assertEquals(self.upd_files, client.update()) > + ## Verify ## > + self.verify() > + > + def test_upd_fail(self): > + from pysvn import ClientError > + from core.controllers.auto_update.auto_update import SVNUpdateError > + client = self.client > + method(client._svnclient, 'update').expects(LOCAL_PATH) > + self.raises(ClientError('file locked')) > + ## Stop recording. Play! > + self.replay() > + self.assertRaises(SVNUpdateError, client.update) > + ## Verify ## > + self.verify() > + > + def test_upd_conflict(self): > + ''' > + Files in conflict exists after update. > + ''' > + pass > + > + def test_upd_nothing_to_update(self): > + '''No update to current copy was made. Tell the user. Presumably > + the revision was incremented''' > + pass > + > + def test_filter_files(self): > + import pysvn > + from pysvn import wc_notify_action as wcna > + from pysvn import Revision > + from core.controllers.auto_update.auto_update import os > + client = self.client > + override(os.path, 'isdir').expects(dontcare()).returns(False) > + set_count(exactly=2) > + ## Stop recording. Play! > + self.replay() > + # Call client's callback function several times > + f1 = '/path/to/file/foo.py' > + ev = {'action': wcna.update_delete, > + 'error': None, 'mime_type': None, 'path': f1, > + 'revision': Revision(pysvn.opt_revision_kind.number, 11)} > + client._register(ev) > + > + f2 = '/path/to/file/foo2.py' > + ev2 = {'action': wcna.update_update, > + 'error': None, 'mime_type': None, > + 'path': f2, > + 'revision': Revision(pysvn.opt_revision_kind.number, 11)} > + client._register(ev2) > + > + expected_res = SVNFilesList([(f1, FILE_DEL), (f2, FILE_UPD)]) > + self.assertEquals(expected_res, > + > client._filter_files(filterbyactions=W3afSVNClient.UPD_ACTIONS)) > + ## Verify ## > + self.verify() > + > + def test_status(self): > + from pysvn import wc_status_kind as wcsk > + client = self.client > + # Mock pysvnstatus objects > + smock = self.mock() > + smock.path > + self.setReturn('/some/path/foo') > + smock.text_status > + self.setReturn(wcsk.modified) > + > + smock2 = self.mock() > + smock2.path > + self.setReturn('/some/path/foo2') > + smock2.text_status > + self.setReturn(wcsk.conflicted) > + > + smock3 = self.mock() > + smock3.path > + self.setReturn('/some/path/foo3') > + smock3.text_status > + self.setReturn('some_weird_status') > + > + status_files = [smock, smock2, smock3] > + method(client._svnclient, 'status').expects(LOCAL_PATH, > recurse=False) > + self.returns(status_files) > + ## Stop recording - Replay ## > + self.replay() > + expected_res = \ > + SVNFilesList([ > + ('/some/path/foo', ST_MODIFIED), > + ('/some/path/foo2', ST_CONFLICT), > + ('/some/path/foo3', ST_UNKNOWN)]) > + self.assertEquals(expected_res, client.status()) > + ## Verify ## > + self.verify() > + > + def test_commit(self): > + pass > + > + > +class TestVersionMgr(PyMockTestCase): > + > + def setUp(self): > + PyMockTestCase.setUp(self) > + # Override auto_update module variable > + import core.controllers.auto_update.auto_update as autoupdmod > + autoupdmod.SVNClientClass = self.mock() > + self.vmgr = VersionMgr(LOCAL_PATH, dummy) > + > + def test_has_to_update(self): > + > + vmgr = self.vmgr > + start_cfg_mock = self.mock() > + vmgr._start_cfg = start_cfg_mock > + > + # Test no auto-update > + start_cfg_mock.auto_upd > + self.setReturn(False) > + self.replay() > + self.assertFalse(vmgr._has_to_update()) > + > + # Test [D]aily, [W]eekly and [M]onthly auto-update > + import datetime > + SC = StartUpConfig > + for freq, diffdays in ((SC.FREQ_DAILY, 1), (SC.FREQ_WEEKLY, 8), \ > + (SC.FREQ_MONTHLY, 34)): > + self.reset() > + start_cfg_mock.auto_upd > + self.setReturn(True) > + start_cfg_mock.freq > + self.setReturn(freq) > + start_cfg_mock.last_upd > + self.setReturn(datetime.date.today() - > datetime.timedelta(days=diffdays)) > + self.replay() > + self.assertTrue(vmgr._has_to_update()) > Index: core/controllers/auto_update/tests/__init__.py > =================================================================== > Index: core/controllers/auto_update/__init__.py > =================================================================== > --- core/controllers/auto_update/__init__.py (revision 0) > +++ core/controllers/auto_update/__init__.py (revision 3977) > @@ -0,0 +1,2 @@ > +from auto_update import * > +__all__ = ['VersionMgr'] > \ No newline at end of file > Index: core/ui/consoleUi/consoleUi.py > =================================================================== > --- core/ui/consoleUi/consoleUi.py (revision 3894) > +++ core/ui/consoleUi/consoleUi.py (revision 3977) > @@ -23,8 +23,11 @@ > import sys > try: > from shlex import * > - import os.path > + import os > + import random > import traceback > + > + from core.controllers.auto_update import VersionMgr, SVNError > from core.ui.consoleUi.rootMenu import * > from core.ui.consoleUi.callbackMenu import * > from core.ui.consoleUi.util import * > @@ -35,10 +38,10 @@ > import core.controllers.outputManager as om > import core.controllers.miscSettings as miscSettings > from core.controllers.w3afException import w3afException > - import random > except KeyboardInterrupt: > sys.exit(0) > > + > class consoleUi: > ''' > This class represents the console. > @@ -47,38 +50,57 @@ > @author Alexander Berezhnoy (alexander.berezhnoy |at| gmail.com) > ''' > > - def __init__(self, commands=[], parent=None): > + def __init__(self, commands=[], parent=None, do_upd=None): > self._commands = commands > self._line = [] # the line which is being typed > self._position = 0 # cursor position > self._history = historyTable() # each menu has array of (array, > positionInArray) > self._trace = [] > + self._upd_avail = False > > - self._handlers = { '\t' : self._onTab, \ > - '\r' : self._onEnter, \ > - term.KEY_BACKSPACE : self._onBackspace, \ > - term.KEY_LEFT : self._onLeft, \ > - term.KEY_RIGHT : self._onRight, \ > - term.KEY_UP : self._onUp, \ > - term.KEY_DOWN : self._onDown, \ > - '^C' : self._backOrExit, \ > + self._handlers = { > + '\t' : self._onTab, > + '\r' : self._onEnter, > + term.KEY_BACKSPACE : self._onBackspace, > + term.KEY_LEFT : self._onLeft, > + term.KEY_RIGHT : self._onRight, > + term.KEY_UP : self._onUp, > + term.KEY_DOWN : self._onDown, > + '^C' : self._backOrExit, > '^D' : self._backOrExit, > '^L' : self._clearScreen, > '^W' : self._delWord, > '^H' : self._onBackspace, > '^A' : self._toLineStart, > - '^E' : self._toLineEnd } > + '^E' : self._toLineEnd > + } > > if parent: > self.__initFromParent(parent) > else: > - self.__initRoot() > + self.__initRoot(do_upd) > > - > - def __initRoot(self): > + def __initRoot(self, do_upd): > + ''' > + Root menu init routine. > + ''' > self._w3af = core.controllers.w3afCore.w3afCore() > self._w3af.setPlugins(['console'], 'output') > - > + > + if do_upd is not False: > + log = om.out.console > + vmgr = VersionMgr(localpath=os.getcwd(), log=log) > + msg = 'Checking if a new version is available in our code repo. > ' \ > + 'Please wait...' > + vmgr.register(vmgr.ON_UPDATE, log, msg) > + try: > + vmgr.update(force=do_upd is True, askvalue=raw_input, > + print_result=True, show_log=True) > + except SVNError, e: > + om.out.error('An error occured while updating:\n%s' % > e.args) > + except KeyboardInterrupt: > + pass > + > def __initFromParent(self, parent): > self._context = parent._context > self._w3af = parent._w3af > @@ -124,7 +146,6 @@ > def _executePending(self): > while (self._commands): > curCmd, self._commands = self._commands[0], self._commands[1:] > - > self._paste(curCmd) > self._onEnter() > > Index: core/ui/consoleUi/menu.py > =================================================================== > --- core/ui/consoleUi/menu.py (revision 3894) > +++ core/ui/consoleUi/menu.py (revision 3977) > @@ -20,14 +20,12 @@ > > ''' > > -import traceback > - > -import core.data.kb.knowledgeBase as kb > +from core.controllers.w3afException import w3afException > from core.ui.consoleUi.util import * > from core.ui.consoleUi.history import * > from core.ui.consoleUi.help import * > import core.controllers.outputManager as om > -from core.controllers.w3afException import w3afException > +import core.data.kb.knowledgeBase as kb > > > class menu: > @@ -86,7 +84,7 @@ > > self._paramHandlers = {} > for cmd in [c for c in dir(self) if c.startswith('_cmd_')]: > - self._handlers[cmd[5:]] = getattr(self, cmd) > + self._handlers[cmd[5:]] = getattr(self, cmd) > > for cmd in self._handlers.keys(): > try: > @@ -219,7 +217,6 @@ > else: > om.out.console( repr(res) ) > > - > def _cmd_assert(self, params): > if not len(params): > raise w3afException('Expression is expected') > > -- Andrés Riancho Director of Web Security at Rapid7 LLC Founder at Bonsai Information Security Project Leader at w3af ------------------------------------------------------------------------------ Special Offer-- Download ArcSight Logger for FREE (a $49 USD value)! Finally, a world-class log management solution at an even better price-free! Download using promo code Free_Logger_4_Dev2Dev. Offer expires February 28th, so secure your free ArcSight Logger TODAY! http://p.sf.net/sfu/arcsight-sfd2d _______________________________________________ W3af-develop mailing list W3af-develop@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/w3af-develop