<commit_before>"""
This module encapsulate the logic for querying an HTTP server.
"""

import base64
import getpass
import logging
import json
import requests

from libpycr.config import Config
from libpycr.exceptions import RequestError
from libpycr.utils.system import fail

from requests.auth import HTTPDigestAuth
from requests.exceptions import ConnectionError, RequestException

from urlparse import urlparse


class RequestFactory(object):
    """A Request factory."""

    # Logger
    log = logging.getLogger(__name__)

    # HTTP protocol method supported by this application
    GET = ('GET', requests.get)
    POST = ('POST', requests.post)
    DELETE = ('DELETE', requests.delete)

    # Response encoding
    BASE64, JSON, TEXT = range(3)

    # To prevent against Cross Site Script Inclusion (XSSI) attacks, the JSON
    # response body starts with a magic prefix line that must be stripped
    GERRIT_MAGIC = ")]}'\n"

    @classmethod
    def set_auth_token(cls, username, password=<PASSWORD>):
        """
        Set the authentication pair to use for HTTP requests.

        PARAMETERS
            username: the account username
            password: the account HTTP password
        """

        Config.set('gerrit.username', username)
        Config.set('gerrit.password', password)

    @classmethod
    def set_host(cls, host):
        """Set the Gerrit Code Review server host name.

        PARAMETERS
            host: the server's host
        """

        Config.set('gerrit.host', host)

    @classmethod
    def set_unsecure_connection(cls, unsecure):
        """If True, requests will be made over HTTP instead of HTTPS.

        Always use HTTPS by default.

        PARAMETERS
            unsecure: True to use HTTP
        """

        Config.set('gerrit.unsecure', unsecure)

    @classmethod
    def require_auth(cls):
        """
        Return True if authentication is required.
        Check whether cls.username is None.

        RETURNS
            True if auth required, False otherwise
        """

        return Config.get('gerrit.username') is not None

    @classmethod
    def get_http_digest_auth_token(cls):
        """
        Return the HTTPDigestAuth object to use for authentication.
        Prompt the user if the password is unknown.

        RETURNS
            requests.auth.HTTPDigestAuth
        """

        username = Config.get('gerrit.username')
        password = Config.get('gerrit.password')

        if password is None:
            password = <PASSWORD>()

        return HTTPDigestAuth(username, password)

    @classmethod
    def get_remote_base_url(cls):
        """
        Return the Gerrit Code Review server base URL.

        RETURNS
            the Gerrit Code Review server base URL as a string
        """

        url = Config.get('gerrit.host')
        unsecure = Config.get('gerrit.unsecure', False)

        if url is None:
            fail('gerrit.host not set')

        # From Gerrit Code Review Documentation (section Authentication):
        # https://gerrit-review.googlesource.com/Documentation/rest-api.html
        #
        # Users (and programs) may authenticate using HTTP authentication by
        # supplying the HTTP password from the user's account settings page.
        # Gerrit by default uses HTTP digest authentication. To authenticate,
        # prefix the endpoint URL with /a/. For example to authenticate to
        # /projects/ request URL /a/projects/.

        if RequestFactory.require_auth():
            url = '%s/a' % url

        if not url.startswith('http://') or not url.startswith('https://'):
            return '%s://%s' % ('http' if unsecure else 'https', url)

        return url

    @classmethod
    def send(cls, endpoint, method=GET, encoding=JSON, **kwargs):
        """
        Return the result of a HTTP request.

        PARAMETERS
            endpoint: the endpoint to the request
            method: HTTP protocol method to use (either RequestFactory.GET or
                RequestFactory.POST)
            encoding: expected response format (JSON, base64 or plain text)
            **kwargs: any additional arguments to the underlying API call

        RETURNS
            a tuple of two elements: the raw response (stripped from magic for
                the JSON format) and the decoded object of that response, or
                None if expected_encoding is TEXT. If response is no content
                (status code 204), returns a tuple of None values

        RAISES
            requests.exceptions.RequestException on error
        """

        cls.log.debug('Query URL: %s' % endpoint)

        name, callback = method
        headers = kwargs['headers'] if 'headers' in kwargs else {}

        if 'auth' in kwargs.keys() or not cls.require_auth():
            return callback(endpoint, **kwargs)

        try:
            kwargs['auth'] = RequestFactory.get_http_digest_auth_token()
            kwargs['headers'] = headers

            response = callback(endpoint, **kwargs)

            if response.status_code == 204:
                # No content
                return None, None

            if response.status_code != 200:
                response.raise_for_status()

        except ConnectionError as why:
            fail('Unable to connect to %s' % urlparse(endpoint).netloc)

        except RequestException as why:
            raise RequestError(
                response.status_code,
                'HTTP %s request failed: %s' % (name, endpoint), why)

        if encoding == RequestFactory.BASE64:
            encoded = response.text
            b64response = encoded + '=' * ((4 - len(encoded) % 4) % 4)

            try:
                decoded_response = base64.b64decode(b64response)
            except TypeError:
                # TypeError: incorrect padding
                fail('invalid response stream (could not decode base64)')

        elif encoding == RequestFactory.JSON:
            if not response.text.startswith(RequestFactory.GERRIT_MAGIC):
                fail('invalid response stream (magic prefix not found)')

            json_response = response.text[len(RequestFactory.GERRIT_MAGIC):]
            decoded_response = json.loads(json_response)

        else:
            decoded_response = None

        return response.text, decoded_response

    @classmethod
    def get(cls, endpoint, **kwargs):
        """<commit_msg>Optimize requests made to the server.<commit_after>"""
