コード例 #1
0
ファイル: page.py プロジェクト: 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
コード例 #2
0
ファイル: processcommits.py プロジェクト: Aessy/critic
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)
コード例 #3
0
ファイル: processchanges.py プロジェクト: ahockersten/critic
def execute(db, user_id, batch_id, output):
    cursor = db.cursor()
    cursor.execute("SELECT review, uid FROM batches WHERE id=%s", (batch_id,))

    review_id, batch_user_id = cursor.fetchone()

    cursor.execute("""SELECT extensions.id, extensions.author, extensions.name, extensionversions.sha1, roles.id, roles.script, roles.function
                        FROM extensions
                        JOIN extensionversions ON (extensionversions.extension=extensions.id)
                        JOIN extensionroles_processchanges AS roles ON (roles.version=extensionversions.id)
                       WHERE uid=%s AND roles.skip < %s""", (user_id, batch_id))

    rows = cursor.fetchall()

    if rows:
        for extension_id, author_id, extension_name, sha1, role_id, script, function in rows:
            cursor.execute("INSERT INTO extensionprocessedbatches (batch, role) VALUES (%s, %s)", (batch_id, role_id))

            # Don't do further processing of own batches.
            if batch_user_id == user_id: continue

            author = dbutils.User.fromId(db, author_id)

            if sha1 is None:
                extension_path = getExtensionPath(author.name, extension_name)
            else:
                extension_path = getExtensionInstallPath(sha1)

            class Error(Exception):
                pass

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

            try:
                try:
                    manifest = Manifest.load(extension_path)
                except ManifestError, error:
                    raise Error("Invalid MANIFEST:\n%s" % error.message)

                for role in manifest.roles:
                    if isinstance(role, ProcessChangesRole) and role.script == script and role.function == function:
                        break
                else:
                    continue

                argv = "[(new critic.Review(%d)).getBatch(%d)]" % (review_id, batch_id)

                try:
                    stdout_data = executeProcess(manifest, role, extension_id, user_id, argv, configuration.extensions.SHORT_TIMEOUT)
                except ProcessTimeout:
                    raise Error("Timeout after %d seconds." % configuration.extensions.SHORT_TIMEOUT)
                except ProcessError, error:
                    if error.returncode < 0:
                        if -error.returncode == signal.SIGXCPU:
                            raise Error("CPU time limit (5 seconds) exceeded.")
                        else:
                            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_name, line)
            except Error, error:
                print_header()
                print >>output, "[%s] Extension error: %s" % (extension_name, error.message)
