def abs_href(self): """The application URL""" if not self.base_url: urlpattern = MultiProductSystem(self.parent).product_base_url if not urlpattern: self.log.warn("product_base_url option not set in " "configuration, generated links may be " "incorrect") urlpattern = 'products/$(prefix)s' envname = os.path.basename(self.parent.path) prefix = unicode_quote(self.product.prefix, safe="") name = unicode_quote(self.product.name, safe="") url = urlpattern.replace('$(', '%(') \ .replace('%(envname)s', envname) \ .replace('%(prefix)s', prefix) \ .replace('%(name)s', name) if urlsplit(url).netloc: # Absolute URLs _abs_href = Href(url) else: # Relative URLs parent_href = Href(self.parent.abs_href(), path_safe="/!~*'()%", query_safe="!~*'()%") _abs_href = Href(parent_href(url)) else: _abs_href = Href(self.base_url) return _abs_href
def __add__(self, rhs): if not rhs: return self.base or '/' if rhs.startswith('?'): return (self.base or '/') + \ unicode_quote(rhs, self._printable_safe) if not rhs.startswith('/'): rhs = '/' + rhs return self.base + unicode_quote(rhs, self._printable_safe)
def _path_to_url(self, path): '''Encode a path in the repos as a fully qualified URL. ''' repos = self._get_repos_direct_object() url_parts = ['file:///', unicode_quote(repos.path.lstrip('/')), '/', unicode_quote(path.lstrip('/')), ] return ''.join(url_parts)
def login_as(self, username, password=None): if not password: password = username assert 'http://' == self.url[:7] unused, protocol, host_and_rest = self.url.partition('http://') new_url = "%s%s:%s@%s/login" % (protocol, unicode_quote(username), unicode_quote(password), host_and_rest) self.windmill.open(url=new_url) self.windmill.waits.forPageLoad() self.windmill.waits.forElement(jquery="('ul.metanav li:contains(Logged in as %s)')[0]" % username) self.windmill.asserts.assertText( xpath="//*[contains(@class, 'metanav')]/li[1]", validator='Logged in as %s' % username)
def create_milestone(self, name=None, due=None): """Creates the specified milestone, with a random name if none is provided. Returns the name of the milestone. """ if name == None: name = random_unique_camel() milestone_url = self.url + "/admin/ticket/milestones" tc.go(milestone_url) tc.url(milestone_url) tc.formvalue('addmilestone', 'name', name) if due: # TODO: How should we deal with differences in date formats? tc.formvalue('addmilestone', 'duedate', due) tc.submit() tc.notfind(internal_error) tc.notfind('Milestone .* already exists') tc.url(milestone_url) tc.find(name) # Make sure it's on the roadmap. tc.follow('Roadmap') tc.url(self.url + "/roadmap") tc.find('Milestone:.*%s' % name) tc.follow(name) tc.url('%s/milestone/%s' % (self.url, unicode_quote(name))) if not due: tc.find('No date set') return name
def import_wiki_attachments(self, template_path): """Imports wiki attachments from template using the Attachment API.""" # check that there are attachments to import template_attachment_path = os.path.join(template_path, 'attachments', 'wiki') if os.path.isdir(template_attachment_path): # clear the wiki attachment table @self.env.with_transaction() def clear_attachments(db): """Clears any wiki attachments from the current attachment table.""" cursor = db.cursor() cursor.execute("DELETE FROM attachment WHERE type='wiki'") # move attachment file into the env and insert database row filepath = os.path.join(template_path, 'attachment.xml') tree = ET.ElementTree(file=filepath) for att in tree.getroot(): attachment = Attachment(self.env, 'wiki', att.attrib['parent_id']) attachment.description = att.text try: fileobj = open(os.path.join(template_attachment_path, att.attrib['parent_id'], unicode_quote(att.attrib['name']))) attachment.insert(att.attrib['name'], fileobj, att.attrib['size']) except IOError: self.log.info("Unable to import attachment %s", att.attrib['name'])
def web_upload(self, req): # TODO: handle file upload when POSTing files = [] for fname in sorted(os.listdir(self._dump_path())): fpath = os.path.join(self._dump_path(), fname) fstat = os.stat(fpath) files.append({"name": fname, "url": unicode_quote(fname), "size": fstat.st_size, "date": fstat.st_mtime}) return {"max_size": self.max_size, "files": files, "action": "upload"}
def download_as_csv(self): url_template = '%(prefix)s/%(backlog)s' backlog_path = url_template % dict(prefix=BACKLOG_URL, backlog='Product Backlog') url = self.tester.url + unicode_quote(backlog_path) + '?format=csv' tc.go(url) tc.code(200) csv_export = tc.show() csvfile = CSVFile(StringIO(csv_export), None, 'UTF-8') return csvfile
def move_attachment_file(env, parent_realm, parent_id, filename): old_path = os.path.join(env.path, 'attachments', parent_realm, unicode_quote(parent_id)) if filename: old_path = os.path.join(old_path, unicode_quote(filename)) old_path = os.path.normpath(old_path) if os.path.isfile(old_path): new_path = Attachment._get_path(env.path, parent_realm, parent_id, filename) try: os.renames(old_path, new_path) except OSError: printerr(_("Unable to move attachment from:\n\n" " %(old_path)s\n\nto:\n\n %(new_path)s\n", old_path=old_path, new_path=new_path)) raise else: env.log.warning("Can't find file for 'attachment:%s:%s:%s', ignoring", filename, parent_realm, parent_id)
def runTest(self): tc.go(self.tester.url + '/admin/ticket/milestones/' + unicode_quote(self.milestone_name())) new_name = self.milestone_name() + 'Renamed' tc.formvalue('modifymilestone', 'name', new_name) tc.submit('save') tc.code(200) # Now we expect that the ticket and the sprint have updated milestone ticket_page = self.tester.navigate_to_ticket_page(self.tkt_id) self.assert_equals(new_name, ticket_page.milestone()) self.tester.go_to_sprint_edit_page("SprintFor" + self.milestone_name()) tc.find('for milestone %s</h1>' % new_name)
def insert(self, filename, fileobj, size, t=None, db=None): # FIXME: `t` should probably be switched to `datetime` too if not db: db = self.env.get_db_cnx() handle_ta = True else: handle_ta = False self.size = size and int(size) or 0 timestamp = int(t or time.time()) self.date = datetime.fromtimestamp(timestamp, utc) # Make sure the path to the attachment is inside the environment # attachments directory attachments_dir = os.path.join(os.path.normpath(self.env.path), 'attachments') commonprefix = os.path.commonprefix([attachments_dir, self.path]) assert commonprefix == attachments_dir if not os.access(self.path, os.F_OK): os.makedirs(self.path) filename = unicode_quote(filename) path, targetfile = create_unique_file(os.path.join(self.path, filename)) try: # Note: `path` is an unicode string because `self.path` was one. # As it contains only quoted chars and numbers, we can use `ascii` basename = os.path.basename(path).encode('ascii') filename = unicode_unquote(basename) cursor = db.cursor() cursor.execute("INSERT INTO attachment " "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", (self.parent_realm, self.parent_id, filename, self.size, timestamp, self.description, self.author, self.ipnr)) shutil.copyfileobj(fileobj, targetfile) self.resource.id = self.filename = filename self.env.log.info('New attachment: %s by %s', self.title, self.author) if handle_ta: db.commit() targetfile.close() for listener in AttachmentModule(self.env).change_listeners: listener.attachment_added(self) finally: if not targetfile.closed: targetfile.close()
def files(pgc,myc,users): print "Starting files" pgc.execute("""SELECT * FROM files"""); count = 0 while (1): row = pgc.fetchone() if row == None: break # create the file attached_to_pagename = row[6] file_blob = str(row[1]) file_blob.decode('latin1') path = ATTACHMENTS+'/'+unicode_quote(attached_to_pagename) if not os.access(path, os.F_OK): os.makedirs(path) filename = path+'/'+row[0] outfile = open(filename,'w') outfile.write(file_blob) outfile.close() print "Wrote %s \n" % filename # insert into database upload_time = int(row[2]) if users.has_key(row[3]): username = users[row[3]] else: username = '' sql = "INSERT INTO attachment VALUES ('wiki',%s,%s,%s,%s,%s,%s,%s)" try: myc.execute(sql,(attached_to_pagename, row[0], len(file_blob), upload_time, '', username, row[5])) except: print "Error inserting "+row[0]+"\n" continue count += 1 mydb.commit() print "Finished %d rows of files" % count
def render_macro(self, req, name, content): args = content.split(",") part = model.BlogPart(self.env, args[0]) if part: body = part.body argnum = int(part.argnum) i = 1 for arg in args[1:]: a = unicode_quote(arg) body = body.replace("%" + "arg_%d" % (i) + "%", a) i = i + 1 return body else: return "Not Impremented %s" % args[0]
def _do_dump(self, directory, *names): if not names: names = ['*'] pages = self.get_wiki_list() if not os.path.isdir(directory): if not os.path.exists(directory): os.mkdir(directory) else: raise AdminCommandError(_("'%(name)s' is not a directory", name=path_to_unicode(directory))) for p in pages: if any(p == name or (name.endswith('*') and p.startswith(name[:-1])) for name in names): dst = os.path.join(directory, unicode_quote(p, '')) printout(' %s => %s' % (p, dst)) self.export_page(p, dst)
def go_to_report(self, id, args=None): """Surf to the specified report. Assumes the report exists. Report variables will be appended if specified. :param id: id of the report :param args: may optionally specify a dictionary of arguments to be encoded as a query string """ report_url = self.url + "/report/%s" % id if args: arglist = [] for param, value in args.items(): arglist.append('%s=%s' % (param.upper(), unicode_quote(value))) report_url += '?' + '&'.join(arglist) tc.go(report_url) tc.url(report_url.encode('string-escape').replace('?', '\?'))
def __init__(self, page_name, page_version, template, env, options): self.page_name = page_name self.page_version = page_version self.template = template self.env = env self.options = options self.template_dirs = [ self.env.get_templates_dir(), resource_filename(__name__, 'templates'), ] self.template_dirs.insert(0, os.path.join(self.env.path, 'attachments', "wiki", unicode_quote(self.page_name))) self.xml = { "content": "", "styles": "", } self.tmpdir = tempfile.mkdtemp(prefix="trac-odtexport") self.styles = {} self.autostyles = {} self.style_name_re = re.compile('style:name="([^"]+)"') self.fonts = {} self.zfile = None
def __call__(self, *args, **kw): href = self.base if href and href[-1] == '/': href = href[:-1] params = [] def add_param(name, value): if type(value) in (list, tuple): for i in [i for i in value if i != None]: params.append((name, i)) elif v != None: params.append((name, value)) if args: lastp = args[-1] if lastp and type(lastp) is dict: for k,v in lastp.items(): add_param(k, v) args = args[:-1] elif lastp and type(lastp) in (list, tuple): for k,v in lastp: add_param(k, v) args = args[:-1] # build the path path = '/'.join([unicode_quote(unicode(arg).strip('/')) for arg in args if arg != None]) if path: href += '/' + path # assemble the query string for k,v in kw.items(): add_param(k, v) if params: href += '?' + unicode_urlencode(params) return href
def __init__(self): api_url = unicode_quote(self.job_url, '/%:@') if api_url and api_url[-1] != '/': api_url += '/' api_url += 'api/xml' pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() pwd_mgr.add_password(None, api_url, self.username, self.password) b_auth = urllib2.HTTPBasicAuthHandler(pwd_mgr) d_auth = urllib2.HTTPDigestAuthHandler(pwd_mgr) self.url_opener = urllib2.build_opener(b_auth, d_auth) self.env.log.debug("registered auth-handler for '%s', username='******'", api_url, self.username) if '/job/' in api_url: path = '/*/build[timestamp>=%(start)s][timestamp<=%(stop)s]' depth = 1 if self.disp_mod: path += ('|/*/module/build' '[timestamp>=%(start)s][timestamp<=%(stop)s]') depth += 1 else: path = '/*/job/build[timestamp>=%(start)s][timestamp<=%(stop)s]' depth = 2 if self.disp_mod: path += ('|/*/job/module/build' '[timestamp>=%(start)s][timestamp<=%(stop)s]') depth += 1 self.info_url = ('%s?xpath=%s&depth=%s' '&exclude=//action|//artifact|//changeSet' '&wrapper=builds' % (api_url.replace('%', '%%'), path, depth)) self.env.log.debug("Build-info url: '%s'", self.info_url)
def __call__(self, *args, **kw): href = self.base params = [] def add_param(name, value): if isinstance(value, (list, tuple)): for i in [i for i in value if i is not None]: params.append((name, i)) elif value is not None: params.append((name, value)) if args: lastp = args[-1] if isinstance(lastp, dict): for k, v in lastp.items(): add_param(k, v) args = args[:-1] elif isinstance(lastp, (list, tuple)): for k, v in lastp: add_param(k, v) args = args[:-1] # build the path path = '/'.join(unicode_quote(unicode(arg).strip('/'), self.path_safe) for arg in args if arg is not None) if path: href += '/' + slashes_re.sub('/', path).lstrip('/') elif not href: href = '/' # assemble the query string for k, v in kw.items(): add_param(k[:-1] if k.endswith('_') else k, v) if params: href += '?' + unicode_urlencode(params, self.query_safe) return href
def __init__(self): # get base api url api_url = unicode_quote(self.job_url, '/%:@') if api_url and api_url[-1] != '/': api_url += '/' api_url += 'api/python' # set up http authentication if self.username and self.api_token: handlers = [ self.HTTPOpenHandlerBasicAuthNoChallenge(self.username, self.api_token) ] elif self.username and self.password: pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() pwd_mgr.add_password(None, api_url, self.username, self.password) b_auth = urllib2.HTTPBasicAuthHandler(pwd_mgr) d_auth = urllib2.HTTPDigestAuthHandler(pwd_mgr) handlers = [ b_auth, d_auth, self.HudsonFormLoginHandler(self) ] else: handlers = [] self.url_opener = urllib2.build_opener(*handlers) if handlers: self.env.log.debug("registered auth-handlers for '%s', " \ "username='******'", api_url, self.username) # construct tree=... parameter to query for the desired items tree = '%(b)s' if self.disp_mod: tree += ',modules[%(b)s]' if '/job/' not in api_url: tree = 'jobs[' + tree + ']' items = 'builds[building,timestamp,duration,result,description,url,' \ 'fullDisplayName' elems = [] if self.list_changesets: elems.append('revision') elems.append('id') if 'author' in self.disp_culprit or 'authors' in self.disp_culprit: elems.append('user') elems.append('author[fullName]') if elems: items += ',changeSet[items[%s]]' % ','.join(elems) if 'culprit' in self.disp_culprit or 'culprits' in self.disp_culprit: items += ',culprits[fullName]' if 'starter' in self.disp_culprit: items += ',actions[causes[userName]]' items += ']' # assemble final url tree = tree % {'b': items} self.info_url = '%s?tree=%s' % (api_url, tree) self.env.log.debug("Build-info url: '%s'", self.info_url)
def dispatch(self, req): """Find a registered handler that matches the request and let it process it. In addition, this method initializes the data dictionary passed to the the template and adds the web site chrome. """ self.log.debug('Dispatching %r', req) chrome = Chrome(self.env) # Setup request callbacks for lazily-evaluated properties req.callbacks.update({ 'authname': self.authenticate, 'chrome': chrome.prepare_request, 'perm': self._get_perm, 'session': self._get_session, 'locale': self._get_locale, 'lc_time': self._get_lc_time, 'tz': self._get_timezone, 'form_token': self._get_form_token, 'use_xsendfile': self._get_use_xsendfile, 'xsendfile_header': self._get_xsendfile_header, }) try: try: # Select the component that should handle the request chosen_handler = None try: for handler in self.handlers: if handler.match_request(req): chosen_handler = handler break if not chosen_handler: if not req.path_info or req.path_info == '/': chosen_handler = self.default_handler # pre-process any incoming request, whether a handler # was found or not chosen_handler = \ self._pre_process_request(req, chosen_handler) except TracError, e: raise HTTPInternalError(e) if not chosen_handler: if req.path_info.endswith('/'): # Strip trailing / and redirect target = unicode_quote(req.path_info.rstrip('/')) if req.query_string: target += '?' + req.query_string req.redirect(req.href + target, permanent=True) raise HTTPNotFound('No handler matched request to %s', req.path_info) req.callbacks['chrome'] = partial(chrome.prepare_request, handler=chosen_handler) # Protect against CSRF attacks: we validate the form token # for all POST requests with a content-type corresponding # to form submissions if req.method == 'POST': ctype = req.get_header('Content-Type') if ctype: ctype, options = cgi.parse_header(ctype) if ctype in ('application/x-www-form-urlencoded', 'multipart/form-data') and \ req.args.get('__FORM_TOKEN') != req.form_token: if self.env.secure_cookies and req.scheme == 'http': msg = _('Secure cookies are enabled, you must ' 'use https to submit forms.') else: msg = _('Do you have cookies enabled?') raise HTTPBadRequest(_('Missing or invalid form token.' ' %(msg)s', msg=msg)) # Process the request and render the template resp = chosen_handler.process_request(req) if resp: if len(resp) == 2: # old Clearsilver template and HDF data self.log.error("Clearsilver template are no longer " "supported (%s)", resp[0]) raise TracError( _("Clearsilver templates are no longer supported, " "please contact your Trac administrator.")) # Genshi template, data, content_type = \ self._post_process_request(req, *resp) if 'hdfdump' in req.args: req.perm.require('TRAC_ADMIN') # debugging helper - no need to render first out = StringIO.StringIO() pprint(data, out) req.send(out.getvalue(), 'text/plain') output = chrome.render_template( req, template, data, content_type, iterable=chrome.use_chunked_encoding) req.send(output, content_type or 'text/html') else: self._post_process_request(req) except RequestDone: raise except: # post-process the request in case of errors err = sys.exc_info() try: self._post_process_request(req) except RequestDone: raise except Exception, e: self.log.error("Exception caught while post-processing" " request: %s", exception_to_unicode(e, traceback=True)) raise err[0], err[1], err[2]
def expand_macro(self, formatter, name, content, args=None): req = formatter.req # prepare options options, query_args = parse_options(self.env, content, copy.copy(DEFAULT_OPTIONS)) query_args[self.remaining_field + "!"] = None tickets = execute_query(self.env, req, query_args) sum = 0.0 estimations = {} for ticket in tickets: if ticket['status'] in self.closed_states: continue try: estimation = float(ticket[self.remaining_field]) owner = ticket['owner'] sum += estimation if owner in estimations: estimations[owner] += estimation else: estimations[owner] = estimation except: pass estimations_string = [] labels = [] for owner, estimation in estimations.iteritems(): # Note: Unconditional obfuscation of owner in case it represents # an email adress, and as the chart API doesn't support SSL # (plain http transfer only, from either client or server). labels.append("%s %g%s" % (obfuscate_email_address(owner), round(estimation, 2), self.estimation_suffix)) estimations_string.append(str(int(estimation))) # Title title = 'Workload' # calculate remaining work time if options.get('today') and options.get('enddate'): currentdate = options['today'] day = timedelta(days=1) days_remaining = 0 while currentdate <= options['enddate']: if currentdate.weekday() < 5: days_remaining += 1 currentdate += day title += ' %g%s (~%s workdays left)' % (round(sum, 2), self.estimation_suffix, days_remaining) chart_args = unicode_urlencode( {'chs': '%sx%s' % (options['width'], options['height']), 'chf': 'bg,s,00000000', 'chd': 't:%s' % ",".join(estimations_string), 'cht': 'p3', 'chtt': title, 'chl': "|".join(labels), 'chco': options['color']}) self.log.debug("WorkloadChart data: %s", chart_args) if self.serverside_charts: return tag.image( src="%s?data=%s" % (req.href.estimationtools('chart'), unicode_quote(chart_args)), alt="Workload Chart (server)") else: return tag.image( src="http://chart.googleapis.com/chart?%s" % chart_args, alt="Workload Chart (client)")
def runTest(self): self._tester.login_as(Usernames.admin) page_url = self._tester.url + '/admin/agilo/teams' #TODO: find a way to test with umlaut without compromising the encoding team_name = self.classname() + 'Team' utf8_team_name = team_name.encode('UTF-8') team_desc = "'''Testdescription'''" self._tester.create_new_team(team_name) # set description tc.fv('modcomp', 'description', team_desc) tc.submit('save') # back at list view tc.url(page_url) tc.code(200) # add a new team and team member #TODO: find a way to test team names with umlaut without assuming the # locale of the testing system is UTF-8.de_DE member_name = self.classname() + 'Member' utf8_member_name = member_name.encode('UTF-8') member_desc = "Goldmember" team_name2 = "T-team" tc.fv('addteam', 'name', team_name2) tc.submit('add') tc.code(200) tc.fv('modcomp', 'team_member', utf8_member_name) tc.fv('modcomp', 'member_description', member_desc) tc.submit('add') tc.code(200) try: # IF the accountmanagerplugin is enabled has to appear # the Confirm user creation frame tc.find('Create new user') except TwillAssertionError: account_manager_plugin_enabled = False else: account_manager_plugin_enabled = True tc.fv('modcomp', 'createUser_ok', 'click') tc.submit('createUser_ok') tc.code(200) # correct team selected? tc.find('<option selected="selected">%s</option>' % team_name2) tc.find(utf8_member_name) tc.find(member_desc) # set new value for mondays and tuesdays tc.fv('modcomp', 'ts_0', '0') tc.fv('modcomp', 'ts_1', '') tc.submit('save') # back at team page tc.url("%s/%s" % (page_url, quote(team_name2))) tc.find(utf8_member_name) # three days x 6h tc.find('18.0h') user_not_confirmed = None if account_manager_plugin_enabled: # start to add a new user as team member but cancel at last user_not_confirmed = 'user-not-confirmed' tc.fv('modcomp', 'team_member', user_not_confirmed) tc.submit('add') # abort user creation tc.find('Create new user') tc.fv('modcomp', 'createUser_cancel', 'click') tc.submit('createUser_cancel') # back at team page tc.url("%s/%s" % (page_url, quote(team_name2))) tc.find(utf8_member_name) if user_not_confirmed is not None: tc.notfind(user_not_confirmed) # change team tc.follow(utf8_member_name) tc.fv('modcomp', 'team', utf8_team_name) tc.submit('save') tc.notfind(utf8_member_name) tc.go("%s/%s" % (page_url, unicode_quote(team_name))) tc.find(utf8_member_name) # --------- unassigned team members functionality -------- # add another team member to team 1 member_name2 = 'member #2' tc.fv('modcomp', 'team_member', member_name2) tc.submit('add') if account_manager_plugin_enabled: tc.find('Create new user') tc.find(member_name2) tc.fv('modcomp', 'createUser_ok', 'click') tc.submit('createUser_ok') tc.fv('modcomp', 'member_description', '') tc.submit('save') # back at team list, delete first team member tc.url("%s/%s" % (page_url, unicode_quote(team_name))) tc.fv('modcomp', 'delete', utf8_member_name) tc.submit('save') # same url, should not list this member anymore tc.url("%s/%s" % (page_url, unicode_quote(team_name))) tc.notfind(utf8_member_name) # select the other one but cancel back to team list. Formvalue # must use True here because Twill treats checkboxes differently # when there are several with the same name or only one tc.fv('modcomp', 'delete', True) tc.submit('cancel') tc.go(page_url) tc.follow('Unassigned team members') # shows the member we deleted, not the one we canceled on tc.url(page_url + "/unassigned") tc.find(utf8_member_name) tc.notfind(member_name2) # completely delete the team member tc.fv('modcomp', 'delete', True) tc.submit('remove') # no team members without teams anymore tc.url(page_url) tc.notfind('unassigned') # now delete the teams tc.fv('team_table', 'delete', utf8_team_name) tc.fv('team_table', 'delete', team_name2) tc.submit() tc.notfind(utf8_team_name) tc.notfind(team_name2) tc.find('unassigned')
def expand_macro(self, formatter, name, content): # args will be null if the macro is called without parenthesis. if not content: return '' # parse arguments # we expect the 1st argument to be a filename (filespec) args = [stripws(arg) for arg in self._split_args_re.split(content)[1::2]] # strip unicode white-spaces and ZWSPs are copied from attachments # section (#10668) filespec = args.pop(0) # style information attr = {} style = {} link = '' # helper for the special case `source:` # from trac.versioncontrol.web_ui import BrowserModule # FIXME: somehow use ResourceSystem.get_known_realms() # ... or directly trac.wiki.extract_link try: browser_links = [res[0] for res in BrowserModule(self.env).get_link_resolvers()] except Exception: browser_links = [] while args: arg = args.pop(0) if self._size_re.match(arg): # 'width' keyword attr['width'] = arg elif arg == 'nolink': link = None elif arg.startswith('link='): val = arg.split('=', 1)[1] elt = extract_link(self.env, formatter.context, val.strip()) elt = find_element(elt, 'href') link = None if elt is not None: link = elt.attrib.get('href') elif arg in ('left', 'right'): style['float'] = arg elif arg == 'center': style['margin-left'] = style['margin-right'] = 'auto' style['display'] = 'block' style.pop('margin', '') elif arg in ('top', 'bottom', 'middle'): style['vertical-align'] = arg else: match = self._attr_re.match(arg) if match: key, val = match.groups() if (key == 'align' and val in ('left', 'right', 'center')) or \ (key == 'valign' and val in ('top', 'middle', 'bottom')): args.append(val) elif key in ('margin-top', 'margin-bottom'): style[key] = ' %dpx' % int(val) elif key in ('margin', 'margin-left', 'margin-right') \ and 'display' not in style: style[key] = ' %dpx' % int(val) elif key == 'border': style['border'] = ' %dpx solid' % int(val) else: m = self._quoted_re.search(val) # unquote "..." and '...' if m: val = m.group(1) attr[str(key)] = val # will be used as a __call__ kwd if self._quoted_re.match(filespec): filespec = filespec.strip('\'"') # parse filespec argument to get realm and id if contained. parts = [i.strip('''['"]''') for i in self._split_filespec_re.split(filespec)[1::2]] url = raw_url = desc = None attachment = None if parts and parts[0] in ('http', 'https', 'ftp', 'data'): # absolute raw_url = url = filespec desc = url.rsplit('?')[0] elif filespec.startswith('//'): # server-relative raw_url = url = filespec[1:] desc = url.rsplit('?')[0] elif filespec.startswith('/'): # project-relative params = '' if '?' in filespec: filespec, params = filespec.rsplit('?', 1) url = formatter.href(filespec) if params: url += '?' + params raw_url, desc = url, filespec elif len(parts) == 3: # realm:id:attachment-filename # # or intertrac:realm:id realm, id, filename = parts intertrac_target = "%s:%s" % (id, filename) it = formatter.get_intertrac_url(realm, intertrac_target) if it: url, desc = it raw_url = url + unicode_quote('?format=raw') else: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 2: realm, filename = parts if realm in browser_links: # source:path # TODO: use context here as well rev = None if '@' in filename: filename, rev = filename.rsplit('@', 1) url = formatter.href.browser(filename, rev=rev) raw_url = formatter.href.browser(filename, rev=rev, format='raw') desc = filespec else: # #ticket:attachment or WikiPage:attachment # FIXME: do something generic about shorthand forms... realm = None id, filename = parts if id and id[0] == '#': realm = 'ticket' id = id[1:] elif id == 'htdocs': raw_url = url = formatter.href.chrome('site', filename) desc = os.path.basename(filename) elif id == 'shared': raw_url = url = formatter.href.chrome('shared', filename) desc = os.path.basename(filename) else: realm = 'wiki' if realm: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 1: # it's an attachment of the current resource attachment = formatter.resource.child('attachment', filespec) else: raise TracError(_("No filespec given")) if attachment and 'ATTACHMENT_VIEW' in formatter.perm(attachment): url = get_resource_url(self.env, attachment, formatter.href) raw_url = get_resource_url(self.env, attachment, formatter.href, format='raw') try: desc = get_resource_summary(self.env, attachment) except ResourceNotFound: raw_url = chrome_resource_path(formatter.context.req, 'common/attachment.png') desc = _('No image "%(id)s" attached to %(parent)s', id=attachment.id, parent=get_resource_name(self.env, attachment.parent)) for key in ('title', 'alt'): if desc and key not in attr: attr[key] = desc if style: attr['style'] = '; '.join('%s:%s' % (k, escape(v)) for k, v in style.iteritems()) result = tag.img(src=raw_url, **attr) if link is not None: result = tag.a(result, href=link or url, style='padding:0; border:none') return result
def test_unicode_quote(self): self.assertEqual(u'the%20%C3%9C%20thing', unicode_quote(u'the Ü thing')) self.assertEqual(u'%2520%C3%9C%20%2520', unicode_quote(u'%20Ü %20'))
def expand_macro(self, formatter, name, content): # args will be null if the macro is called without parenthesis. if not content: return '' # parse arguments # we expect the 1st argument to be a filename (filespec) args = content.split(',') if len(args) == 0: raise Exception("No argument.") filespec = args.pop(0) # style information size_re = re.compile('[0-9]+(%|px)?$') attr_re = re.compile('(align|valign|border|width|height|alt' '|margin(?:-(?:left|right|top|bottom))?' '|title|longdesc|class|id|usemap)=(.+)') quoted_re = re.compile("(?:[\"'])(.*)(?:[\"'])$") attr = {} style = {} link = '' # helper for the special case `source:` # from trac.versioncontrol.web_ui import BrowserModule # FIXME: somehow use ResourceSystem.get_known_realms() # ... or directly trac.wiki.extract_link try: browser_links = [res[0] for res in BrowserModule(self.env).get_link_resolvers()] except Exception: browser_links = [] while args: arg = args.pop(0).strip() if size_re.match(arg): # 'width' keyword attr['width'] = arg elif arg == 'nolink': link = None elif arg.startswith('link='): val = arg.split('=', 1)[1] elt = extract_link(self.env, formatter.context, val.strip()) elt = find_element(elt, 'href') link = None if elt is not None: link = elt.attrib.get('href') elif arg in ('left', 'right'): style['float'] = arg elif arg == 'center': style['margin-left'] = style['margin-right'] = 'auto' style['display'] = 'block' style.pop('margin', '') elif arg in ('top', 'bottom', 'middle'): style['vertical-align'] = arg else: match = attr_re.match(arg) if match: key, val = match.groups() if (key == 'align' and val in ('left', 'right', 'center')) or \ (key == 'valign' and \ val in ('top', 'middle', 'bottom')): args.append(val) elif key in ('margin-top', 'margin-bottom'): style[key] = ' %dpx' % int(val) elif key in ('margin', 'margin-left', 'margin-right') \ and 'display' not in style: style[key] = ' %dpx' % int(val) elif key == 'border': style['border'] = ' %dpx solid' % int(val) else: m = quoted_re.search(val) # unquote "..." and '...' if m: val = m.group(1) attr[str(key)] = val # will be used as a __call__ kwd # parse filespec argument to get realm and id if contained. parts = filespec.split(':') url = raw_url = desc = None attachment = None if (parts and parts[0] in ('http', 'https', 'ftp')): # absolute raw_url = url = desc = filespec elif filespec.startswith('//'): # server-relative raw_url = url = desc = filespec[1:] elif filespec.startswith('/'): # project-relative # use href, but unquote to allow args (use default html escaping) raw_url = url = desc = unquote(formatter.href(filespec)) elif len(parts) == 3: # realm:id:attachment-filename # # or intertrac:realm:id realm, id, filename = parts intertrac_target = "%s:%s" % (id, filename) it = formatter.get_intertrac_url(realm, intertrac_target) if it: url, desc = it raw_url = url + unicode_quote('?format=raw') else: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 2: realm, filename = parts if realm in browser_links: # source:path # TODO: use context here as well rev = None if '@' in filename: filename, rev = filename.rsplit('@', 1) url = formatter.href.browser(filename, rev=rev) raw_url = formatter.href.browser(filename, rev=rev, format='raw') desc = filespec else: # #ticket:attachment or WikiPage:attachment # FIXME: do something generic about shorthand forms... realm = None id, filename = parts if id and id[0] == '#': realm = 'ticket' id = id[1:] elif id == 'htdocs': raw_url = url = formatter.href.chrome('site', filename) desc = os.path.basename(filename) else: realm = 'wiki' if realm: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 1: # it's an attachment of the current resource attachment = formatter.resource.child('attachment', filespec) else: raise TracError('No filespec given') if attachment and 'ATTACHMENT_VIEW' in formatter.perm(attachment): url = get_resource_url(self.env, attachment, formatter.href) raw_url = get_resource_url(self.env, attachment, formatter.href, format='raw') try: desc = get_resource_summary(self.env, attachment) except ResourceNotFound, e: raw_url = formatter.href.chrome('common/attachment.png') desc = _('No image "%(id)s" attached to %(parent)s', id=attachment.id, parent=get_resource_name(self.env, attachment.parent))
def dispatch(self, req): """Find a registered handler that matches the request and let it process it. In addition, this method initializes the data dictionary passed to the the template and adds the web site chrome. """ self.log.debug('Dispatching %r', req) chrome = Chrome(self.env) try: # Select the component that should handle the request chosen_handler = None for handler in self._request_handlers.values(): if handler.match_request(req): chosen_handler = handler break if not chosen_handler and req.path_info in ('', '/'): chosen_handler = self._get_valid_default_handler(req) # pre-process any incoming request, whether a handler # was found or not self.log.debug("Chosen handler is %s", chosen_handler) chosen_handler = self._pre_process_request(req, chosen_handler) if not chosen_handler: if req.path_info.endswith('/'): # Strip trailing / and redirect target = unicode_quote(req.path_info.rstrip('/')) if req.query_string: target += '?' + req.query_string req.redirect(req.href + target, permanent=True) raise HTTPNotFound('No handler matched request to %s', req.path_info) req.callbacks['chrome'] = partial(chrome.prepare_request, handler=chosen_handler) # Protect against CSRF attacks: we validate the form token # for all POST requests with a content-type corresponding # to form submissions if req.method == 'POST': ctype = req.get_header('Content-Type') if ctype: ctype, options = cgi.parse_header(ctype) if ctype in ('application/x-www-form-urlencoded', 'multipart/form-data') and \ req.args.get('__FORM_TOKEN') != req.form_token: if self.env.secure_cookies and req.scheme == 'http': msg = _('Secure cookies are enabled, you must ' 'use https to submit forms.') else: msg = _('Do you have cookies enabled?') raise HTTPBadRequest( _('Missing or invalid form token.' ' %(msg)s', msg=msg)) # Process the request and render the template resp = chosen_handler.process_request(req) if resp: resp = self._post_process_request(req, *resp) template, data, metadata, method = resp if 'hdfdump' in req.args: req.perm.require('TRAC_ADMIN') # debugging helper - no need to render first out = io.BytesIO() pprint( { 'template': template, 'metadata': metadata, 'data': data }, out) req.send(out.getvalue(), 'text/plain') self.log.debug("Rendering response with template %s", template) iterable = chrome.use_chunked_encoding if isinstance(metadata, dict): iterable = metadata.setdefault('iterable', iterable) content_type = metadata.get('content_type') else: content_type = metadata output = chrome.render_template(req, template, data, metadata, iterable=iterable, method=method) # TODO (1.5.1) remove iterable and method parameters req.send(output, content_type or 'text/html') else: self.log.debug("Empty or no response from handler. " "Entering post_process_request.") self._post_process_request(req) except RequestDone: raise except Exception as e: # post-process the request in case of errors err = sys.exc_info() try: self._post_process_request(req) except RequestDone: pass except TracError as e2: self.log.warning( "Exception caught while post-processing" " request: %s", exception_to_unicode(e2)) except Exception as e2: if not (type(e) is type(e2) and e.args == e2.args): self.log.error( "Exception caught while post-processing" " request: %s", exception_to_unicode(e2, traceback=True)) if isinstance(e, PermissionError): raise HTTPForbidden(e) if isinstance(e, ResourceNotFound): raise HTTPNotFound(e) if isinstance(e, NotImplementedError): tb = traceback.extract_tb(err[2])[-1] self.log.warning("%s caught from %s:%d in %s: %s", e.__class__.__name__, tb[0], tb[1], tb[2], to_unicode(e) or "(no message)") raise HTTPInternalServerError(TracNotImplementedError(e)) if isinstance(e, TracError): raise HTTPInternalServerError(e) raise err[0], err[1], err[2]
def test_unicode_unquote(self): u = u'the Ü thing' up = u'%20Ü %20' self.assertEqual(u, unicode_unquote(unicode_quote(u))) self.assertEqual(up, unicode_unquote(unicode_quote(up)))
def __init__(self): # get base api url api_url = unicode_quote(self.job_url, '/%:@') if api_url and api_url[-1] != '/': api_url += '/' api_url += 'api/python' # set up http authentication if self.username and self.api_token: handlers = [ self.HTTPOpenHandlerBasicAuthNoChallenge( self.username, self.api_token) ] elif self.username and self.password: pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() pwd_mgr.add_password(None, api_url, self.username, self.password) b_auth = urllib2.HTTPBasicAuthHandler(pwd_mgr) d_auth = urllib2.HTTPDigestAuthHandler(pwd_mgr) handlers = [b_auth, d_auth, self.HudsonFormLoginHandler(self)] else: handlers = [] self.url_opener = urllib2.build_opener(*handlers) if handlers: self.env.log.debug("registered auth-handlers for '%s', " \ "username='******'", api_url, self.username) # construct tree=... parameter to query for the desired items tree = '%(b)s' if self.disp_mod: tree += ',modules[%(b)s]' if '/job/' not in api_url: tree = 'jobs[' + tree + ']' items = 'builds[building,timestamp,duration,result,description,url,' \ 'fullDisplayName' elems = [] if self.list_changesets: elems.append('revision') elems.append('id') if 'author' in self.disp_culprit or 'authors' in self.disp_culprit: elems.append('user') elems.append('author[fullName]') if elems: items += ',changeSet[items[%s]]' % ','.join(elems) if 'culprit' in self.disp_culprit or 'culprits' in self.disp_culprit: items += ',culprits[fullName]' if 'starter' in self.disp_culprit: items += ',actions[causes[userName]]' items += ']' # assemble final url tree = tree % {'b': items} self.info_url = '%s?tree=%s' % (api_url, tree) self.env.log.debug("Build-info url: '%s'", self.info_url)
def expand_macro(self, formatter, name, content, args=None): # prepare options req = formatter.req options, query_args = parse_options(self.env, content, copy.copy(DEFAULT_OPTIONS)) if not options['startdate']: raise TracError("No start date specified!") # minimum time frame is one day if options['startdate'] >= options['enddate']: options['enddate'] = options['startdate'] + timedelta(days=1) # calculate data timetable = self._calculate_timetable(options, query_args, req) timetable_spent = self._calculate_timetable_spent(options, query_args, req) # remove weekends if not options['weekends']: for date in timetable.keys(): if date.weekday() >= 5: del timetable[date] del timetable_spent[date] # scale data xdata, ydata, maxhours = self._scale_data(timetable, options) xdata_spent, ydata_spent, maxhours_spent = self._scale_data(timetable_spent, options) if not options['spent']: spentdata = "|0,0|0,0" else: spentdata = "|%s|%s" % (",".join(xdata_spent), ",".join(ydata_spent)) # build html for google chart api dates = sorted(timetable.keys()) bottomaxis = "0:|" + "|".join([str(date.day) for date in dates]) + \ "|1:|%s/%s|%s/%s" % (dates[0].month, dates[0].year, dates[- 1].month, dates[- 1].year) leftaxis = "2,0,%s" % maxhours # add line for expected progress if options['expected'] == '0': expecteddata = "" else: expecteddata = "|0,100|%s,0" % ( round(Decimal(options['expected']) * 100 / maxhours, 2)) # prepare gridlines if options['gridlines'] == '0': # create top and right bounding line by using grid gridlinesdata = "100.0,100.0,1,0" else: gridlinesdata = "%s,%s" % (xdata[1], ( round(Decimal(options['gridlines']) * 100 / maxhours, 4))) # mark weekends weekends = [] saturday = None index = 0 halfday = self._round(Decimal("0.5") / (len(dates) - 1)) for date in dates: if date.weekday() == 5: saturday = index if saturday and date.weekday() == 6: weekends.append("R,%s,0,%s,%s" % (options['wecolor'], self._round((Decimal( xdata[saturday]) / 100) - halfday), self._round( (Decimal(xdata[index]) / 100) + halfday))) saturday = None index += 1 # special handling if time period starts with Sundays... if len(dates) > 0 and dates[0].weekday() == 6: weekends.append("R,%s,0,0.0,%s" % (options['wecolor'], halfday)) # or ends with Saturday if len(dates) > 0 and dates[- 1].weekday() == 5: weekends.append( "R,%s,0,%s,1.0" % (options['wecolor'], Decimal(1) - halfday)) # chart title title = options.get('title', None) if title is None and options.get('milestone'): title = options['milestone'].split('|')[0] chart_args = unicode_urlencode( {'chs': '%sx%s' % (options['width'], options['height']), 'chf': 'c,s,%s|bg,s,00000000' % options['bgcolor'], 'chd': 't:%s|%s%s%s' % (",".join(xdata), ",".join(ydata), spentdata, expecteddata), 'cht': 'lxy', 'chxt': 'x,x,y', 'chxl': bottomaxis, 'chxr': leftaxis, 'chm': "|".join(weekends), 'chg': gridlinesdata, 'chco': '%s,%s,%s' % (options['color'], options['colorspent'], options['colorexpected']), 'chdl': 'Remaining|Spent|Estimated', 'chtt': title}) self.log.debug("BurndownChart data: %s", chart_args) if self.serverside_charts: return tag.image( src="%s?data=%s" % (req.href.estimationtools('chart'), unicode_quote(chart_args)), alt="Burndown Chart (server)") else: return tag.image( src="http://chart.googleapis.com/chart?%s" % chart_args, alt="Burndown Chart (client)")
def expand_macro(self, formatter, name, content): # args will be null if the macro is called without parenthesis. if not content: return '' # parse arguments # we expect the 1st argument to be a filename (filespec) args = content.split(',') if len(args) == 0: raise Exception("No argument.") # strip unicode white-spaces and ZWSPs are copied from attachments # section (#10668) filespec = stripws(args.pop(0)) # style information size_re = re.compile('[0-9]+(%|px)?$') attr_re = re.compile('(align|valign|border|width|height|alt' '|margin(?:-(?:left|right|top|bottom))?' '|title|longdesc|class|id|usemap)=(.+)') quoted_re = re.compile("(?:[\"'])(.*)(?:[\"'])$") attr = {} style = {} link = '' # helper for the special case `source:` # from trac.versioncontrol.web_ui import BrowserModule # FIXME: somehow use ResourceSystem.get_known_realms() # ... or directly trac.wiki.extract_link try: browser_links = [res[0] for res in BrowserModule(self.env).get_link_resolvers()] except Exception: browser_links = [] while args: arg = args.pop(0).strip() if size_re.match(arg): # 'width' keyword attr['width'] = arg elif arg == 'nolink': link = None elif arg.startswith('link='): val = arg.split('=', 1)[1] elt = extract_link(self.env, formatter.context, val.strip()) elt = find_element(elt, 'href') link = None if elt is not None: link = elt.attrib.get('href') elif arg in ('left', 'right'): style['float'] = arg elif arg == 'center': style['margin-left'] = style['margin-right'] = 'auto' style['display'] = 'block' style.pop('margin', '') elif arg in ('top', 'bottom', 'middle'): style['vertical-align'] = arg else: match = attr_re.match(arg) if match: key, val = match.groups() if (key == 'align' and val in ('left', 'right', 'center')) or \ (key == 'valign' and \ val in ('top', 'middle', 'bottom')): args.append(val) elif key in ('margin-top', 'margin-bottom'): style[key] = ' %dpx' % int(val) elif key in ('margin', 'margin-left', 'margin-right') \ and 'display' not in style: style[key] = ' %dpx' % int(val) elif key == 'border': style['border'] = ' %dpx solid' % int(val) else: m = quoted_re.search(val) # unquote "..." and '...' if m: val = m.group(1) attr[str(key)] = val # will be used as a __call__ kwd # parse filespec argument to get realm and id if contained. parts = filespec.split(':') url = raw_url = desc = None attachment = None if (parts and parts[0] in ('http', 'https', 'ftp')): # absolute raw_url = url = filespec desc = url.rsplit('?')[0] elif filespec.startswith('//'): # server-relative raw_url = url = filespec[1:] desc = url.rsplit('?')[0] elif filespec.startswith('/'): # project-relative params = '' if '?' in filespec: filespec, params = filespec.rsplit('?', 1) url = formatter.href(filespec) if params: url += '?' + params raw_url, desc = url, filespec elif len(parts) == 3: # realm:id:attachment-filename # # or intertrac:realm:id realm, id, filename = parts intertrac_target = "%s:%s" % (id, filename) it = formatter.get_intertrac_url(realm, intertrac_target) if it: url, desc = it raw_url = url + unicode_quote('?format=raw') else: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 2: realm, filename = parts if realm in browser_links: # source:path # TODO: use context here as well rev = None if '@' in filename: filename, rev = filename.rsplit('@', 1) url = formatter.href.browser(filename, rev=rev) raw_url = formatter.href.browser(filename, rev=rev, format='raw') desc = filespec else: # #ticket:attachment or WikiPage:attachment # FIXME: do something generic about shorthand forms... realm = None id, filename = parts if id and id[0] == '#': realm = 'ticket' id = id[1:] elif id == 'htdocs': raw_url = url = formatter.href.chrome('site', filename) desc = os.path.basename(filename) else: realm = 'wiki' if realm: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 1: # it's an attachment of the current resource attachment = formatter.resource.child('attachment', filespec) else: raise TracError('No filespec given') if attachment and 'ATTACHMENT_VIEW' in formatter.perm(attachment): url = get_resource_url(self.env, attachment, formatter.href) raw_url = get_resource_url(self.env, attachment, formatter.href, format='raw') try: desc = get_resource_summary(self.env, attachment) except ResourceNotFound, e: raw_url = formatter.href.chrome('common/attachment.png') desc = _('No image "%(id)s" attached to %(parent)s', id=attachment.id, parent=get_resource_name(self.env, attachment.parent))
def _get_path(self): path = os.path.join(self.env.path, 'attachments', self.parent_type, unicode_quote(self.parent_id)) if self.filename: path = os.path.join(path, unicode_quote(self.filename)) return os.path.normpath(path)
def dispatch(self, req): """Find a registered handler that matches the request and let it process it. In addition, this method initializes the data dictionary passed to the the template and adds the web site chrome. """ self.log.debug('Dispatching %r', req) chrome = Chrome(self.env) # Setup request callbacks for lazily-evaluated properties req.callbacks.update({ 'authname': self.authenticate, 'chrome': chrome.prepare_request, 'perm': self._get_perm, 'session': self._get_session, 'locale': self._get_locale, 'lc_time': self._get_lc_time, 'tz': self._get_timezone, 'form_token': self._get_form_token, 'use_xsendfile': self._get_use_xsendfile, 'xsendfile_header': self._get_xsendfile_header, }) try: try: # Select the component that should handle the request chosen_handler = None try: for handler in self._request_handlers.values(): if handler.match_request(req): chosen_handler = handler break if not chosen_handler and \ (not req.path_info or req.path_info == '/'): chosen_handler = self._get_valid_default_handler(req) # pre-process any incoming request, whether a handler # was found or not chosen_handler = \ self._pre_process_request(req, chosen_handler) except TracError as e: raise HTTPInternalError(e) if not chosen_handler: if req.path_info.endswith('/'): # Strip trailing / and redirect target = unicode_quote(req.path_info.rstrip('/')) if req.query_string: target += '?' + req.query_string req.redirect(req.href + target, permanent=True) raise HTTPNotFound('No handler matched request to %s', req.path_info) req.callbacks['chrome'] = partial(chrome.prepare_request, handler=chosen_handler) # Protect against CSRF attacks: we validate the form token # for all POST requests with a content-type corresponding # to form submissions if req.method == 'POST': ctype = req.get_header('Content-Type') if ctype: ctype, options = cgi.parse_header(ctype) if ctype in ('application/x-www-form-urlencoded', 'multipart/form-data') and \ req.args.get('__FORM_TOKEN') != req.form_token: if self.env.secure_cookies and req.scheme == 'http': msg = _('Secure cookies are enabled, you must ' 'use https to submit forms.') else: msg = _('Do you have cookies enabled?') raise HTTPBadRequest( _('Missing or invalid form token.' ' %(msg)s', msg=msg)) # Process the request and render the template resp = chosen_handler.process_request(req) if resp: if len(resp) == 2: # old Clearsilver template and HDF data self.log.error( "Clearsilver template are no longer " "supported (%s)", resp[0]) raise TracError( _("Clearsilver templates are no longer supported, " "please contact your Trac administrator.")) # Genshi template, data, content_type, method = \ self._post_process_request(req, *resp) if 'hdfdump' in req.args: req.perm.require('TRAC_ADMIN') # debugging helper - no need to render first out = StringIO() pprint(data, out) req.send(out.getvalue(), 'text/plain') output = chrome.render_template( req, template, data, content_type, method=method, iterable=chrome.use_chunked_encoding) req.send(output, content_type or 'text/html') else: self._post_process_request(req) except RequestDone: raise except: # post-process the request in case of errors err = sys.exc_info() try: self._post_process_request(req) except RequestDone: raise except Exception as e: self.log.error( "Exception caught while post-processing" " request: %s", exception_to_unicode(e, traceback=True)) raise err[0], err[1], err[2] except PermissionError as e: raise HTTPForbidden(e) except ResourceNotFound as e: raise HTTPNotFound(e) except TracError as e: raise HTTPInternalError(e)
def test_unicode_quote(self): self.assertEqual(u"the%20%C3%9C%20thing", unicode_quote(u"the Ü thing")) self.assertEqual(u"%2520%C3%9C%20%2520", unicode_quote(u"%20Ü %20"))
def test_unicode_unquote(self): u = u"the Ü thing" up = u"%20Ü %20" self.assertEqual(u, unicode_unquote(unicode_quote(u))) self.assertEqual(up, unicode_unquote(unicode_quote(up)))
def expand_macro(self, formatter, name, content): args = None if content: content = stripws(content) # parse arguments # we expect the 1st argument to be a filename (filespec) args = [stripws(arg) for arg in self._split_args_re.split(content)[1::2]] if not args: return '' # strip unicode white-spaces and ZWSPs are copied from attachments # section (#10668) filespec = args.pop(0) # style information attr = {} style = {} link = '' # helper for the special case `source:` # from trac.versioncontrol.web_ui import BrowserModule # FIXME: somehow use ResourceSystem.get_known_realms() # ... or directly trac.wiki.extract_link try: browser_links = [res[0] for res in BrowserModule(self.env).get_link_resolvers()] except Exception: browser_links = [] while args: arg = args.pop(0) if self._size_re.match(arg): # 'width' keyword attr['width'] = arg elif arg == 'nolink': link = None elif arg.startswith('link='): val = arg.split('=', 1)[1] elt = extract_link(self.env, formatter.context, val.strip()) elt = find_element(elt, 'href') link = None if elt is not None: link = elt.attrib.get('href') elif arg in ('left', 'right'): style['float'] = arg elif arg == 'center': style['margin-left'] = style['margin-right'] = 'auto' style['display'] = 'block' style.pop('margin', '') elif arg in ('top', 'bottom', 'middle'): style['vertical-align'] = arg else: match = self._attr_re.match(arg) if match: key, val = match.groups() if (key == 'align' and val in ('left', 'right', 'center')) or \ (key == 'valign' and val in ('top', 'middle', 'bottom')): args.append(val) elif key in ('margin-top', 'margin-bottom'): style[key] = ' %dpx' % _arg_as_int(val, key, min=1) elif key in ('margin', 'margin-left', 'margin-right') \ and 'display' not in style: style[key] = ' %dpx' % _arg_as_int(val, key, min=1) elif key == 'border': style['border'] = ' %dpx solid' % _arg_as_int(val, key) else: m = self._quoted_re.search(val) # unquote "..." and '...' if m: val = m.group(1) attr[str(key)] = val # will be used as a __call__ kwd if self._quoted_re.match(filespec): filespec = filespec.strip('\'"') # parse filespec argument to get realm and id if contained. parts = [i.strip('\'"') for i in self._split_filespec_re.split(filespec)[1::2]] realm = parts[0] if parts else None url = raw_url = desc = None attachment = None interwikimap = InterWikiMap(self.env) if realm in ('http', 'https', 'ftp', 'data'): # absolute raw_url = url = filespec desc = url.rsplit('?')[0] elif realm in interwikimap: url, desc = interwikimap.url(realm, ':'.join(parts[1:])) raw_url = url elif filespec.startswith('//'): # server-relative raw_url = url = filespec[1:] desc = url.rsplit('?')[0] elif filespec.startswith('/'): # project-relative params = '' if '?' in filespec: filespec, params = filespec.rsplit('?', 1) url = formatter.href(filespec) if params: url += '?' + params raw_url, desc = url, filespec elif len(parts) == 3: # realm:id:attachment-filename # # or intertrac:realm:id realm, id, filename = parts intertrac_target = "%s:%s" % (id, filename) it = formatter.get_intertrac_url(realm, intertrac_target) if it: url, desc = it raw_url = url + unicode_quote('?format=raw') else: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 2: realm, filename = parts if realm in browser_links: # source:path # TODO: use context here as well rev = None if '@' in filename: filename, rev = filename.rsplit('@', 1) url = formatter.href.browser(filename, rev=rev) raw_url = formatter.href.browser(filename, rev=rev, format='raw') desc = filespec else: # #ticket:attachment or WikiPage:attachment # FIXME: do something generic about shorthand forms... realm = None id, filename = parts if id and id[0] == '#': realm = 'ticket' id = id[1:] elif id == 'htdocs': raw_url = url = formatter.href.chrome('site', filename) desc = os.path.basename(filename) elif id == 'shared': raw_url = url = formatter.href.chrome('shared', filename) desc = os.path.basename(filename) else: realm = 'wiki' if realm: attachment = Resource(realm, id).child('attachment', filename) elif len(parts) == 1: # it's an attachment of the current resource attachment = formatter.resource.child('attachment', filespec) else: return system_message(_("No filespec given")) if attachment: try: desc = get_resource_summary(self.env, attachment) except ResourceNotFound: link = None raw_url = chrome_resource_path(formatter.context.req, 'common/attachment.png') desc = _('No image "%(id)s" attached to %(parent)s', id=attachment.id, parent=get_resource_name(self.env, attachment.parent)) else: if 'ATTACHMENT_VIEW' in formatter.perm(attachment): url = get_resource_url(self.env, attachment, formatter.href) raw_url = get_resource_url(self.env, attachment, formatter.href, format='raw') for key in ('title', 'alt'): if desc and key not in attr: attr[key] = desc if style: attr['style'] = '; '.join('%s:%s' % (k, escape(v)) for k, v in style.iteritems()) if not WikiSystem(self.env).is_safe_origin(raw_url, formatter.context.req): attr['crossorigin'] = 'anonymous' # avoid password prompt result = tag.img(src=raw_url, **attr) if link is not None: result = tag.a(result, href=link or url, style='padding:0; border:none') return result
def expand_macro(self, formatter, name, content): req = formatter.req db = self.env.get_db_cnx() # prepare options options, query_args = parse_options(db, content, copy.copy(DEFAULT_OPTIONS)) query_args[self.estimation_field + "!"] = None tickets = execute_query(self.env, req, query_args) sum = 0.0 estimations = {} for ticket in tickets: if ticket['status'] in self.closed_states: continue try: estimation = float(ticket[self.estimation_field]) owner = ticket['owner'] sum += estimation if estimations.has_key(owner): estimations[owner] += estimation else: estimations[owner] = estimation except: pass estimations_string = [] labels = [] for owner, estimation in estimations.iteritems(): # Note: Unconditional obfuscation of owner in case it represents # an email adress, and as the chart API doesn't support SSL # (plain http transfer only, from either client or server). labels.append("%s %g%s" % (obfuscate_email_address(owner), round( estimation, 2), self.estimation_suffix)) estimations_string.append(str(int(estimation))) # Title title = 'Workload' # calculate remaining work time if options.get('today') and options.get('enddate'): currentdate = options['today'] day = timedelta(days=1) days_remaining = 0 while currentdate <= options['enddate']: if currentdate.weekday() < 5: days_remaining += 1 currentdate += day title += ' %g%s (~%s workdays left)' % (round( sum, 2), self.estimation_suffix, days_remaining) chart_args = unicode_urlencode({ 'chs': '%sx%s' % (options['width'], options['height']), 'chf': 'bg,s,00000000', 'chd': 't:%s' % ",".join(estimations_string), 'cht': 'p3', 'chtt': title, 'chl': "|".join(labels), 'chco': options['color'] }) self.log.debug("WorkloadChart data: %s" % repr(chart_args)) if self.serverside_charts: return tag.image( src="%s?data=%s" % (req.href.estimationtools('chart'), unicode_quote(chart_args)), alt="Workload Chart (server)") else: return tag.image(src="http://chart.googleapis.com/chart?%s" % chart_args, alt="Workload Chart (client)")
def expand_macro(self, formatter, name, content): req = formatter.req db = self.env.get_db_cnx() # prepare options options, query_args = parse_options(db, content, copy.copy(DEFAULT_OPTIONS)) query_args[self.estimation_field + "!"] = None tickets = execute_query(self.env, req, query_args) sum = 0.0 estimations = {} for ticket in tickets: if ticket['status'] in self.closed_states: continue try: estimation = float(ticket[self.estimation_field] or 0.0) if options.get('remainingworkload'): completion_cursor = db.cursor() completion_cursor.execute( "SELECT t.value AS totalhours, c.value AS complete, d.value AS due_close FROM ticket tk LEFT JOIN ticket_custom t ON (tk.id = t.ticket AND t.name = 'totalhours') LEFT JOIN ticket_custom c ON (tk.id = c.ticket AND c.name = 'complete') LEFT JOIN ticket_custom d ON (tk.id = d.ticket AND d.name = 'due_close') WHERE tk.id = %s" % ticket['id']) for row in completion_cursor: ticket['totalhours'], ticket['complete'], ticket[ 'due_close'] = row break # skip ticket ticket if due date is later than 'enddate': if options.get('showdueonly'): if not ticket['due_close']: continue # skip tickets with empty ETA when in 'showdueonly' mode due_close = parse_date(ticket['due_close'], ["%Y/%m/%d"]) startdate = options.get('startdate') enddate = options.get('enddate') if startdate and startdate > due_close: continue # skip tickets with ETA in the past if enddate and enddate < due_close: continue # skip tickets with ETA in the future pass totalhours = float(ticket['totalhours'] or 0.0) completed = (float(ticket['complete'] or 0.0) / 100) * estimation completed_hours = min(estimation, max(totalhours, completed)) estimation -= completed_hours pass owner = ticket['owner'] sum += estimation if estimations.has_key(owner): estimations[owner] += estimation else: estimations[owner] = estimation except: raise # Title title = 'Workload' days_remaining = None # calculate remaining work time if options.get('today') and options.get('enddate'): currentdate = options['today'] day = timedelta(days=1) days_remaining = 0 while currentdate <= options['enddate']: if currentdate.weekday() < 5: days_remaining += 1 currentdate += day title += ' %g%s (~%s workdays left)' % (round( sum, 2), self.estimation_suffix, days_remaining) estimations_string = [] labels = [] workhoursperday = max(float(options.get('workhoursperday')), 0.0) chts = '000000' for owner, estimation in estimations.iteritems(): # Note: Unconditional obfuscation of owner in case it represents # an email adress, and as the chart API doesn't support SSL # (plain http transfer only, from either client or server). label = "%s %g%s" % (obfuscate_email_address(owner), round(estimation, 2), self.estimation_suffix) if days_remaining != None: user_remaining_hours = days_remaining * workhoursperday if not user_remaining_hours or (estimation / user_remaining_hours) > 1: label = "%s (~%g hours left)!" % ( label, round(user_remaining_hours, 2) ) # user does not have enough hours left chts = 'FF0000' # set chart title style to red pass pass labels.append(label) estimations_string.append(str(int(estimation))) pass chart_args = unicode_urlencode({ 'chs': '%sx%s' % (options['width'], options['height']), 'chf': 'bg,s,00000000', 'chd': 't:%s' % ",".join(estimations_string), 'cht': 'p3', 'chtt': title, 'chts': chts, 'chl': "|".join(labels), 'chco': options['color'] }) self.log.debug("WorkloadChart data: %s" % repr(chart_args)) if self.serverside_charts: return tag.image( src="%s?data=%s" % (req.href.estimationtools('chart'), unicode_quote(chart_args)), alt="Workload Chart (server)") else: return tag.image(src="https://chart.googleapis.com/chart?%s" % chart_args, alt="Workload Chart (client)")