Reverse-engineer a kojiweb URL into an equivalent API response.

        Only a few kojiweb URL endpoints work here.

        See also connect_from_web().

        :param url: ``str``, for example
                    "http://cbs.centos.org/koji/buildinfo?buildID=21155"
        :returns: deferred that when fired returns a Munch (dict-like) object
                  with data about this resource, or None if we could not parse
                  the url.

def from_web(self, url):
        """
        Reverse-engineer a kojiweb URL into an equivalent API response.

        Only a few kojiweb URL endpoints work here.

        See also connect_from_web().

        :param url: ``str``, for example
                    "http://cbs.centos.org/koji/buildinfo?buildID=21155"
        :returns: deferred that when fired returns a Munch (dict-like) object
                  with data about this resource, or None if we could not parse
                  the url.
        """
        # Treat any input with whitespace as invalid:
        if re.search(r'\s', url):
            return defer.succeed(None)
        o = urlparse(url)
        endpoint = os.path.basename(o.path)
        if o.query:
            query = parse_qs(o.query)
        # Known Kojiweb endpoints:
        endpoints = {
            'buildinfo': ('buildID', self.getBuild),
            'channelinfo': ('channelID', self.getChannel),
            'hostinfo': ('hostID', self.getHost),
            'packageinfo': ('packageID', self.getPackage),
            'taskinfo': ('taskID', self.getTaskInfo),
            'taginfo': ('tagID', self.getTag),
            'targetinfo': ('targetID', self.getTarget),
            'userinfo': ('userID', self.getUser),
        }
        try:
            (param, method) = endpoints[endpoint]
        except KeyError:
            return defer.succeed(None)
        try:
            id_str = query[param][0]
            id_ = int(id_str)
        except (KeyError, ValueError):
            return defer.succeed(None)
        return method(id_)