#!/usr/bin/env python3 # This file is part of Elixir, a source code cross-referencer. # # Copyright (C) 2017--2020 Mikaƫl Bouillot # and contributors. # # Elixir is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Elixir 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with Elixir. If not, see . from io import StringIO from urllib import parse realprint = print outputBuffer = StringIO() def print(arg, end='\n'): global outputBuffer outputBuffer.write(arg + end) import cgitb import cgi import os import re from re import search, sub # Create /tmp/elixir-errors if not existing yet (could happen after a reboot) errdir = '/tmp/elixir-errors' if not(os.path.isdir(errdir)): os.makedirs(errdir, exist_ok=True) # Enable CGI Trackback Manager for debugging (https://docs.python.org/fr/3/library/cgitb.html) cgitb.enable(display=0, logdir=errdir, format='text') ident = '' status = 200 url = os.environ.get('REQUEST_URI') or os.environ.get('SCRIPT_URL') # Split the URL into its components (project, version, cmd, arg) m = search('^/([^/]*)/([^/]*)(?:/([^/]))?/([^/]*)(.*)$', url) if m: project = m.group(1) version = m.group(2) version_decoded = parse.unquote(version) family = m.group(3) cmd = m.group(4) arg = m.group(5) if family == None: family = 'C' basedir = os.environ['LXR_PROJ_DIR'] datadir = basedir + '/' + project + '/data' repodir = basedir + '/' + project + '/repo' if not(os.path.exists(datadir)) or not(os.path.exists(repodir)): status = 400 elif cmd == 'source': path = arg if len(path) > 0 and path[-1] == '/': path = path[:-1] status = 301 location = '/'+project+'/'+version+'/source'+path else: mode = 'source' if not search('^[A-Za-z0-9_/.,+-]*$', path): status = 400 url = 'source'+path elif cmd == 'ident': ident = arg[1:] form = cgi.FieldStorage() ident2 = form.getvalue('i') family2 = form.getvalue('f') if ident == '' and ident2: status = 302 ident2 = parse.quote(ident2.strip()) location = '/'+project+'/'+version+'/'+family2+'/ident/'+ident2 else: mode = 'ident' if not(ident and search('^[A-Za-z0-9_-]*$', ident)): ident = '' url = family + '/ident/' + ident else: status = 400 else: status = 400 if status == 301: realprint('Status: 301 Moved Permanently') realprint('Location: '+location+'\n') exit() elif status == 302: realprint('Status: 302 Found') realprint('Location: '+location+'\n') exit() elif status == 400: realprint('Status: 400 Bad Request\n') exit() os.environ['LXR_DATA_DIR'] = datadir os.environ['LXR_REPO_DIR'] = repodir projects = [] for (dirpath, dirnames, filenames) in os.walk(basedir): projects.extend(dirnames) break projects.sort() import sys sys.path = [ sys.path[0] + '/..' ] + sys.path from query import query if version_decoded == 'latest': tag = query('latest') else: tag = version_decoded data = { 'baseurl': '/' + project + '/', 'tag': tag, 'version': version, 'url': url, 'project': project, 'projects': projects, 'ident': ident, 'family': family, 'breadcrumb': '/' } versions = query('versions') v = '' b = 1 for topmenu in versions: submenus = versions[topmenu] v += '
  • \n' v += '\t'+topmenu+'\n' v += '\t
      \n' b += 1 for submenu in submenus: tags = submenus[submenu] if submenu == tags[0] and len(tags) == 1: if submenu == tag: v += '\t\t\n' else: v += '\t\t\n' else: v += '\t\t
    • \n' v += '\t\t\t'+submenu+'\n' v += '\t\t\t
        \n' for _tag in tags: _tag_encoded = parse.quote(_tag, safe='') if _tag == tag: v += '\t\t\t\t\n' else: v += '\t\t\t\t\n' v += '\t\t\t
    • \n' v += '\t
  • \n' data['versions'] = v title_suffix = project.capitalize()+' source code ('+tag+') - Bootlin' if mode == 'source': path_split = path.split('/')[1:] path_temp = '' links = [] for p in path_split: path_temp += '/'+p links.append(''+p+'') if links: data['breadcrumb'] += '/'.join(links) data['ident'] = ident # Create titles like this: # root path: "Linux source code (v5.5.6) - Bootlin" # first level path: "arch - Linux source code (v5.5.6) - Bootlin" # deeper paths: "Makefile - arch/um/Makefile - Linux source code (v5.5.6) - Bootlin" data['title'] = ('' if path == '' else path_split[0]+' - ' if len(path_split) == 1 else path_split[-1]+' - '+'/'.join(path_split)+' - ') \ +title_suffix lines = ['null - - -'] type = query('type', tag, path) if len(type) > 0: if type == 'tree': lines += query('dir', tag, path) elif type == 'blob': code = query('file', tag, path) else: print('

    This file does not exist.

    ') status = 404 if type == 'tree': if path != '': lines[0] = 'back - - -' print('
    ') print('\n') for l in lines: type, name, size, perm = l.split(' ') if type == 'null': continue elif type == 'tree': size = '' path2 = path+'/'+name name = name elif type == 'blob': size = size+' bytes' path2 = path+'/'+name if perm == '120000': # 120000 permission means it's a symlink # So we need to handle that correctly dir_name = os.path.dirname(path) rel_path = query('file', tag, path2) if dir_name != '/': dir_name += '/' path2 = os.path.abspath(dir_name + rel_path) name = name + ' -> ' + path2 elif type == 'back': size = '' path2 = os.path.dirname(path[:-1]) if path2 == '/': path2 = '' name = 'Parent directory' print(' \n') print(' \n') print(' \n') print(' \n') print('
    '+name+''+size+'
    ', end='') print('
    ') elif type == 'blob': import pygments import pygments.lexers import pygments.formatters fname = os.path.basename(path) filename, extension = os.path.splitext(fname) extension = extension[1:].lower() family = query('family', fname) data['family'] = family # Source common filter definitions os.chdir('filters') exec(open("common.py").read()) # Source project specific filters f = project + '.py' if os.path.isfile(f): exec(open(f).read()) os.chdir('..') # Apply filters for f in filters: c = f['case'] if (c == 'any' or (c == 'filename' and filename in f['match']) or (c == 'extension' and extension in f['match'])): apply_filter = True if 'path_exceptions' in f: for p in f['path_exceptions']: if re.match(p, path): apply_filter = False break if apply_filter: code = sub(f ['prerex'], f ['prefunc'], code, flags=re.MULTILINE) try: lexer = pygments.lexers.guess_lexer_for_filename(path, code) except: lexer = pygments.lexers.get_lexer_by_name('text') lexer.stripnl = False formatter = pygments.formatters.HtmlFormatter(linenos=True, anchorlinenos=True) result = pygments.highlight(code, lexer, formatter) # Replace line numbers by links to the corresponding line in the current file result = sub('href="#-(\d+)', 'name="L\\1" id="L\\1" href="'+version+'/source'+path+'#L\\1', result) for f in filters: c = f['case'] if (c == 'any' or (c == 'filename' and filename in f['match']) or (c == 'extension' and extension in f['match'])): result = sub(f ['postrex'], f ['postfunc'], result) print('
    ' + result + '
    ') elif mode == 'ident': data['title'] = ident+' identifier - '+title_suffix symbol_definitions, symbol_references, symbol_doccomments = query('ident', tag, ident, family) print('
    ') if len(symbol_definitions): print('

    Defined in '+str(len(symbol_definitions))+' files:

    ') print('
      ') for symbol_definition in symbol_definitions: print('
    • {f}, line {n} (as a {t})'.format( v=version, f=symbol_definition.path, n=symbol_definition.line, t=symbol_definition.type )) print('
    ') if len(symbol_doccomments): print('

    Documented in '+str(len(symbol_doccomments))+' files:

    ') print('
      ') for symbol_doccomment in symbol_doccomments: print('
    • {f}, line {n}
    • '.format( v=version, f=symbol_doccomment.path, n=symbol_doccomment.line )) print('
    ') print('

    Referenced in '+str(len(symbol_references))+' files:

    ') print('
      ') for symbol_reference in symbol_references: ln = symbol_reference.line.split(',') if len(ln) == 1: n = ln[0] print('
    • {f}, line {n}'.format( v=version, f=symbol_reference.path, n=n )) else: if len(symbol_references) > 100: # Concise display n = len(ln) print('
    • {f}, {n} times'.format( v=version, f=symbol_reference.path, n=n )) else: # Verbose display print('
    • {f}'.format( v=version, f=symbol_reference.path, n=ln[0] )) print('
        ') for n in ln: print('
      • line {n}'.format( v=version, f=symbol_reference.path, n=n )) print('
      ') print('
    ') else: if ident != '': print('

    Identifier not used

    ') status = 404 print('
    ') else: print('Invalid request') if status == 404: realprint('Status: 404 Not Found') import jinja2 loader = jinja2.FileSystemLoader(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../templates/')) environment = jinja2.Environment(loader=loader) template = environment.get_template('layout.html') realprint('Content-Type: text/html;charset=utf-8\n') data['main'] = outputBuffer.getvalue() realprint(template.render(data), end='')