Example #1
0
def listFilterHooks(db, user):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)
    filterhooks = []

    for extension_id, version_id, version_sha1, is_universal in installs:
        if version_id is not None:
            cursor.execute(
                """SELECT 1
                                FROM extensionroles
                                JOIN extensionfilterhookroles ON (role=id)
                               WHERE version=%s""", (version_id, ))

            if not cursor.fetchone():
                continue

            extension = Extension.fromId(db, extension_id)
            manifest = extension.getManifest(sha1=version_sha1)
        else:
            try:
                extension = Extension.fromId(db, extension_id)
            except ExtensionError:
                # If the author/hosting user no longer exists, or the extension
                # directory no longer exists or is inaccessible, ignore the
                # extension.
                continue

            try:
                manifest = Manifest.load(extension.getPath())
            except ManifestError:
                # If the MANIFEST is missing or invalid, we can't know whether
                # the extension has any filter hook roles, so assume it doesn't
                # and ignore it.
                continue

            if not any(
                    isinstance(role, FilterHookRole)
                    for role in manifest.roles):
                continue

        filterhooks.append((extension, manifest,
                            sorted((role for role in manifest.roles
                                    if isinstance(role, FilterHookRole)),
                                   key=lambda role: role.title)))

    return sorted(filterhooks,
                  key=lambda (extension, manifest, roles): extension.getKey())
Example #2
0
def listFilterHooks(db, user):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)
    filterhooks = []

    for extension_id, version_id, version_sha1, is_universal in installs:
        if version_id is not None:
            cursor.execute("""SELECT 1
                                FROM extensionroles
                                JOIN extensionfilterhookroles ON (role=id)
                               WHERE version=%s""",
                           (version_id,))

            if not cursor.fetchone():
                continue

            extension = Extension.fromId(db, extension_id)
            manifest = extension.getManifest(sha1=version_sha1)
        else:
            try:
                extension = Extension.fromId(db, extension_id)
            except ExtensionError:
                # If the author/hosting user no longer exists, or the extension
                # directory no longer exists or is inaccessible, ignore the
                # extension.
                continue

            try:
                manifest = Manifest.load(extension.getPath())
            except ManifestError:
                # If the MANIFEST is missing or invalid, we can't know whether
                # the extension has any filter hook roles, so assume it doesn't
                # and ignore it.
                continue

            if not any(isinstance(role, FilterHookRole)
                       for role in manifest.roles):
                continue

        filterhooks.append((extension, manifest, sorted(
                    (role for role in manifest.roles
                     if isinstance(role, FilterHookRole)),
                    key=lambda role: role.title)))

    return sorted(filterhooks,
                  key=lambda (extension, manifest, roles): extension.getKey())
Example #3
0
def getExtension(author_name, extension_name):
    """Create an Extension object ignoring whether it is valid"""
    try:
        return Extension(author_name, extension_name)
    except ExtensionError as error:
        if error.extension is None:
            raise error
        return error.extension
Example #4
0
def getFilterHookRole(db, filter_id):
    cursor = db.cursor()

    cursor.execute("""SELECT extension, uid, name
                        FROM extensionhookfilters
                       WHERE id=%s""",
                   (filter_id,))

    extension_id, user_id, filterhook_name = cursor.fetchone()

    extension = Extension.fromId(db, extension_id)
    user = dbutils.User.fromId(db, user_id)

    installed_sha1, _ = extension.getInstalledVersion(db, user)

    if installed_sha1 is False:
        return

    manifest = extension.getManifest(sha1=installed_sha1)

    for role in manifest.roles:
        if isinstance(role, FilterHookRole) and role.name == filterhook_name:
            return role
Example #5
0
def getFilterHookRole(db, filter_id):
    cursor = db.cursor()

    cursor.execute(
        """SELECT extension, uid, name
                        FROM extensionhookfilters
                       WHERE id=%s""", (filter_id, ))

    extension_id, user_id, filterhook_name = cursor.fetchone()

    extension = Extension.fromId(db, extension_id)
    user = dbutils.User.fromId(db, user_id)

    installed_sha1, _ = extension.getInstalledVersion(db, user)

    if installed_sha1 is False:
        return

    manifest = extension.getManifest(sha1=installed_sha1)

    for role in manifest.roles:
        if isinstance(role, FilterHookRole) and role.name == filterhook_name:
            return role
Example #6
0
def processFilterHookEvent(db, event_id, logfn):
    cursor = db.cursor()

    cursor.execute("""SELECT filters.extension, filters.uid, filters.path,
                             filters.name, events.review, events.uid, events.data
                        FROM extensionfilterhookevents AS events
                        JOIN extensionhookfilters AS filters ON (filters.id=events.filter)
                       WHERE events.id=%s""",
                   (event_id,))

    # Note:
    # - filter_user_id / filter_user represent the user whose filter was
    #   triggered.
    # - user_id /user represent the user that added commits and thereby
    #   triggered the filter.

    (extension_id, filter_user_id, filter_path,
     filterhook_name, review_id, user_id, filter_data) = cursor.fetchone()

    extension = Extension.fromId(db, extension_id)
    filter_user = dbutils.User.fromId(db, filter_user_id)

    installed_sha1, _ = extension.getInstalledVersion(db, filter_user)

    if installed_sha1 is False:
        # Invalid event (user doesn't have extension installed); do nothing.
        # The event will be deleted by the caller.
        return

    manifest = extension.getManifest(sha1=installed_sha1)

    for role in manifest.roles:
        if isinstance(role, FilterHookRole) and role.name == filterhook_name:
            break
    else:
        # Invalid event (installed version of extension doesn't have the named
        # filter hook role); do nothing.  The event will be deleted by the
        # caller.
        return

    cursor.execute("""SELECT commit
                        FROM extensionfilterhookcommits
                       WHERE event=%s""",
                   (event_id,))
    commit_ids = [commit_id for (commit_id,) in cursor]

    cursor.execute("""SELECT file
                        FROM extensionfilterhookfiles
                       WHERE event=%s""",
                   (event_id,))
    file_ids = [file_id for (file_id,) in cursor]

    argv = """

(function () {
   var review = new critic.Review(%(review_id)d);
   var user = new critic.User(%(user_id)d);
   var repository = review.repository;
   var commits = new critic.CommitSet(
     %(commit_ids)r.map(
       function (commit_id) {
         return repository.getCommit(commit_id);
       }));
   var files = %(file_ids)r.map(
     function (file_id) {
       return critic.File.find(file_id);
     });
   return [%(filter_data)s, review, user, commits, files];
 })()

""" % { "filter_data": htmlutils.jsify(filter_data),
        "review_id": review_id,
        "user_id": user_id,
        "commit_ids": commit_ids,
        "file_ids": file_ids }

    argv = re.sub("[ \n]+", " ", argv.strip())

    logfn("argv=%r" % argv)
    logfn("script=%r" % role.script)
    logfn("function=%r" % role.function)

    try:
        executeProcess(
            manifest, "filterhook", role.script, role.function, extension_id,
            filter_user_id, argv, configuration.extensions.LONG_TIMEOUT)
    except (ProcessTimeout, ProcessError) as error:
        review = dbutils.Review.fromId(db, review_id)

        recipients = set([filter_user])

        author = extension.getAuthor(db)
        if author is None:
            recipients.update(dbutils.User.withRole(db, "administrator"))
        else:
            recipients.add(author)

        body = """\
An error occurred while processing an extension hook filter event!

Filter details:

  Extension:   %(extension.title)s
  Filter hook: %(role.title)s
  Repository:  %(repository.name)s
  Path:        %(filter.path)s
  Data:        %(filter.data)s

Event details:

  Review:  r/%(review.id)d "%(review.summary)s"
  Commits: %(commits)s

Error details:

  Error:  %(error.message)s
  Output:%(error.output)s

-- critic"""

        commits = (gitutils.Commit.fromId(db, review.repository, commit_id)
                   for commit_id in commit_ids)
        commits_text = "\n           ".join(
            ('%s "%s"' % (commit.sha1[:8], commit.niceSummary())
             for commit in commits))

        if isinstance(error, ProcessTimeout):
            error_output = " N/A"
        else:
            error_output = "\n\n    " + "\n    ".join(error.stderr.splitlines())

        body = body % { "extension.title": extension.getTitle(db),
                        "role.title": role.title,
                        "repository.name": review.repository.name,
                        "filter.path": filter_path,
                        "filter.data": htmlutils.jsify(filter_data),
                        "review.id": review.id,
                        "review.summary": review.summary,
                        "commits": commits_text,
                        "error.message": error.message,
                        "error.output": error_output }

        mailutils.sendMessage(
            recipients=list(recipients),
            subject="Failed: " + role.title,
            body=body)
Example #7
0
def installExtension(db, user, author_name, extension_name, version):
    doInstallExtension(db, user, Extension(author_name, extension_name),
                       version)
    db.commit()