This module encapsulate the logic for querying an HTTP server.
"""

import base64
import getpass
import logging
import json
import requests

from libpycr.config import Config
from libpycr.exceptions import RequestError
from libpycr.utils.system import fail

from requests.auth import HTTPDigestAuth
from requests.exceptions import ConnectionError, RequestException

from urlparse import urlparse


class RequestFactory(object):
    """A Request factory."""

    # Logger
    log = logging.getLogger(__name__)

    # HTTP protocol method supported by this application
    GET = 'GET'
    POST = 'POST'
    DELETE = 'DELETE'

    # Response encoding
    BASE64, JSON, TEXT = range(3)

    # To prevent against Cross Site Script Inclusion (XSSI) attacks, the JSON
    # response body starts with a magic prefix line that must be stripped
    GERRIT_MAGIC = ")]}'\n"

    # The session object, to enable connection reuse
    _session = None

    @classmethod
    def set_auth_token(cls, username, password=None):
        """
        Set the authentication pair to use for HTTP requests.

        PARAMETERS
            username: the account username
            password: the account HTTP password
        """

        Config.set('gerrit.username', username)
        Config.set('gerrit.password', password)

    @classmethod
    def set_host(cls, host):
        """Set the Gerrit Code Review server host name.

        PARAMETERS
            host: the server's host
        """

        Config.set('gerrit.host', host)

    @classmethod
    def set_unsecure_connection(cls, unsecure):
        """If True, requests will be made over HTTP instead of HTTPS.

        Always use HTTPS by default.

        PARAMETERS
            unsecure: True to use HTTP
        """

        Config.set('gerrit.unsecure', unsecure)

    @classmethod
    def require_auth(cls):
        """
        Return True if authentication is required.
        Check whether cls.username is None.

        RETURNS
            True if auth required, False otherwise
        """

        return Config.get('gerrit.username') is not None

    @classmethod
    def get_http_digest_auth_token(cls):
        """
        Return the HTTPDigestAuth object to use for authentication.
        Prompt the user if the password is unknown.

        RETURNS
            requests.auth.HTTPDigestAuth
        """

        username = Config.get('gerrit.username')
        password = Config.get('gerrit.password')

        if password is None:
            password = getpass.getpass()

        return HTTPDigestAuth(username, password)

    @classmethod
    def get_remote_base_url(cls):
        """
        Return the Gerrit Code Review server base URL.

        RETURNS
            the Gerrit Code Review server base URL as a string
        """

        url = Config.get('gerrit.host')
        unsecure = Config.get('gerrit.unsecure', False)

        if url is None:
            fail('gerrit.host not set')

        # From Gerrit Code Review Documentation (section Authentication):
        # https://gerrit-review.googlesource.com/Documentation/rest-api.html
        #
        # Users (and programs) may authenticate using HTTP authentication by
        # supplying the HTTP password from the user's account settings page.
        # Gerrit by default uses HTTP digest authentication. To authenticate,
        # prefix the endpoint URL with /a/. For example to authenticate to
        # /projects/ request URL /a/projects/.

        if RequestFactory.require_auth():
            url = '%s/a' % url

        if not url.startswith('http://') or not url.startswith('https://'):
            return '%s://%s' % ('http' if unsecure else 'https', url)

        return url

    @classmethod
    def get_session(cls, **kwargs):
        """
        Return a requests.Session object.

        RETURNS
            requests.Session
        """

        if cls._session is None:
            cls._session = requests.Session()

            if cls.require_auth():
                cls._session.auth = RequestFactory.get_http_digest_auth_token()

            headers = kwargs['headers'] if 'headers' in kwargs else {}
            cls._session.headers.update(headers)

        return cls._session

    @classmethod
    def send(cls, endpoint, method=GET, encoding=JSON, **kwargs):
        """
        Return the result of a HTTP request.

        PARAMETERS
            endpoint: the endpoint to the request
            method: HTTP protocol method to use (either RequestFactory.GET or
                RequestFactory.POST)
            encoding: expected response format (JSON, base64 or plain text)
            **kwargs: any additional arguments to the underlying API call

        RETURNS
            a tuple of two elements: the raw response (stripped from magic for
                the JSON format) and the decoded object of that response, or
                None if expected_encoding is TEXT. If response is no content
                (status code 204), returns a tuple of None values

        RAISES
            requests.exceptions.RequestException on error
        """

        cls.log.debug('Query URL: %s' % endpoint)

        try:
            response = cls.get_session().request(method, endpoint, **kwargs)

            if response.status_code == 204:
                # No content
                return None, None

            if response.status_code != 200:
                response.raise_for_status()

        except ConnectionError as why:
            fail('Unable to connect to %s' % urlparse(endpoint).netloc)

        except RequestException as why:
            raise RequestError(
                response.status_code,
                'HTTP %s request failed: %s' % (method, endpoint), why)

        if encoding == RequestFactory.BASE64:
            encoded = response.text
            b64response = encoded + '=' * ((4 - len(encoded) % 4) % 4)

            try:
                decoded_response = base64.b64decode(b64response)
            except TypeError:
                # TypeError: incorrect padding
                fail('invalid response stream (could not decode base64)')

        elif encoding == RequestFactory.JSON:
            if not response.text.startswith(RequestFactory.GERRIT_MAGIC):
                fail('invalid response stream (magic prefix not found)')

            json_response = response.text[len(RequestFactory.GERRIT_MAGIC):]
            decoded_response = json.loads(json_response)

        else:
            decoded_response = None

        return response.text, decoded_response

    @classmethod
    def get(cls, endpoint, **kwargs):
        """
{'fraction_non_alphanumeric': 0.08084358523725835, 'fraction_numerical': 0.0047375257889508676, 'mean_word_length': 2.132599329822882, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5766456', 'n_tokens_mistral': 3473, 'n_tokens_neox': 3259, 'n_words': 1140}