コード例 #4
0
ファイル: filterhook.py プロジェクト: tomekjarosik/critic
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)
コード例 #5
0
ファイル: page.py プロジェクト: tomekjarosik/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
コード例 #6
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)
コード例 #7
0
ファイル: inject.py プロジェクト: Aessy/critic
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))
コード例 #8
0
ファイル: inject.py プロジェクト: ahockersten/critic
def execute(db, paths, args, user, document, links, injected, profiler=None):
    cursor = db.cursor()

    cursor.execute("""SELECT extensions.id, extensions.author, extensions.name, extensionversions.sha1, roles.path, roles.script, roles.function
                        FROM extensions
                        JOIN extensionversions ON (extensionversions.extension=extensions.id)
                        JOIN extensionroles_inject AS roles ON (roles.version=extensionversions.id)
                       WHERE uid=%s""", (user.id,))

    for extension_id, author_id, extension_name, sha1, regexp, script, function in cursor:
        for path in paths:
            match = re.match(regexp, path)
            if match: break

        if match:
            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 args:
                query = "Object.freeze({ raw: %s, params: Object.freeze({ %s }) })" % (jsify(args), ", ".join(map(param, args.split("&"))))
            else:
                query = "null"

            author = dbutils.User.fromId(db, author_id)

            if sha1 is None:
                extension_path = os.path.join(configuration.extensions.SEARCH_ROOT, author.name, "CriticExtensions", extension_name)
            else:
                extension_path = os.path.join(configuration.extensions.INSTALL_DIR, sha1)

            class Error(Exception):
                pass

            try:
                try:
                    manifest = Manifest.load(extension_path)
                except ManifestError, error:
                    raise Error("Invalid MANIFEST:\n%s" % error.message)

                for role in manifest.roles:
                    if isinstance(role, InjectRole) and role.regexp == regexp and role.script == script and role.function == function:
                        break
                else:
                    continue

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

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

                commands = []

                def processLine(line):
                    try:
                        command, value = line.split(" ", 1)
                    except ValueError:
                        raise Error("Invalid line in output: %r" % line)

                    if command not in ("link", "script", "stylesheet", "preference"):
                        raise Error("Invalid command: %r" % command)

                    try:
                        value = json_decode(value.strip())
                    except ValueError:
                        raise Error("Invalid JSON: %r" % value.strip())

                    def is_string(value):
                        return isinstance(value, basestring)

                    if command in ("script", "stylesheet") and not is_string(value):
                        raise Error("Invalid value for %r: %r (expected string)" % (command, value))
                    elif command == "link":
                        if not isinstance(value, list) or len(value) != 2:
                            raise Error("Invalid value for %r: %r (expected array of length two)" % (command, value))
                        elif not is_string(value[0]):
                            raise Error("Invalid value for %r: %r (expected string at array[0])" % (command, value))
                        elif not (is_string(value[1]) or value[1] is None):
                            raise Error("Invalid value for %r: %r (expected string or null at array[1])" % (command, value))
                    elif command == "preference":
                        if path != "config":
                            raise Error("Invalid command: %r only valid on /config page" % command)
                        elif not isinstance(value, dict):
                            raise Error("Invalid value for %r: %r (expected object)" % (command, value))

                        for name in ("url", "name", "type", "value", "default", "description"):
                            if name not in value:
                                raise Error("Invalid value for %r: %r (missing property: %r)" % (command, value, name))

                        preference_url = value["url"]
                        preference_name = value["name"]
                        preference_type = value["type"]
                        preference_value = value["value"]
                        preference_default = value["default"]
                        preference_description = value["description"]

                        if not is_string(preference_url):
                            raise Error("Invalid value for %r: %r (expected string as %r)" % (command, value, "url"))
                        elif not is_string(preference_name):
                            raise Error("Invalid value for %r: %r (expected string as %r)" % (command, value, "name"))
                        elif not is_string(preference_description):
                            raise Error("Invalid value for %r: %r (expected string as %r)" % (command, value, "description"))

                        if is_string(preference_type):
                            if preference_type not in ("boolean", "integer", "string"):
                                raise Error("Invalid value for %r: %r (unsupported preference type)" % (command, value))

                            if preference_type == "boolean": type_check = lambda value: isinstance(value, bool)
                            elif preference_type == "integer": type_check = lambda value: isinstance(value, int)
                            else: type_check = is_string

                            if not type_check(preference_value):
                                raise Error("Invalid value for %r: %r (type mismatch between %r and %r)" % (command, value, "value", "type"))

                            if not type_check(preference_default):
                                raise Error("Invalid value for %r: %r (type mismatch between %r and %r)" % (command, value, "default", "type"))
                        else:
                            if not isinstance(preference_type, list):
                                raise Error("Invalid value for %r: %r (invalid %r, expected string or array)" % (command, value, "type"))

                            for index, choice in enumerate(preference_type):
                                if not isinstance(choice, dict) \
                                        or not isinstance(choice.get("value"), basestring) \
                                        or not isinstance(choice.get("title"), basestring):
                                    raise Error("Invalid value for %r: %r (invalid preference choice: %r)" % (command, value, choice))

                            choices = set([choice["value"] for choice in preference_type])

                            if not is_string(preference_value) or preference_value not in choices:
                                raise Error("Invalid value for %r: %r (%r not among valid choices)" % (command, value, "value"))

                            if not is_string(preference_default) or preference_default not in choices:
                                raise Error("Invalid value for %r: %r (%r not among valid choices)" % (command, value, "default"))

                    commands.append((command, value))
                    return True

                failed = False

                for line in stdout_data.splitlines():
                    if line.strip():
                        if not processLine(line.strip()):
                            failed = True
                            break

                if not failed:
                    preferences = None

                    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, (url, label, not_current, style, title) in enumerate(links):
                                if label == value[0]:
                                    if value[1] is None: del links[index]
                                    else: links[index][0] = value[0]
                                    break
                            else:
                                if value[1] is not None:
                                    links.append([value[1], value[0], True, None, None])
                        elif command == "preference":
                            if not preferences:
                                preferences = []
                                injected.setdefault("preferences", []).append((extension_name, author, preferences))
                            preferences.append(value)
            except Error, error:
                document.comment("\n\n[%s/%s] Extension error:\n%s\n\n" % (author.name, extension_name, error.message))