Example #8
0
def execute(db, user, review, all_commits, old_head, new_head, output):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    data = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        extension = Extension.fromId(db, extension_id)

        if version_id is not None:
            cursor.execute("""SELECT script, function
                                FROM extensionroles
                                JOIN extensionprocesscommitsroles ON (role=id)
                               WHERE version=%s
                            ORDER BY id ASC""",
                           (version_id,))

            handlers.extend(cursor)

            if not handlers:
                continue

            extension_path = getExtensionInstallPath(version_sha1)
            manifest = Manifest.load(extension_path)
        else:
            manifest = Manifest.load(extension.getPath())

            for role in manifest.roles:
                if isinstance(role, ProcessCommitsRole):
                    handlers.append((role.script, role.function))

            if not handlers:
                continue

        if data is None:
            commitset = log.commitset.CommitSet(all_commits)

            assert old_head is None or old_head in commitset.getTails()
            assert new_head in commitset.getHeads()
            assert len(commitset.getHeads()) == 1

            tails = commitset.getFilteredTails(review.repository)
            if len(tails) == 1:
                tail = gitutils.Commit.fromSHA1(db, review.repository, tails.pop())
                changeset_id = changeset.utils.createChangeset(
                    db, user, review.repository, from_commit=tail, to_commit=new_head)[0].id
                changeset_arg = "repository.getChangeset(%d)" % changeset_id
            else:
                changeset_arg = "null"

            commits_arg = "[%s]" % ",".join(
                [("repository.getCommit(%d)" % commit.getId(db))
                 for commit in all_commits])

            data = { "review_id": review.id,
                     "changeset": changeset_arg,
                     "commits": commits_arg }

        for script, function in handlers:
            class Error(Exception):
                pass

            def print_header():
                header = "%s::%s()" % (script, function)
                print >>output, ("\n[%s] %s\n[%s] %s"
                                 % (extension.getName(), header,
                                    extension.getName(), "=" * len(header)))

            try:
                argv = """

(function ()
 {
   var review = new critic.Review(%(review_id)d);
   var repository = review.repository;
   var changeset = %(changeset)s;
   var commitset = new critic.CommitSet(%(commits)s);

   return [review, changeset, commitset];
 })()

""" % data
                argv = re.sub("[ \n]+", " ", argv.strip())

                try:
                    stdout_data = executeProcess(
                        manifest, "processcommits", script, function, extension_id, user.id,
                        argv, configuration.extensions.SHORT_TIMEOUT)
                except ProcessTimeout:
                    raise Error("Timeout after %d seconds." % configuration.extensions.SHORT_TIMEOUT)
                except ProcessError as error:
                    if error.returncode < 0:
                        raise Error("Process terminated by signal %d." % -error.returncode)
                    else:
                        raise Error("Process returned %d.\n%s" % (error.returncode, error.stderr))

                if stdout_data.strip():
                    print_header()
                    for line in stdout_data.splitlines():
                        print >>output, "[%s] %s" % (extension.getName(), line)
            except Error as error:
                print_header()
                print >>output, "[%s] Extension error: %s" % (extension.getName(), error.message)
Example #9
0
def executeProcess(db, manifest, role_name, script, function, extension_id,
                   user_id, argv, timeout, stdin=None, rlimit_rss=256):
    # If |user_id| is not the same as |db.user|, then one user's access of the
    # system is triggering an extension on behalf of another user.  This will
    # for instance happen when one user is adding changes to a review,
    # triggering an extension filter hook set up by another user.
    #
    # In this case, we need to check that the other user can access the
    # extension.
    #
    # If |user_id| is the same as |db.user|, we need to use |db.profiles|, which
    # may contain a profile associated with an access token that was used to
    # authenticate the user.
    if user_id != db.user.id:
        user = dbutils.User.fromId(db, user_id)
        authentication_labels = auth.DATABASE.getAuthenticationLabels(user)
        profiles = [auth.AccessControlProfile.forUser(
            db, user, authentication_labels)]
    else:
        authentication_labels = db.authentication_labels
        profiles = db.profiles

    extension = Extension.fromId(db, extension_id)
    if not auth.AccessControlProfile.isAllowedExtension(
            profiles, "execute", extension):
        raise auth.AccessDenied("Access denied to extension: execute %s"
                                % extension.getKey())

    flavor = manifest.flavor

    if manifest.flavor not in configuration.extensions.FLAVORS:
        flavor = configuration.extensions.DEFAULT_FLAVOR

    stdin_data = "%s\n" % json_encode({
            "library_path": configuration.extensions.FLAVORS[flavor]["library"],
            "rlimit": { "rss": rlimit_rss },
            "hostname": configuration.base.HOSTNAME,
            "dbname": configuration.database.PARAMETERS["database"],
            "dbuser": configuration.database.PARAMETERS["user"],
            "git": configuration.executables.GIT,
            "python": configuration.executables.PYTHON,
            "python_path": "%s:%s" % (configuration.paths.CONFIG_DIR,
                                      configuration.paths.INSTALL_DIR),
            "repository_work_copy_path": configuration.extensions.WORKCOPY_DIR,
            "changeset_address": configuration.services.CHANGESET["address"],
            "branchtracker_pid_path": configuration.services.BRANCHTRACKER["pidfile_path"],
            "maildelivery_pid_path": configuration.services.MAILDELIVERY["pidfile_path"],
            "is_development": configuration.debug.IS_DEVELOPMENT,
            "extension_path": manifest.path,
            "extension_id": extension_id,
            "user_id": user_id,
            "authentication_labels": list(authentication_labels),
            "role": role_name,
            "script_path": script,
            "fn": function,
            "argv": argv })

    if stdin is not None:
        stdin_data += stdin

    # Double the timeout. Timeouts are primarily handled by the extension runner
    # service, which returns an error response on timeout. This deadline here is
    # thus mostly to catch the extension runner service itself timing out.
    deadline = time.time() + timeout * 2

    try:
        connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        connection.settimeout(max(0, deadline - time.time()))
        connection.connect(configuration.services.EXTENSIONRUNNER["address"])
        connection.sendall(json_encode({
            "stdin": stdin_data,
            "flavor": flavor,
            "timeout": timeout
        }))
        connection.shutdown(socket.SHUT_WR)

        data = ""

        while True:
            connection.settimeout(max(0, deadline - time.time()))
            try:
                received = connection.recv(4096)
            except socket.error as error:
                if error.errno == errno.EINTR:
                    continue
                raise
            if not received:
                break
            data += received

        connection.close()
    except socket.timeout as error:
        raise ProcessTimeout(timeout)
    except socket.error as error:
        raise ProcessError("failed to read response: %s" % error)

    try:
        data = json_decode(data)
    except ValueError as error:
        raise ProcessError("failed to decode response: %s" % error)

    if data["status"] == "timeout":
        raise ProcessTimeout(timeout)

    if data["status"] == "error":
        raise ProcessError(data["error"])

    if data["returncode"] != 0:
        raise ProcessFailure(data["returncode"], data["stderr"])

    return data["stdout"]
Example #10
0
def execute(db, req, user):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    argv = None
    stdin_data = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        if version_id is not None:
            cursor.execute(
                """SELECT script, function, path
                                FROM extensionroles
                                JOIN extensionpageroles ON (role=id)
                               WHERE version=%s
                            ORDER BY id ASC""", (version_id, ))

            for script, function, path_regexp in cursor:
                if re.match(path_regexp, req.path):
                    handlers.append((script, function))

            if not handlers:
                continue

            extension_path = getExtensionInstallPath(version_sha1)
            manifest = Manifest.load(extension_path)
        else:
            try:
                extension = Extension.fromId(db, extension_id)
            except ExtensionError:
                # If the author/hosting user no longer exists, or the extension
                # directory no longer exists or is inaccessible, ignore the
                # extension.
                continue

            try:
                manifest = Manifest.load(extension.getPath())
            except ManifestError:
                # If the MANIFEST is missing or invalid, we can't know whether
                # the extension has a page role handling the path, so assume it
                # doesn't and ignore it.
                continue

            for role in manifest.roles:
                if isinstance(role, PageRole) and re.match(
                        role.regexp, req.path):
                    handlers.append((role.script, role.function))

            if not handlers:
                continue

        if argv is None:

            def param(raw):
                parts = raw.split("=", 1)
                if len(parts) == 1:
                    return "%s: null" % jsify(decodeURIComponent(raw))
                else:
                    return "%s: %s" % (jsify(decodeURIComponent(
                        parts[0])), jsify(decodeURIComponent(parts[1])))

            if req.query:
                query = (
                    "Object.freeze({ raw: %s, params: Object.freeze({ %s }) })"
                    % (jsify(req.query), ", ".join(
                        map(param, req.query.split("&")))))
            else:
                query = "null"

            headers = ("Object.freeze({ %s })" % ", ".join(
                ("%s: %s" % (jsify(name), jsify(value)))
                for name, value in req.getRequestHeaders().items()))

            argv = ("[%(method)s, %(path)s, %(query)s, %(headers)s]" % {
                'method': jsify(req.method),
                'path': jsify(req.path),
                'query': query,
                'headers': headers
            })

        if req.method == "POST":
            if stdin_data is None:
                stdin_data = req.read()

        for script, function in handlers:
            before = time.time()

            try:
                stdout_data = executeProcess(
                    manifest,
                    "page",
                    script,
                    function,
                    extension_id,
                    user.id,
                    argv,
                    configuration.extensions.LONG_TIMEOUT,
                    stdin=stdin_data)
            except ProcessTimeout:
                req.setStatus(500, "Extension Timeout")
                return "Extension timed out!"
            except ProcessError as error:
                req.setStatus(500, "Extension Failure")
                if error.returncode < 0:
                    return ("Extension failure: terminated by signal %d\n" %
                            -error.returncode)
                else:
                    return ("Extension failure: returned %d\n%s" %
                            (error.returncode, error.stderr))

            after = time.time()

            status = None
            headers = {}

            if not stdout_data:
                return False

            while True:
                try:
                    line, stdout_data = stdout_data.split("\n", 1)
                except:
                    req.setStatus(500, "Extension Error")
                    return "Extension error: output format error.\n%r\n" % stdout_data

                if status is None:
                    try:
                        status = int(line.strip())
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: first line should contain only a numeric HTTP status code.\n%r\n" % line
                elif not line:
                    break
                else:
                    try:
                        name, value = line.split(":", 1)
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: header line should be on 'name: value' format.\n%r\n" % line
                    headers[name.strip()] = value.strip()

            if status is None:
                req.setStatus(500, "Extension Error")
                return "Extension error: first line should contain only a numeric HTTP status code.\n"

            content_type = "text/plain"

            for name, value in headers.items():
                if name.lower() == "content-type":
                    content_type = value
                    del headers[name]
                else:
                    headers[name] = value

            req.setStatus(status)
            req.setContentType(content_type)

            for name, value in headers.items():
                req.addResponseHeader(name, value)

            if content_type == "text/tutorial":
                req.setContentType("text/html")
                return renderTutorial(db, user, stdout_data)

            if content_type.startswith("text/html"):
                stdout_data += "\n\n<!-- extension execution time: %.2f seconds -->\n" % (
                    after - before)

            return stdout_data

    return False
Example #11
0
 def __call__(self, value, context):
     from extensions.extension import Extension
     super(ExtensionId, self).__call__(value, context)
     return Extension.fromId(context.db, value)
Example #12
0
File: page.py Project: Aessy/critic
def execute(db, req, user):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    argv = None
    stdin_data = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        if version_id is not None:
            cursor.execute("""SELECT script, function, path
                                FROM extensionroles
                                JOIN extensionpageroles ON (role=id)
                               WHERE version=%s
                            ORDER BY id ASC""",
                           (version_id,))

            for script, function, path_regexp in cursor:
                if re.match(path_regexp, req.path):
                    handlers.append((script, function))

            if not handlers:
                continue

            extension_path = getExtensionInstallPath(version_sha1)
            manifest = Manifest.load(extension_path)
        else:
            try:
                extension = Extension.fromId(db, extension_id)
            except ExtensionError:
                # If the author/hosting user no longer exists, or the extension
                # directory no longer exists or is inaccessible, ignore the
                # extension.
                continue

            try:
                manifest = Manifest.load(extension.getPath())
            except ManifestError:
                # If the MANIFEST is missing or invalid, we can't know whether
                # the extension has a page role handling the path, so assume it
                # doesn't and ignore it.
                continue

            for role in manifest.roles:
                if isinstance(role, PageRole) and re.match(role.regexp, req.path):
                    handlers.append((role.script, role.function))

            if not handlers:
                continue

        if argv is None:
            def param(raw):
                parts = raw.split("=", 1)
                if len(parts) == 1:
                    return "%s: null" % jsify(decodeURIComponent(raw))
                else:
                    return "%s: %s" % (jsify(decodeURIComponent(parts[0])),
                                       jsify(decodeURIComponent(parts[1])))

            if req.query:
                query = ("Object.freeze({ raw: %s, params: Object.freeze({ %s }) })"
                         % (jsify(req.query),
                            ", ".join(map(param, req.query.split("&")))))
            else:
                query = "null"

            headers = ("Object.freeze({ %s })"
                       % ", ".join(("%s: %s" % (jsify(name), jsify(value)))
                                   for name, value in req.getRequestHeaders().items()))

            argv = ("[%(method)s, %(path)s, %(query)s, %(headers)s]"
                    % { 'method': jsify(req.method),
                        'path': jsify(req.path),
                        'query': query,
                        'headers': headers })

        if req.method == "POST":
            if stdin_data is None:
                stdin_data = req.read()

        for script, function in handlers:
            before = time.time()

            try:
                stdout_data = executeProcess(
                    manifest, "page", script, function, extension_id, user.id,
                    argv, configuration.extensions.LONG_TIMEOUT, stdin=stdin_data)
            except ProcessTimeout:
                req.setStatus(500, "Extension Timeout")
                return "Extension timed out!"
            except ProcessError as error:
                req.setStatus(500, "Extension Failure")
                if error.returncode < 0:
                    return ("Extension failure: terminated by signal %d\n"
                            % -error.returncode)
                else:
                    return ("Extension failure: returned %d\n%s"
                            % (error.returncode, error.stderr))

            after = time.time()

            status = None
            headers = {}

            if not stdout_data:
                return False

            while True:
                try: line, stdout_data = stdout_data.split("\n", 1)
                except:
                    req.setStatus(500, "Extension Error")
                    return "Extension error: output format error.\n%r\n" % stdout_data

                if status is None:
                    try: status = int(line.strip())
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: first line should contain only a numeric HTTP status code.\n%r\n" % line
                elif not line:
                    break
                else:
                    try: name, value = line.split(":", 1)
                    except:
                        req.setStatus(500, "Extension Error")
                        return "Extension error: header line should be on 'name: value' format.\n%r\n" % line
                    headers[name.strip()] = value.strip()

            if status is None:
                req.setStatus(500, "Extension Error")
                return "Extension error: first line should contain only a numeric HTTP status code.\n"

            content_type = "text/plain"

            for name, value in headers.items():
                if name.lower() == "content-type":
                    content_type = value
                    del headers[name]
                else:
                    headers[name] = value

            req.setStatus(status)
            req.setContentType(content_type)

            for name, value in headers.items():
                req.addResponseHeader(name, value)

            if content_type == "text/tutorial":
                req.setContentType("text/html")
                return renderTutorial(db, user, stdout_data)

            if content_type.startswith("text/html"):
                stdout_data += "\n\n<!-- extension execution time: %.2f seconds -->\n" % (after - before)

            return stdout_data

    return False
Example #13
0
def doInstallExtension(db, user, extension, version):
    auth.AccessControl.accessExtension(db, "install", extension)

    is_universal = user is None
    extension_id = extension.getExtensionID(db, create=True)
    manifest = extension.getManifest(version)

    # Detect conflicting extension installs.
    current_installs = Extension.getInstalls(db, user)
    for current_extension_id, _, _, current_is_universal in current_installs:
        # Two installs never conflict if one is universal and one is not.
        if is_universal != current_is_universal:
            continue

        try:
            current_extension = Extension.fromId(db, current_extension_id)
        except ExtensionError as error:
            # Invalid extension => no conflict.
            #
            # But if there would be a conflict, should the installed extension
            # later become valid again, then delete the installation.
            if extension.getName() == error.extension.getName():
                doUninstallExtension(db, user, error.extension)
            continue

        # Same extension => conflict
        #
        # The web UI will typically not let you try to do this; if the extension
        # is already installed the UI will only let you uninstall or upgrade it.
        # But you never know.  Also, there's a UNIQUE constraint in the database
        # that would prevent this, but with a significantly worse error message,
        # of course.
        if extension_id == current_extension_id:
            raise InstallationError(
                title="Conflicting install",
                message=("The extension <code>%s</code> is already "
                         "%sinstalled."
                         % (current_extension.getTitle(db),
                            "universally " if is_universal else "")),
                is_html=True)

        # Different extensions, same name => also conflict
        #
        # Two extensions with the same name are probably simply two forks of the
        # same extension, and are very likely to have overlapping and
        # conflicting functionality.  Also, extension resource paths only
        # contain the extension name as an identifier, and thus will conflict
        # between the two extensions, even if they are actually completely
        # unrelated.
        if extension.getName() == current_extension.getName():
            raise InstallationError(
                title="Conflicting install",
                message=("The extension <code>%s</code> is already "
                         "%sinstalled, and conflicts with the extension "
                         "<code>%s</code> since they have the same name."
                         % (current_extension.getTitle(db),
                            "universally " if is_universal else "",
                            extension.getTitle(db))),
                is_html=True)

    cursor = db.cursor()

    if is_universal:
        user_id = None
    else:
        user_id = user.id

    if version is not None:
        sha1 = extension.prepareVersionSnapshot(version)

        cursor.execute("""SELECT id
                            FROM extensionversions
                           WHERE extension=%s
                             AND name=%s
                             AND sha1=%s""",
                       (extension_id, version, sha1))
        row = cursor.fetchone()

        if row:
            (version_id,) = row
        else:
            cursor.execute("""INSERT INTO extensionversions (extension, name, sha1)
                                   VALUES (%s, %s, %s)
                                RETURNING id""",
                           (extension_id, version, sha1))
            (version_id,) = cursor.fetchone()

            for role in manifest.roles:
                role.install(db, version_id)
    else:
        version_id = None

    cursor.execute("""INSERT INTO extensioninstalls (uid, extension, version)
                           VALUES (%s, %s, %s)""",
                   (user_id, extension_id, version_id))
Example #14
0
def reinstallExtension(db, user, author_name, extension_name, version):
    doUninstallExtension(db, user, getExtension(author_name, extension_name))
    doInstallExtension(db, user, Extension(author_name, extension_name),
                       version)
    db.commit()