コード例 #9
0
ファイル: filterhook.py プロジェクト: Aessy/critic
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)
コード例 #10
0
ファイル: inject.py プロジェクト: tomekjarosik/critic
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))
コード例 #11
0
ファイル: processcommits.py プロジェクト: ahockersten/critic
def execute(db, user, review, all_commits, old_head, new_head, output):
    cursor = db.cursor()
    cursor.execute("""SELECT extensions.id, extensions.author, extensions.name, extensionversions.sha1, roles.script, roles.function
                        FROM extensions
                        JOIN extensionversions ON (extensionversions.extension=extensions.id)
                        JOIN extensionroles_processcommits AS roles ON (roles.version=extensionversions.id)
                       WHERE uid=%s
                         AND filter IS NULL""", (user.id,))

    rows = cursor.fetchall()

    if rows:
        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
            db.commit()
        else:
            changeset_arg = "null"

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

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

        for extension_id, author_id, extension_name, sha1, script, function in rows:
            author = dbutils.User.fromId(db, author_id)

            if sha1 is None: extension_path = getExtensionPath(author.name, extension_name)
            else: extension_path = getExtensionInstallPath(sha1)

            class Error(Exception):
                pass

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

            try:
                try:
                    manifest = Manifest.load(extension_path)
                except ManifestError, error:
                    raise Error("Invalid MANIFEST:\n%s" % error.message)

                for role in manifest.roles:
                    if isinstance(role, ProcessCommitsRole) and role.script == script and role.function == function:
                        break
                else:
                    continue

                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, role, extension_id, user.id, argv, configuration.extensions.SHORT_TIMEOUT)
                except ProcessTimeout:
                    raise Error("Timeout after %d seconds." % configuration.extensions.SHORT_TIMEOUT)
                except ProcessError, error:
                    if error.returncode < 0:
                        if -error.returncode == signal.SIGXCPU:
                            raise Error("CPU time limit (5 seconds) exceeded.")
                        else:
                            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_name, line)
            except Error, error:
                print_header()
                print >>output, "[%s] Extension error: %s" % (extension_name, error.message)
コード例 #12
0
ファイル: page.py プロジェクト: ryfow/critic
def execute(db, req, user):
    cursor = db.cursor()

    cursor.execute("""SELECT extensions.id, extensions.author, extensions.name, extensionversions.sha1, roles.path, roles.script, roles.function
                        FROM extensions
                        JOIN extensionversions ON (extensionversions.extension=extensions.id)
                        JOIN extensionroles_page AS roles ON (roles.version=extensionversions.id)
                       WHERE uid=%s""", (user.id,))

    for extension_id, author_id, extension_name, sha1, regexp, script, function in cursor:
        if re.match(regexp, req.path):
            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.create(null, { %s })" % ", ".join(["%s: { value: %s, enumerable: true }" % (jsify(name), jsify(value)) for name, value in req.getRequestHeaders().items()])

            author = dbutils.User.fromId(db, author_id)

            if sha1 is None: extension_path = os.path.join(configuration.extensions.SEARCH_ROOT, author.name, "CriticExtensions", extension_name)
            else: extension_path = os.path.join(configuration.extensions.INSTALL_DIR, sha1)

            manifest = Manifest.load(extension_path)

            for role in manifest.roles:
                if isinstance(role, PageRole) and role.regexp == regexp and role.script == script and role.function == function:
                    break
            else:
                continue

            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":
                stdin_data = req.read()
            else:
                stdin_data = None

            before = time.time()

            try:
                stdout_data = executeProcess(manifest, role, extension_id, user.id, argv, configuration.extensions.LONG_TIMEOUT,
                                             stdin=stdin_data, rlimit_cpu=60)
            except ProcessTimeout:
                req.setStatus(500, "Extension Timeout")
                return "Extension timed out!"
            except ProcessError, error:
                req.setStatus(500, "Extension Failure")
                if error.returncode < 0:
                    if -error.returncode == signal.SIGXCPU:
                        return "Extension failure: time limit (5 CPU seconds) exceeded\n"
                    else:
                        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