Example #15
0
def executeProcess(db,
                   manifest,
                   role_name,
                   script,
                   function,
                   extension_id,
                   user_id,
                   argv,
                   timeout,
                   stdin=None,
                   rlimit_rss=256):
    # If |user_id| is not the same as |db.user|, then one user's access of the
    # system is triggering an extension on behalf of another user.  This will
    # for instance happen when one user is adding changes to a review,
    # triggering an extension filter hook set up by another user.
    #
    # In this case, we need to check that the other user can access the
    # extension.
    #
    # If |user_id| is the same as |db.user|, we need to use |db.profiles|, which
    # may contain a profile associated with an access token that was used to
    # authenticate the user.
    if user_id != db.user.id:
        user = dbutils.User.fromId(db, user_id)
        authentication_labels = auth.DATABASE.getAuthenticationLabels(user)
        profiles = [
            auth.AccessControlProfile.forUser(db, user, authentication_labels)
        ]
    else:
        authentication_labels = db.authentication_labels
        profiles = db.profiles

    extension = Extension.fromId(db, extension_id)
    if not auth.AccessControlProfile.isAllowedExtension(
            profiles, "execute", extension):
        raise auth.AccessDenied("Access denied to extension: execute %s" %
                                extension.getKey())

    flavor = manifest.flavor

    if manifest.flavor not in configuration.extensions.FLAVORS:
        flavor = configuration.extensions.DEFAULT_FLAVOR

    stdin_data = "%s\n" % json_encode({
        "library_path":
        configuration.extensions.FLAVORS[flavor]["library"],
        "rlimit": {
            "rss": rlimit_rss
        },
        "hostname":
        configuration.base.HOSTNAME,
        "dbname":
        configuration.database.PARAMETERS["database"],
        "dbuser":
        configuration.database.PARAMETERS["user"],
        "git":
        configuration.executables.GIT,
        "python":
        configuration.executables.PYTHON,
        "python_path":
        "%s:%s" %
        (configuration.paths.CONFIG_DIR, configuration.paths.INSTALL_DIR),
        "repository_work_copy_path":
        configuration.extensions.WORKCOPY_DIR,
        "changeset_address":
        configuration.services.CHANGESET["address"],
        "branchtracker_pid_path":
        configuration.services.BRANCHTRACKER["pidfile_path"],
        "maildelivery_pid_path":
        configuration.services.MAILDELIVERY["pidfile_path"],
        "is_development":
        configuration.debug.IS_DEVELOPMENT,
        "extension_path":
        manifest.path,
        "extension_id":
        extension_id,
        "user_id":
        user_id,
        "authentication_labels":
        list(authentication_labels),
        "role":
        role_name,
        "script_path":
        script,
        "fn":
        function,
        "argv":
        argv
    })

    if stdin is not None:
        stdin_data += stdin

    # Double the timeout. Timeouts are primarily handled by the extension runner
    # service, which returns an error response on timeout. This deadline here is
    # thus mostly to catch the extension runner service itself timing out.
    deadline = time.time() + timeout * 2

    try:
        connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        connection.settimeout(max(0, deadline - time.time()))
        connection.connect(configuration.services.EXTENSIONRUNNER["address"])
        connection.sendall(
            json_encode({
                "stdin": stdin_data,
                "flavor": flavor,
                "timeout": timeout
            }))
        connection.shutdown(socket.SHUT_WR)

        data = ""

        while True:
            connection.settimeout(max(0, deadline - time.time()))
            try:
                received = connection.recv(4096)
            except socket.error as error:
                if error.errno == errno.EINTR:
                    continue
                raise
            if not received:
                break
            data += received

        connection.close()
    except socket.timeout as error:
        raise ProcessTimeout(timeout)
    except socket.error as error:
        raise ProcessError("failed to read response: %s" % error)

    try:
        data = json_decode(data)
    except ValueError as error:
        raise ProcessError("failed to decode response: %s" % error)

    if data["status"] == "timeout":
        raise ProcessTimeout(timeout)

    if data["status"] == "error":
        raise ProcessError(data["error"])

    if data["returncode"] != 0:
        raise ProcessFailure(data["returncode"], data["stderr"])

    return data["stdout"]
Example #16
0
def processFilterHookEvent(db, event_id, logfn):
    cursor = db.cursor()

    cursor.execute(
        """SELECT filters.extension, filters.uid, filters.path,
                             filters.name, events.review, events.uid, events.data
                        FROM extensionfilterhookevents AS events
                        JOIN extensionhookfilters AS filters ON (filters.id=events.filter)
                       WHERE events.id=%s""", (event_id, ))

    # Note:
    # - filter_user_id / filter_user represent the user whose filter was
    #   triggered.
    # - user_id /user represent the user that added commits and thereby
    #   triggered the filter.

    (extension_id, filter_user_id, filter_path, filterhook_name, review_id,
     user_id, filter_data) = cursor.fetchone()

    extension = Extension.fromId(db, extension_id)
    filter_user = dbutils.User.fromId(db, filter_user_id)

    installed_sha1, _ = extension.getInstalledVersion(db, filter_user)

    if installed_sha1 is False:
        # Invalid event (user doesn't have extension installed); do nothing.
        # The event will be deleted by the caller.
        return

    manifest = extension.getManifest(sha1=installed_sha1)

    for role in manifest.roles:
        if isinstance(role, FilterHookRole) and role.name == filterhook_name:
            break
    else:
        # Invalid event (installed version of extension doesn't have the named
        # filter hook role); do nothing.  The event will be deleted by the
        # caller.
        return

    cursor.execute(
        """SELECT commit
                        FROM extensionfilterhookcommits
                       WHERE event=%s""", (event_id, ))
    commit_ids = [commit_id for (commit_id, ) in cursor]

    cursor.execute(
        """SELECT file
                        FROM extensionfilterhookfiles
                       WHERE event=%s""", (event_id, ))
    file_ids = [file_id for (file_id, ) in cursor]

    argv = """

(function () {
   var review = new critic.Review(%(review_id)d);
   var user = new critic.User(%(user_id)d);
   var repository = review.repository;
   var commits = new critic.CommitSet(
     %(commit_ids)r.map(
       function (commit_id) {
         return repository.getCommit(commit_id);
       }));
   var files = %(file_ids)r.map(
     function (file_id) {
       return critic.File.find(file_id);
     });
   return [%(filter_data)s, review, user, commits, files];
 })()

""" % {
        "filter_data": htmlutils.jsify(filter_data),
        "review_id": review_id,
        "user_id": user_id,
        "commit_ids": commit_ids,
        "file_ids": file_ids
    }

    argv = re.sub("[ \n]+", " ", argv.strip())

    logfn("argv=%r" % argv)
    logfn("script=%r" % role.script)
    logfn("function=%r" % role.function)

    try:
        executeProcess(manifest, "filterhook", role.script, role.function,
                       extension_id, filter_user_id, argv,
                       configuration.extensions.LONG_TIMEOUT)
    except (ProcessTimeout, ProcessError) as error:
        review = dbutils.Review.fromId(db, review_id)

        recipients = set([filter_user])

        author = extension.getAuthor(db)
        if author is None:
            recipients.update(dbutils.User.withRole(db, "administrator"))
        else:
            recipients.add(author)

        body = """\
An error occurred while processing an extension hook filter event!

Filter details:

  Extension:   %(extension.title)s
  Filter hook: %(role.title)s
  Repository:  %(repository.name)s
  Path:        %(filter.path)s
  Data:        %(filter.data)s

Event details:

  Review:  r/%(review.id)d "%(review.summary)s"
  Commits: %(commits)s

Error details:

  Error:  %(error.message)s
  Output:%(error.output)s

-- critic"""

        commits = (gitutils.Commit.fromId(db, review.repository, commit_id)
                   for commit_id in commit_ids)
        commits_text = "\n           ".join(
            ('%s "%s"' % (commit.sha1[:8], commit.niceSummary())
             for commit in commits))

        if isinstance(error, ProcessTimeout):
            error_output = " N/A"
        else:
            error_output = "\n\n    " + "\n    ".join(
                error.stderr.splitlines())

        body = body % {
            "extension.title": extension.getTitle(db),
            "role.title": role.title,
            "repository.name": review.repository.name,
            "filter.path": filter_path,
            "filter.data": htmlutils.jsify(filter_data),
            "review.id": review.id,
            "review.summary": review.summary,
            "commits": commits_text,
            "error.message": error.message,
            "error.output": error_output
        }

        mailutils.sendMessage(recipients=list(recipients),
                              subject="Failed: " + role.title,
                              body=body)
Example #17
0
def execute(db, req, user, document, links, injected, profiler=None):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    def get_matching_path(path_regexp):
        if re.match(path_regexp, req.path):
            return (req.path, req.query)
        elif re.match(path_regexp, req.original_path):
            return (req.original_path, req.original_query)
        else:
            return None, None

    query = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        try:
            if version_id is not None:
                cursor.execute("""SELECT script, function, path
                                    FROM extensionroles
                                    JOIN extensioninjectroles ON (role=id)
                                   WHERE version=%s
                                ORDER BY id ASC""",
                               (version_id,))

                for script, function, path_regexp in cursor:
                    path, query = get_matching_path(path_regexp)
                    if path is not None:
                        handlers.append((path, query, script, function))

                if not handlers:
                    continue

                extension = Extension.fromId(db, extension_id)
                manifest = Manifest.load(getExtensionInstallPath(version_sha1))
            else:
                extension = Extension.fromId(db, extension_id)
                manifest = Manifest.load(extension.getPath())

                for role in manifest.roles:
                    if isinstance(role, InjectRole):
                        path, query = get_matching_path(role.regexp)
                        if path is not None:
                            handlers.append((path, query, role.script, role.function))

                if not handlers:
                    continue

            def construct_query(query):
                if not query:
                    return "null"

                params = urlparse.parse_qs(query, keep_blank_values=True)

                for key in params:
                    values = params[key]
                    if len(values) == 1:
                        if not values[0]:
                            params[key] = None
                        else:
                            params[key] = values[0]

                return ("Object.freeze({ raw: %s, params: Object.freeze(%s) })"
                        % (json_encode(query), json_encode(params)))

            preferences = None
            commands = []

            for path, query, script, function in handlers:
                argv = "[%s, %s]" % (jsify(path), construct_query(query))

                try:
                    stdout_data = executeProcess(
                        manifest, "inject", script, function, extension_id, user.id, argv,
                        configuration.extensions.SHORT_TIMEOUT)
                except ProcessTimeout:
                    raise InjectError("Timeout after %d seconds." % configuration.extensions.SHORT_TIMEOUT)
                except ProcessError as error:
                    if error.returncode < 0:
                        raise InjectError("Process terminated by signal %d." % -error.returncode)
                    else:
                        raise InjectError("Process returned %d.\n%s" % (error.returncode, error.stderr))

                for line in stdout_data.splitlines():
                    if line.strip():
                        commands.append(processLine(path, line.strip()))

            for command, value in commands:
                if command == "script":
                    document.addExternalScript(value, use_static=False, order=1)
                elif command == "stylesheet":
                    document.addExternalStylesheet(value, use_static=False, order=1)
                elif command == "link":
                    for index, (_, label, _, _) in enumerate(links):
                        if label == value["label"]:
                            if value["url"] is None:
                                del links[index]
                            else:
                                links[index][0] = value["url"]
                            break
                    else:
                        if value["url"] is not None:
                            links.append([value["url"], value["label"], None, None])
                elif command == "preference":
                    if not preferences:
                        preferences = []
                        injected.setdefault("preferences", []).append(
                            (extension.getName(), extension.getAuthor(db), preferences))
                    preferences.append(value)

            if profiler:
                profiler.check("inject: %s" % extension.getKey())
        except ExtensionError as error:
            document.comment("\n\n[%s] Extension error:\nInvalid extension:\n%s\n\n"
                             % (error.extension.getKey(), error.message))
        except ManifestError as error:
            document.comment("\n\n[%s] Extension error:\nInvalid MANIFEST:\n%s\n\n"
                             % (extension.getKey(), error.message))
        except InjectError as error:
            document.comment("\n\n[%s] Extension error:\n%s\n\n"
                             % (extension.getKey(), error.message))
Example #18
0
def renderManageExtensions(req, db, user):
    if not configuration.extensions.ENABLED:
        administrators = dbutils.getAdministratorContacts(db, as_html=True)
        raise page.utils.DisplayMessage(
            title="Extension support not enabled",
            body=(("<p>This Critic system does not support extensions.</p>"
                   "<p>Contact %s to have it enabled, or see the "
                   "<a href='/tutorial?item=administration#extensions'>"
                   "section on extensions</a> in the system administration "
                   "tutorial for more information.</p>")
                  % administrators),
            html=True)

    cursor = db.cursor()

    what = req.getParameter("what", "available")
    selected_versions = page.utils.json_decode(req.getParameter("select", "{}"))
    focused = req.getParameter("focus", None)

    if what == "installed":
        title = "Installed Extensions"
        listed_extensions = []
        for extension_id, _, _, _ in Extension.getInstalls(db, user):
            try:
                listed_extensions.append(Extension.fromId(db, extension_id))
            except ExtensionError as error:
                listed_extensions.append(error)
    else:
        title = "Available Extensions"
        listed_extensions = Extension.find(db)

    req.content_type = "text/html; charset=utf-8"

    document = htmlutils.Document(req)
    document.setTitle("Manage Extensions")

    html = document.html()
    head = html.head()
    body = html.body()

    def generateRight(target):
        target.a("button", href="tutorial?item=extensions").text("Tutorial")
        target.text(" ")
        target.a("button", href="tutorial?item=extensions-api").text("API Documentation")

    page.utils.generateHeader(body, db, user, current_page="extensions", generate_right=generateRight)

    document.addExternalStylesheet("resource/manageextensions.css")
    document.addExternalScript("resource/manageextensions.js")
    document.addInternalScript(user.getJS())

    table = page.utils.PaleYellowTable(body, title)

    def addTitleRightLink(url, label):
        if user.name != req.user:
            url += "&user=%s" % user.name
        table.titleRight.text(" ")
        table.titleRight.a(href=url).text("[" + label + " extensions]")

    if what != "installed" or focused:
        addTitleRightLink("/manageextensions?what=installed", "installed")
    if what != "available" or focused:
        addTitleRightLink("/manageextensions?what=available", "available")

    for item in listed_extensions:
        if isinstance(item, ExtensionError):
            extension_error = item
            extension = item.extension
        else:
            extension_error = None
            extension = item

        if focused and extension.getKey() != focused:
            continue

        extension_path = extension.getPath()

        if extension.isSystemExtension():
            hosting_user = None
        else:
            hosting_user = extension.getAuthor(db)

        selected_version = selected_versions.get(extension.getKey(), False)
        installed_sha1, installed_version = extension.getInstalledVersion(db, user)
        universal_sha1, universal_version = extension.getInstalledVersion(db, None)
        installed_upgradeable = universal_upgradeable = False

        if extension_error is None:
            if installed_sha1:
                current_sha1 = extension.getCurrentSHA1(installed_version)
                installed_upgradeable = installed_sha1 != current_sha1
            if universal_sha1:
                current_sha1 = extension.getCurrentSHA1(universal_version)
                universal_upgradeable = universal_sha1 != current_sha1

        def massage_version(version):
            if version is None:
                return "live"
            elif version:
                return "version/%s" % version
            else:
                return None

        if selected_version is False:
            selected_version = installed_version
        if selected_version is False:
            selected_version = universal_version

        install_version = massage_version(selected_version)
        installed_version = massage_version(installed_version)
        universal_version = massage_version(universal_version)

        manifest = None

        if extension_error is None:
            try:
                if selected_version is False:
                    manifest = extension.getManifest()
                else:
                    manifest = extension.getManifest(selected_version)
            except ManifestError as error:
                pass
        elif installed_sha1:
            manifest = extension.getManifest(installed_version, installed_sha1)
        elif universal_sha1:
            manifest = extension.getManifest(universal_version, universal_sha1)

        if manifest:
            if what == "available" and manifest.hidden:
                # Hide from view unless the user is hosting the extension, or is
                # an administrator and the extension is a system extension.
                if extension.isSystemExtension():
                    if not user.hasRole(db, "administrator"):
                        continue
                elif hosting_user != user:
                    continue
        else:
            if hosting_user != user:
                continue

        extension_id = extension.getExtensionID(db, create=False)

        if not user.isAnonymous():
            buttons = []

            if extension_id is not None:
                cursor.execute("""SELECT 1
                                    FROM extensionstorage
                                   WHERE extension=%s
                                     AND uid=%s""",
                               (extension_id, user.id))

                if cursor.fetchone():
                    buttons.append(("Clear storage",
                                    ("clearExtensionStorage(%s, %s)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName())))))

            if not installed_version:
                if manifest and install_version and install_version != universal_version:
                    buttons.append(("Install",
                                    ("installExtension(%s, %s, %s)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName()),
                                        htmlutils.jsify(install_version)))))
            else:
                buttons.append(("Uninstall",
                                ("uninstallExtension(%s, %s)"
                                 % (htmlutils.jsify(extension.getAuthorName()),
                                    htmlutils.jsify(extension.getName())))))

                if manifest and (install_version != installed_version
                                 or (installed_sha1 and installed_upgradeable)):
                    if install_version == installed_version:
                        label = "Upgrade"
                    else:
                        label = "Install"

                    buttons.append(("Upgrade",
                                    ("reinstallExtension(%s, %s, %s)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName()),
                                        htmlutils.jsify(install_version)))))

            if user.hasRole(db, "administrator"):
                if not universal_version:
                    if manifest and install_version:
                        buttons.append(("Install (universal)",
                                        ("installExtension(%s, %s, %s, true)"
                                         % (htmlutils.jsify(extension.getAuthorName()),
                                            htmlutils.jsify(extension.getName()),
                                            htmlutils.jsify(install_version)))))
                else:
                    buttons.append(("Uninstall (universal)",
                                    ("uninstallExtension(%s, %s, true)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName())))))

                    if manifest and (install_version != universal_version
                                     or (universal_sha1 and universal_upgradeable)):
                        if install_version == universal_version:
                            label = "Upgrade (universal)"
                        else:
                            label = "Install (universal)"

                        buttons.append((label,
                                        ("reinstallExtension(%s, %s, %s, true)"
                                         % (htmlutils.jsify(extension.getAuthorName()),
                                            htmlutils.jsify(extension.getName()),
                                            htmlutils.jsify(universal_version)))))
        else:
            buttons = None

        def renderItem(target):
            target.span("name").innerHTML(extension.getTitle(db, html=True))

            if hosting_user:
                is_author = manifest and manifest.isAuthor(db, hosting_user)
                is_sole_author = is_author and len(manifest.authors) == 1
            else:
                is_sole_author = False

            if extension_error is None:
                span = target.span("details")
                span.b().text("Details: ")
                select = span.select("details", critic_author=extension.getAuthorName(), critic_extension=extension.getName())
                select.option(value='', selected="selected" if selected_version is False else None).text("Select version")
                versions = extension.getVersions()
                if versions:
                    optgroup = select.optgroup(label="Official Versions")
                    for version in versions:
                        optgroup.option(value="version/%s" % version, selected="selected" if selected_version == version else None).text("%s" % version.upper())
                optgroup = select.optgroup(label="Development")
                optgroup.option(value='live', selected="selected" if selected_version is None else None).text("LIVE")

            if manifest:
                is_installed = bool(installed_version)

                if is_installed:
                    target.span("installed").text(" [installed]")
                else:
                    is_installed = bool(universal_version)

                    if is_installed:
                        target.span("installed").text(" [installed (universal)]")

                target.div("description").preformatted().text(manifest.description, linkify=True)

                if not is_sole_author:
                    authors = target.div("authors")
                    authors.b().text("Author%s:" % ("s" if len(manifest.authors) > 1 else ""))
                    authors.text(", ".join(author.name for author in manifest.getAuthors()))
            else:
                is_installed = False

                div = target.div("description broken").preformatted()

                if extension_error is None:
                    anchor = div.a(href="loadmanifest?key=%s" % extension.getKey())
                    anchor.text("[This extension has an invalid MANIFEST file]")
                else:
                    div.text("[This extension has been deleted or has become inaccessible]")

            if selected_version is False:
                return

            pages = []
            injects = []
            processcommits = []
            filterhooks = []
            scheduled = []

            if manifest:
                for role in manifest.roles:
                    if isinstance(role, PageRole):
                        pages.append(role)
                    elif isinstance(role, InjectRole):
                        injects.append(role)
                    elif isinstance(role, ProcessCommitsRole):
                        processcommits.append(role)
                    elif isinstance(role, FilterHookRole):
                        filterhooks.append(role)
                    elif isinstance(role, ScheduledRole):
                        scheduled.append(role)

            role_table = target.table("roles")

            if pages:
                role_table.tr().th(colspan=2).text("Pages")

                for role in pages:
                    row = role_table.tr()
                    url = "%s/%s" % (dbutils.getURLPrefix(db, user), role.pattern)
                    if is_installed and "*" not in url:
                        row.td("pattern").a(href=url).text(url)
                    else:
                        row.td("pattern").text(url)
                    td = row.td("description")
                    td.text(role.description)

            if injects:
                role_table.tr().th(colspan=2).text("Page Injections")

                for role in injects:
                    row = role_table.tr()
                    row.td("pattern").text("%s/%s" % (dbutils.getURLPrefix(db, user), role.pattern))
                    td = row.td("description")
                    td.text(role.description)

            if processcommits:
                role_table.tr().th(colspan=2).text("ProcessCommits hooks")
                ul = role_table.tr().td(colspan=2).ul()

                for role in processcommits:
                    li = ul.li()
                    li.text(role.description)

            if filterhooks:
                role_table.tr().th(colspan=2).text("FilterHook hooks")

                for role in filterhooks:
                    row = role_table.tr()
                    row.td("title").text(role.title)
                    row.td("description").text(role.description)

            if scheduled:
                role_table.tr().th(colspan=2).text("Scheduled hooks")

                for role in scheduled:
                    row = role_table.tr()
                    row.td("pattern").text("%s @ %s" % (role.frequency, role.at))
                    td = row.td("description")
                    td.text(role.description)

        installed_by = ""

        if extension_id is not None:
            cursor.execute("""SELECT uid
                                FROM extensioninstalls
                                JOIN extensions ON (extensions.id=extensioninstalls.extension)
                               WHERE extensions.id=%s""",
                           (extension.getExtensionID(db, create=False),))

            user_ids = set(user_id for user_id, in cursor.fetchall())
            if user_ids:
                installed_by = " (installed"
                if None in user_ids:
                    installed_by += " universally"
                    user_ids.remove(None)
                    if user_ids:
                        installed_by += " and"
                if user_ids:
                    installed_by += (" by %d user%s"
                                  % (len(user_ids),
                                     "s" if len(user_ids) > 1 else ""))
                installed_by += ")"

        table.addItem("Extension", renderItem, extension_path + "/" + installed_by, buttons)

    document.addInternalScript("var selected_versions = %s;" % page.utils.json_encode(selected_versions))

    return document
Example #19
0
def execute(db, user, review, all_commits, old_head, new_head, output):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    data = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        extension = Extension.fromId(db, extension_id)

        if version_id is not None:
            cursor.execute(
                """SELECT script, function
                                FROM extensionroles
                                JOIN extensionprocesscommitsroles ON (role=id)
                               WHERE version=%s
                            ORDER BY id ASC""", (version_id, ))

            handlers.extend(cursor)

            if not handlers:
                continue

            extension_path = getExtensionInstallPath(version_sha1)
            manifest = Manifest.load(extension_path)
        else:
            manifest = Manifest.load(extension.getPath())

            for role in manifest.roles:
                if isinstance(role, ProcessCommitsRole):
                    handlers.append((role.script, role.function))

            if not handlers:
                continue

        if data is None:
            commitset = log.commitset.CommitSet(all_commits)

            assert old_head is None or old_head in commitset.getTails()
            assert new_head in commitset.getHeads()
            assert len(commitset.getHeads()) == 1

            tails = commitset.getFilteredTails(review.repository)
            if len(tails) == 1:
                tail = gitutils.Commit.fromSHA1(db, review.repository,
                                                tails.pop())
                changeset_id = changeset.utils.createChangeset(
                    db,
                    user,
                    review.repository,
                    from_commit=tail,
                    to_commit=new_head)[0].id
                changeset_arg = "repository.getChangeset(%d)" % changeset_id
            else:
                changeset_arg = "null"

            commits_arg = "[%s]" % ",".join(
                [("repository.getCommit(%d)" % commit.getId(db))
                 for commit in all_commits])

            data = {
                "review_id": review.id,
                "changeset": changeset_arg,
                "commits": commits_arg
            }

        for script, function in handlers:

            class Error(Exception):
                pass

            def print_header():
                header = "%s::%s()" % (script, function)
                print >> output, ("\n[%s] %s\n[%s] %s" %
                                  (extension.getName(), header,
                                   extension.getName(), "=" * len(header)))

            try:
                argv = """

(function ()
 {
   var review = new critic.Review(%(review_id)d);
   var repository = review.repository;
   var changeset = %(changeset)s;
   var commitset = new critic.CommitSet(%(commits)s);

   return [review, changeset, commitset];
 })()

""" % data
                argv = re.sub("[ \n]+", " ", argv.strip())

                try:
                    stdout_data = executeProcess(
                        manifest, "processcommits", script, function,
                        extension_id, user.id, argv,
                        configuration.extensions.SHORT_TIMEOUT)
                except ProcessTimeout:
                    raise Error("Timeout after %d seconds." %
                                configuration.extensions.SHORT_TIMEOUT)
                except ProcessError as error:
                    if error.returncode < 0:
                        raise Error("Process terminated by signal %d." %
                                    -error.returncode)
                    else:
                        raise Error("Process returned %d.\n%s" %
                                    (error.returncode, error.stderr))

                if stdout_data.strip():
                    print_header()
                    for line in stdout_data.splitlines():
                        print >> output, "[%s] %s" % (extension.getName(),
                                                      line)
            except Error as error:
                print_header()
                print >> output, "[%s] Extension error: %s" % (
                    extension.getName(), error.message)
Example #20
0
def generateHeader(target,
                   db,
                   user,
                   generate_right=None,
                   current_page=None,
                   extra_links=[],
                   profiler=None):
    target.addExternalStylesheet("resource/third-party/jquery-ui.css")
    target.addExternalStylesheet("resource/third-party/chosen.css")
    target.addExternalStylesheet("resource/overrides.css")
    target.addExternalStylesheet("resource/basic.css")
    target.addInternalStylesheet(".defaultfont, body { %s }" %
                                 user.getPreference(db, "style.defaultFont"))
    target.addInternalStylesheet(".sourcefont { %s }" %
                                 user.getPreference(db, "style.sourceFont"))
    target.addExternalScript("resource/third-party/jquery.js")
    target.addExternalScript("resource/third-party/jquery-ui.js")
    target.addExternalScript(
        "resource/third-party/jquery-ui-autocomplete-html.js")
    target.addExternalScript("resource/third-party/chosen.js")
    target.addExternalScript("resource/basic.js")

    target.noscript().h1("noscript").blink().text(
        "Please enable scripting support!")

    row = target.table("pageheader", width='100%').tr()
    left = row.td("left", valign='bottom', align='left')
    b = left.b()

    opera_class = "opera"

    if configuration.debug.IS_DEVELOPMENT:
        opera_class += " development"

    b.b(opera_class, onclick="location.href='/';").text("Opera")
    b.b("critic", onclick="location.href='/';").text("Critic")

    links = []

    if not user.isAnonymous():
        links.append(["home", "Home", None, None])

    links.append(["dashboard", "Dashboard", None, None])
    links.append(["branches", "Branches", None, None])
    links.append(["search", "Search", None, None])

    if user.hasRole(db, "administrator"):
        links.append(["services", "Services", None, None])
    if user.hasRole(db, "repositories"):
        links.append(["repositories", "Repositories", None, None])

    if profiler:
        profiler.check("generateHeader (basic)")

    if configuration.extensions.ENABLED:
        from extensions.extension import Extension

        updated = Extension.getUpdatedExtensions(db, user)

        if updated:
            link_title = "\n".join([
                ("%s by %s can be updated!" %
                 (extension_name, author_fullname))
                for author_fullname, extension_name in updated
            ])
            links.append([
                "manageextensions",
                "Extensions (%d)" % len(updated), "color: red", link_title
            ])
        else:
            links.append(["manageextensions", "Extensions", None, None])

        if profiler:
            profiler.check("generateHeader (updated extensions)")

    links.append(["config", "Config", None, None])
    links.append(["tutorial", "Tutorial", None, None])

    if user.isAnonymous():
        count = 0
    else:
        cursor = db.cursor()
        cursor.execute(
            """SELECT COUNT(*)
                            FROM newsitems
                 LEFT OUTER JOIN newsread ON (item=id AND uid=%s)
                           WHERE uid IS NULL""", (user.id, ))
        count = cursor.fetchone()[0]

    if count:
        links.append([
            "news",
            "News (%d)" % count, "color: red",
            "There are %d unread news items!" % count
        ])
    else:
        links.append(["news", "News", None, None])

    if profiler:
        profiler.check("generateHeader (news)")

    req = target.getRequest()

    if configuration.base.AUTHENTICATION_MODE != "host" \
           and configuration.base.SESSION_TYPE == "cookie":
        if user.isAnonymous():
            links.append([
                "javascript:void(location.href='/login?target='+encodeURIComponent(location.href));",
                "Sign in", None, None
            ])
        elif not req or req.user == user.name:
            links.append(["javascript:signOut();", "Sign out", None, None])

    for url, label in extra_links:
        links.append([url, label, None, None])

    if req and configuration.extensions.ENABLED:
        import extensions.role.inject

        injected = {}

        extensions.role.inject.execute(db,
                                       req,
                                       user,
                                       target,
                                       links,
                                       injected,
                                       profiler=profiler)

        for url in injected.get("stylesheets", []):
            target.addExternalStylesheet(url, use_static=False, order=1)

        for url in injected.get("scripts", []):
            target.addExternalScript(url, use_static=False, order=1)
    else:
        injected = None

    ul = left.ul()

    for index, (url, label, style, title) in enumerate(links):
        if not re.match("[-.a-z]+:|/", url):
            url = "/" + url
        ul.li().a(href=url, style=style, title=title).text(label)

        rel = LINK_RELS.get(label)
        if rel: target.setLink(rel, url)

    right = row.td("right", valign='bottom', align='right')
    if generate_right:
        generate_right(right)
    else:
        right.div("buttons").span("buttonscope buttonscope-global")

    if profiler:
        profiler.check("generateHeader (finish)")

    return injected
Example #21
0
def doInstallExtension(db, user, extension, version):
    is_universal = user is None
    extension_id = extension.getExtensionID(db, create=True)
    manifest = extension.getManifest(version)

    # Detect conflicting extension installs.
    current_installs = Extension.getInstalls(db, user)
    for current_extension_id, _, _, current_is_universal in current_installs:
        # Two installs never conflict if one is universal and one is not.
        if is_universal != current_is_universal:
            continue

        try:
            current_extension = Extension.fromId(db, current_extension_id)
        except ExtensionError as error:
            # Invalid extension => no conflict.
            #
            # But if there would be a conflict, should the installed extension
            # later become valid again, then delete the installation.
            if extension.getName() == error.extension.getName():
                doUninstallExtension(db, user, error.extension)
            continue

        # Same extension => conflict
        #
        # The web UI will typically not let you try to do this; if the extension
        # is already installed the UI will only let you uninstall or upgrade it.
        # But you never know.  Also, there's a UNIQUE constraint in the database
        # that would prevent this, but with a significantly worse error message,
        # of course.
        if extension_id == current_extension_id:
            raise InstallationError(
                title="Conflicting install",
                message=("The extension <code>%s</code> is already "
                         "%sinstalled." %
                         (current_extension.getTitle(db),
                          "universally " if is_universal else "")),
                is_html=True)

        # Different extensions, same name => also conflict
        #
        # Two extensions with the same name are probably simply two forks of the
        # same extension, and are very likely to have overlapping and
        # conflicting functionality.  Also, extension resource paths only
        # contain the extension name as an identifier, and thus will conflict
        # between the two extensions, even if they are actually completely
        # unrelated.
        if extension.getName() == current_extension.getName():
            raise InstallationError(
                title="Conflicting install",
                message=("The extension <code>%s</code> is already "
                         "%sinstalled, and conflicts with the extension "
                         "<code>%s</code> since they have the same name." %
                         (current_extension.getTitle(db), "universally "
                          if is_universal else "", extension.getTitle(db))),
                is_html=True)

    cursor = db.cursor()

    if is_universal:
        user_id = None
    else:
        user_id = user.id

    if version is not None:
        sha1 = extension.prepareVersionSnapshot(version)

        cursor.execute(
            """SELECT id
                            FROM extensionversions
                           WHERE extension=%s
                             AND name=%s
                             AND sha1=%s""", (extension_id, version, sha1))
        row = cursor.fetchone()

        if row:
            (version_id, ) = row
        else:
            cursor.execute(
                """INSERT INTO extensionversions (extension, name, sha1)
                                   VALUES (%s, %s, %s)
                                RETURNING id""", (extension_id, version, sha1))
            (version_id, ) = cursor.fetchone()

            for role in manifest.roles:
                role.install(db, version_id)
    else:
        version_id = None

    cursor.execute(
        """INSERT INTO extensioninstalls (uid, extension, version)
                           VALUES (%s, %s, %s)""",
        (user_id, extension_id, version_id))
Example #22
0
 def __call__(self, value, context):
     from extensions.extension import Extension
     super(ExtensionId, self).__call__(value, context)
     return Extension.fromId(context.db, value)
Example #23
0
def renderManageExtensions(req, db, user):
    cursor = db.cursor()

    what = page.utils.getParameter(req, "what", "available")
    selected_versions = page.utils.json_decode(page.utils.getParameter(req, "select", "{}"))
    focused = page.utils.getParameter(req, "focus", None)

    if what == "available":
        title = "Available Extensions"
        other = ("installed extensions", "/manageextensions?what=installed" + ("&user="******""))
        listed_extensions = Extension.find()
    elif what == "installed":
        title = "Installed Extensions"
        other = ("available extensions", "/manageextensions?what=available" + ("&user="******""))
        cursor.execute("""SELECT DISTINCT users.name, extensions.name
                            FROM users
                            JOIN extensions ON (extensions.author=users.id)
                            JOIN extensionversions ON (extensionversions.extension=extensions.id)
                 LEFT OUTER JOIN extensionroles_page ON (extensionroles_page.version=extensionversions.id AND extensionroles_page.uid=%s)
                 LEFT OUTER JOIN extensionroles_processcommits ON (extensionroles_processcommits.version=extensionversions.id AND extensionroles_processcommits.uid=%s)
                           WHERE extensionroles_page.uid IS NOT NULL
                              OR extensionroles_processcommits.uid IS NOT NULL""",
                       (user.id, user.id))
        listed_extensions = [Extension(*row) for row in cursor]

    req.content_type = "text/html; charset=utf-8"

    document = htmlutils.Document(req)
    document.setTitle("Manage Extensions")

    html = document.html()
    head = html.head()
    body = html.body()

    def generateRight(target):
        target.a("button", href="tutorial?item=extensions").text("Tutorial")
        target.text(" ")
        target.a("button", href="tutorial?item=extensions-api").text("API Documentation")

    page.utils.generateHeader(body, db, user, current_page="extensions", generate_right=generateRight)

    document.addExternalStylesheet("resource/manageextensions.css")
    document.addExternalScript("resource/manageextensions.js")
    document.addInternalScript(user.getJS())

    table = page.utils.PaleYellowTable(body, title)
    table.titleRight.a(href=other[1]).text("[" + other[0] + "]")

    for extension in listed_extensions:
        extension_path = extension.getPath()
        author = dbutils.User.fromName(db, extension.getAuthorName())

        if focused and extension.getKey() != focused:
            continue

        selected_version = selected_versions.get(extension.getKey(), False)
        installed_sha1, installed_version = extension.getInstalledVersion(db, user)

        if selected_version is False:
            selected_version = installed_version

        if selected_version is None: install_version = "live"
        elif selected_version is not False: install_version = "version/%s" % selected_version
        else: install_version = None

        try:
            if selected_version is False:
                manifest = extension.readManifest()
            else:
                manifest = extension.getInstallationStatus(db, user, selected_version)
        except ManifestError, error:
            manifest = None

        if installed_sha1:
            current_sha1 = extension.getCurrentSHA1(installed_version)

        if manifest:
            if what == "available" and author != user and manifest.hidden: continue
        else:
            if author != user: continue

        if manifest:
            buttons = []

            if installed_version is False:
                if install_version:
                    buttons.append(("Install", "installExtension(%s, %s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()), htmlutils.jsify(install_version))))
            else:
                buttons.append(("Uninstall", "uninstallExtension(%s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()))))

                if installed_sha1 and installed_sha1 != current_sha1: label = "Update"
                elif manifest.status != "installed": label = "Reinstall"
                else: label = None

                if label: buttons.append((label, "reinstallExtension(%s, %s, %s)" % (htmlutils.jsify(extension.getAuthorName()), htmlutils.jsify(extension.getName()), htmlutils.jsify(install_version))))
        else:
            buttons = None

        def renderItem(target):
            span = target.span("name")
            span.b().text(extension.getName())
            span.text(" by %s" % author.fullname)

            span = target.span("details")
            span.b().text("Details: ")
            select = span.select("details", critic_author=extension.getAuthorName(), critic_extension=extension.getName())
            select.option(value='', selected="selected" if selected_version is False else None).text("Select version")
            versions = extension.getVersions()
            if versions:
                optgroup = select.optgroup(label="Official Versions")
                for version in extension.getVersions():
                    optgroup.option(value="version/%s" % version, selected="selected" if selected_version == version else None).text("%s" % version.upper())
            optgroup = select.optgroup(label="Development")
            optgroup.option(value='live', selected="selected" if selected_version is None else None).text("LIVE")

            if manifest:
                is_installed = manifest.status in ("partial", "installed") or installed_version is not False

                if is_installed:
                    target.span("installed").text(" [installed]")

                target.div("description").preformatted().text(manifest.description, linkify=True)
            else:
                is_installed = False

                target.div("description broken").preformatted().a(href="loadmanifest?author=%s&name=%s" % (extension.getAuthorName(), extension.getName())).text("[This extension has an invalid MANIFEST file]")

            if selected_version is False:
                return

            pages = []
            injects = []
            processcommits = []
            processchanges = []
            scheduled = []

            if manifest:
                for role in manifest.roles:
                    if isinstance(role, PageRole):
                        pages.append(role)
                    elif isinstance(role, InjectRole):
                        injects.append(role)
                    elif isinstance(role, ProcessCommitsRole):
                        processcommits.append(role)
                    elif isinstance(role, ProcessChangesRole):
                        processchanges.append(role)
                    elif isinstance(role, ScheduledRole):
                        scheduled.append(role)

            role_table = target.table("roles")

            if pages:
                role_table.tr().th(colspan=2).text("Pages")

                for role in pages:
                    row = role_table.tr()
                    url = "%s/%s" % (dbutils.getURLPrefix(db), role.pattern)
                    if is_installed and role.installed and "*" not in url:
                        row.td("pattern").a(href=url).text(url)
                    else:
                        row.td("pattern").text(url)
                    td = row.td("description")
                    td.text(role.description)

                    if is_installed and not role.installed:
                        td.text(" ")
                        td.span("inactive").text("[Not active!]")

            if injects:
                role_table.tr().th(colspan=2).text("Page Injections")

                for role in injects:
                    row = role_table.tr()
                    row.td("pattern").text("%s/%s" % (dbutils.getURLPrefix(db), role.pattern))
                    td = row.td("description")
                    td.text(role.description)

                    if is_installed and not role.installed:
                        td.text(" ")
                        td.span("inactive").text("[Not active!]")

            if processcommits:
                role_table.tr().th(colspan=2).text("ProcessCommits hooks")
                ul = role_table.tr().td(colspan=2).ul()

                for role in processcommits:
                    li = ul.li()
                    li.text(role.description)

                    if is_installed and not role.installed:
                        li.text(" ")
                        li.span("inactive").text("[Not active!]")

            if processchanges:
                role_table.tr().th(colspan=2).text("ProcessChanges hooks")
                ul = role_table.tr().td(colspan=2).ul()

                for role in processchanges:
                    li = ul.li()
                    li.text(role.description)

                    if is_installed and not role.installed:
                        li.text(" ")
                        li.span("inactive").text("[Not active!]")

            if scheduled:
                role_table.tr().th(colspan=2).text("Scheduled hooks")

                for role in scheduled:
                    row = role_table.tr()
                    row.td("pattern").text("%s @ %s" % (role.frequency, role.at))
                    td = row.td("description")
                    td.text(role.description)

                    if is_installed and not role.installed:
                        td.text(" ")
                        td.span("inactive").text("[Not active!]")

        cursor.execute("""SELECT DISTINCT uid
                            FROM extensionroles
                            JOIN extensionversions ON (extensionversions.id=extensionroles.version)
                            JOIN extensions ON (extensions.id=extensionversions.extension)
                           WHERE extensions.author=%s
                             AND extensions.name=%s""",
                       (author.id, extension.getName()))

        installed_count = len(cursor.fetchall())

        if installed_count: installed = " (installed by %d user%s)" % (installed_count, "s" if installed_count > 1 else "")
        else: installed = ""

        table.addItem("Extension", renderItem, extension_path + "/" + installed, buttons)
Example #24
0
 def __call__(self, value, context):
     from extensions.extension import Extension
     super(ExtensionKey, self).__call__(value, context)
     author_name, _, extension_name = value.rpartition("/")
     return Extension(author_name, extension_name)
Example #25
0
def generateHeader(target, db, user, generate_right=None, current_page=None, extra_links=[], profiler=None):
    target.addExternalStylesheet("resource/third-party/jquery-ui.css")
    target.addExternalStylesheet("resource/third-party/chosen.css")
    target.addExternalStylesheet("resource/overrides.css")
    target.addExternalStylesheet("resource/basic.css")
    target.addInternalStylesheet(".defaultfont, body { %s }" % user.getPreference(db, "style.defaultFont"))
    target.addInternalStylesheet(".sourcefont { %s }" % user.getPreference(db, "style.sourceFont"))
    target.addExternalScript("resource/third-party/jquery.js")
    target.addExternalScript("resource/third-party/jquery-ui.js")
    target.addExternalScript("resource/third-party/jquery-ui-autocomplete-html.js")
    target.addExternalScript("resource/third-party/chosen.js")
    target.addExternalScript("resource/basic.js")

    target.noscript().h1("noscript").blink().text("Please enable scripting support!")

    row = target.table("pageheader", width='100%').tr()
    left = row.td("left", valign='bottom', align='left')
    b = left.b()

    opera_class = "opera"

    if configuration.debug.IS_DEVELOPMENT:
        opera_class += " development"

    b.b(opera_class, onclick="location.href='/';").text("Opera")
    b.b("critic", onclick="location.href='/';").text("Critic")

    links = []

    if not user.isAnonymous():
        links.append(["home", "Home", None, None])

    links.append(["dashboard", "Dashboard", None, None])
    links.append(["branches", "Branches", None, None])
    links.append(["search", "Search", None, None])

    if user.hasRole(db, "administrator"):
        links.append(["services", "Services", None, None])
    if user.hasRole(db, "repositories"):
        links.append(["repositories", "Repositories", None, None])

    if profiler:
        profiler.check("generateHeader (basic)")

    if configuration.extensions.ENABLED:
        from extensions.extension import Extension

        updated = Extension.getUpdatedExtensions(db, user)

        if updated:
            link_title = "\n".join([("%s by %s can be updated!" % (extension_name, author_fullname)) for author_fullname, extension_name in updated])
            links.append(["manageextensions", "Extensions (%d)" % len(updated), "color: red", link_title])
        else:
            links.append(["manageextensions", "Extensions", None, None])

        if profiler:
            profiler.check("generateHeader (updated extensions)")

    links.append(["config", "Config", None, None])
    links.append(["tutorial", "Tutorial", None, None])

    if user.isAnonymous():
        count = 0
    else:
        cursor = db.cursor()
        cursor.execute("""SELECT COUNT(*)
                            FROM newsitems
                 LEFT OUTER JOIN newsread ON (item=id AND uid=%s)
                           WHERE uid IS NULL""",
                       (user.id,))
        count = cursor.fetchone()[0]

    if count:
        links.append(["news", "News (%d)" % count, "color: red", "There are %d unread news items!" % count])
    else:
        links.append(["news", "News", None, None])

    if profiler:
        profiler.check("generateHeader (news)")

    req = target.getRequest()

    if configuration.base.AUTHENTICATION_MODE != "host" \
           and configuration.base.SESSION_TYPE == "cookie":
        if user.isAnonymous():
            links.append(["javascript:void(location.href='/login?target='+encodeURIComponent(location.href));", "Sign in", None, None])
        elif not req or (req.user == user.name and req.session_type == "cookie"):
            links.append(["javascript:signOut();", "Sign out", None, None])

    for url, label in extra_links:
        links.append([url, label, None, None])

    if req and configuration.extensions.ENABLED:
        import extensions.role.inject

        injected = {}

        extensions.role.inject.execute(db, req, user, target, links, injected, profiler=profiler)

        for url in injected.get("stylesheets", []):
            target.addExternalStylesheet(url, use_static=False, order=1)

        for url in injected.get("scripts", []):
            target.addExternalScript(url, use_static=False, order=1)
    else:
        injected = None

    ul = left.ul()

    for index, (url, label, style, title) in enumerate(links):
        if not re.match("[-.a-z]+:|/", url):
            url = "/" + url
        ul.li().a(href=url, style=style, title=title).text(label)

        rel = LINK_RELS.get(label)
        if rel: target.setLink(rel, url)

    right = row.td("right", valign='bottom', align='right')
    if generate_right:
        generate_right(right)
    else:
        right.div("buttons").span("buttonscope buttonscope-global")

    if profiler:
        profiler.check("generateHeader (finish)")

    return injected
Example #26
0
def execute(db, req, user, document, links, injected, profiler=None):
    cursor = db.cursor()

    installs = Extension.getInstalls(db, user)

    def get_matching_path(path_regexp):
        if re.match(path_regexp, req.path):
            return (req.path, req.query)
        elif re.match(path_regexp, req.original_path):
            return (req.original_path, req.original_query)
        else:
            return None, None

    query = None

    for extension_id, version_id, version_sha1, is_universal in installs:
        handlers = []

        try:
            if version_id is not None:
                cursor.execute(
                    """SELECT script, function, path
                                    FROM extensionroles
                                    JOIN extensioninjectroles ON (role=id)
                                   WHERE version=%s
                                ORDER BY id ASC""", (version_id, ))

                for script, function, path_regexp in cursor:
                    path, query = get_matching_path(path_regexp)
                    if path is not None:
                        handlers.append((path, query, script, function))

                if not handlers:
                    continue

                extension = Extension.fromId(db, extension_id)
                manifest = Manifest.load(getExtensionInstallPath(version_sha1))
            else:
                extension = Extension.fromId(db, extension_id)
                manifest = Manifest.load(extension.getPath())

                for role in manifest.roles:
                    if isinstance(role, InjectRole):
                        path, query = get_matching_path(role.regexp)
                        if path is not None:
                            handlers.append(
                                (path, query, role.script, role.function))

                if not handlers:
                    continue

            def construct_query(query):
                if not query:
                    return "null"

                params = urlparse.parse_qs(query, keep_blank_values=True)

                for key in params:
                    values = params[key]
                    if len(values) == 1:
                        if not values[0]:
                            params[key] = None
                        else:
                            params[key] = values[0]

                return (
                    "Object.freeze({ raw: %s, params: Object.freeze(%s) })" %
                    (json_encode(query), json_encode(params)))

            preferences = None
            commands = []

            for path, query, script, function in handlers:
                argv = "[%s, %s]" % (jsify(path), construct_query(query))

                try:
                    stdout_data = executeProcess(
                        manifest, "inject", script, function, extension_id,
                        user.id, argv, configuration.extensions.SHORT_TIMEOUT)
                except ProcessTimeout:
                    raise InjectError("Timeout after %d seconds." %
                                      configuration.extensions.SHORT_TIMEOUT)
                except ProcessError as error:
                    if error.returncode < 0:
                        raise InjectError("Process terminated by signal %d." %
                                          -error.returncode)
                    else:
                        raise InjectError("Process returned %d.\n%s" %
                                          (error.returncode, error.stderr))

                for line in stdout_data.splitlines():
                    if line.strip():
                        commands.append(processLine(path, line.strip()))

            for command, value in commands:
                if command == "script":
                    document.addExternalScript(value,
                                               use_static=False,
                                               order=1)
                elif command == "stylesheet":
                    document.addExternalStylesheet(value,
                                                   use_static=False,
                                                   order=1)
                elif command == "link":
                    for index, (_, label, _, _) in enumerate(links):
                        if label == value["label"]:
                            if value["url"] is None:
                                del links[index]
                            else:
                                links[index][0] = value["url"]
                            break
                    else:
                        if value["url"] is not None:
                            links.append(
                                [value["url"], value["label"], None, None])
                elif command == "preference":
                    if not preferences:
                        preferences = []
                        injected.setdefault("preferences", []).append(
                            (extension.getName(), extension.getAuthor(db),
                             preferences))
                    preferences.append(value)

            if profiler:
                profiler.check("inject: %s" % extension.getKey())
        except ExtensionError as error:
            document.comment(
                "\n\n[%s] Extension error:\nInvalid extension:\n%s\n\n" %
                (error.extension.getKey(), error.message))
        except ManifestError as error:
            document.comment(
                "\n\n[%s] Extension error:\nInvalid MANIFEST:\n%s\n\n" %
                (extension.getKey(), error.message))
        except InjectError as error:
            document.comment("\n\n[%s] Extension error:\n%s\n\n" %
                             (extension.getKey(), error.message))