Exemple #1
0
def handle_from_file(file_):
    """
    Converts a standard Python file object into a :class:`HANDLE` object.

    .. warning::

        This function is mainly intended for internal use.  Passing in a file
        object with an invalid file descriptor may crash your interpreter.

    :param file file_:
        The Python file object to convert to a :class:`HANDLE` object.

    :raises InputError:
        Raised if ``file_`` does not appear to be a file object or is currently
        closed.

    :rtype: :class:`HANDLE`
    """
    try:
        fileno = file_.fileno()
    except AttributeError:
        raise InputError("file_",
                         file_,
                         allowed_types=None,
                         message="Expected a file like object for `file_`")
    except ValueError:
        raise InputError(
            "file_",
            file_,
            allowed_types=None,
            message="Expected an open file like object for `file_`")
    else:
        _, library = dist.load()
        return HANDLE(library.handle_from_fd(fileno))
Exemple #2
0
def _environment_to_string(environment):
    """
    This function is used internally by :func:`CreateProcess` to convert
    the input to ``lpEnvironment`` to a string which the underlying C API
    call will understand.

    >>> from pywincffi.kernel32.process import _environment_to_string
    >>> _environment_to_string({"A": "a", "B": "b"})
    'A=a\x00B=b'

    :param environment:
        A dictionary or dictionary like object to convert to a string.

    :raises InputError:
        Raised if we cannot convert ``environment`` to a string.  This can
        happen if:

            * ``environment`` is not a dictionary like object.
            * Not all keys and values in the environment are strings (
              str in Python 3.x, unicode in Python 2.x)
            * One or more of the keys contains the `=` symbol.
    """
    try:
        items = environment.iteritems
    except AttributeError:
        try:
            items = environment.items
        except AttributeError:
            raise InputError(
                "environment",
                environment,
                message="Expected a dictionary like object for `environment`")

    converted = []
    for key, value in items():
        if not isinstance(key, text_type):
            raise InputError(u"environment key {0}".format(key),
                             key,
                             allowed_types=(text_type, ))

        if not isinstance(value, text_type):
            raise InputError(u"environment value {0} (key: {1!r})".format(
                value, key),
                             value,
                             allowed_types=(text_type, ))

        # From Microsoft's documentation on `lpEnvironment`:
        #   Because the equal sign is used as a separator, it must not be used
        #   in the name of an environment variable.
        if u"=" in key:
            raise InputError(
                key,
                key,
                None,
                message=u"Environment keys cannot contain the `=` symbol.  "
                u"Offending key: {0}".format(key))

        converted.append(u"{0}={1}\0".format(key, value))

    return u"".join(converted) + u"\0"
 def test_input_values_are_none(self):
     with self.assertRaises(ValueError):
         InputError("",
                    None,
                    allowed_types=None,
                    allowed_values=None,
                    message=None)
 def test_message_allowed_types_ffi_is_none(self):
     error = InputError("", None, ffi=None, allowed_types=(int, ))
     name = "type" if PY2 else "class"
     self.assertEqual(
         error.message,
         "Expected type(s) (<%s 'int'>,) for ''. Type of '' is "
         "<%s 'NoneType'>." % (name, name))
 def test_message_allowed_types_type_of_failure(self):
     ffi, _ = dist.load()
     error = InputError("", 1, ffi=ffi, allowed_types=(int, ))
     name = "type" if PY2 else "class"
     self.assertEqual(
         error.message,
         "Expected type(s) (<%s 'int'>,) for ''. Type of '' is "
         "<%s 'int'>." % (name, name))
Exemple #6
0
def input_check(name, value, allowed_types=None, allowed_values=None):
    """
    A small wrapper around :func:`isinstance`.  This is mainly meant
    to be used inside of other functions to pre-validate input rater
    than using assertions.  It's better to fail early with bad input
    so more reasonable error message can be provided instead of from
    somewhere deep in cffi or Windows.

    :param str name:
        The name of the input being checked.  This is provided
        so error messages make more sense and can be attributed
        to specific input arguments.

    :param value:
        The value we're performing the type check on.

    :keyword allowed_types:
        The allowed type or types for ``value``.

    :keyword tuple allowed_values:
        A tuple of allowed values.  When provided ``value`` must
        be in this tuple otherwise :class:`InputError` will be
        raised.

    :raises pywincffi.exceptions.InputError:
        Raised if ``value`` is not an instance of ``allowed_types``

    :raises TypeError:
        Raised if ``allowed_values`` is provided and not a tuple.
    """
    if allowed_values is not None and not isinstance(allowed_values, tuple):
        raise TypeError("`allowed_values` must be a tuple")

    if allowed_types is not None and not isinstance(value, allowed_types):
        ffi, _ = dist.load()
        raise InputError(name, value, ffi=ffi, allowed_types=allowed_types)

    if allowed_values is not None and value not in allowed_values:
        ffi, _ = dist.load()
        raise InputError(name,
                         value,
                         None,
                         ffi=ffi,
                         allowed_values=allowed_values)
 def test_message_allowed_types_type_of_success(self):
     ffi, _ = dist.load()
     error = InputError("",
                        ffi.new("wchar_t[0]"),
                        ffi=ffi,
                        allowed_types=(int, ))
     name = "type" if PY2 else "class"
     self.assertEqual(
         error.message,
         "Expected type(s) (<%s 'int'>,) for ''. Type of '' is "
         "CDataOwn(kind='array', cname='wchar_t[0]')." % name)
Exemple #8
0
def socket_from_object(sock):
    """
    Converts a Python socket to a Windows SOCKET object.

    .. warning::

        This function is mainly intended for internal use.  Passing in an
        invalid object may result in a crash.

    :param socket._socketobject sock:
        The Python socket to convert to :class:`pywincffi.wintypes.SOCKET`
        object.

    :rtype: :class:`pywincffi.wintypes.SOCKET`
    """
    try:
        fileno = sock.fileno()

        # In later versions of Python calling fileno() on a closed
        # socket returns -1 instead of raising socket.error.
        if fileno == -1:
            raise socket.error(socket.errno.EBADF, "Bad file descriptor")

    except AttributeError:
        raise InputError("sock",
                         sock,
                         expected_types=None,
                         message="Expected a Python socket object for `sock`")
    except socket.error as error:
        raise InputError("sock",
                         sock,
                         expected_types=None,
                         message="Invalid socket object (error: %s)" % error)
    else:
        ffi, _ = dist.load()
        sock = SOCKET()
        sock._cdata[0] = ffi.cast("SOCKET", fileno)
        return sock
Exemple #9
0
def module_name(path):
    """
    Returns the module name for the given ``path``

        >>> module_name(u"C:\\Python27\\python.exe -c 'print True'")
        'C:\\Python27\\python.exe'
        >>> module_name(u"C:\\Program Files (x86)\\Foo\\program.exe -h")
        'C:\\Program'
        >>> module_name(u"'C:\\Program Files (x86)\\Foo\\program.exe' -h")
        'C:\\Program Files (x86)\\Foo\\program.exe'

    This function is used internally by :func:`CreateProcess` to assist in
    validating input to the ``lpCommandLine`` argument.  When calling
    :func:`CreateProcess` if ``lpApplicationName`` is not set then
    ``lpCommandLine``'s module name cannot exceed ``MAX_PATH``.

    :raises TypeError:
        Raised if ``path`` is not a text type.
    """
    # Try to tokenize the input.  In the case of properly quoted strings
    # the module name should be the first entry.
    for type_, string, _, _, line in generate_tokens(StringIO(path).readline):
        if type_ == STRING and line.startswith(string) and line != string:
            module = string
            break
    else:
        module = path.split(" ", 1)[0]

    if not module:
        raise InputError("",
                         None,
                         None,
                         message="Failed to determine module name in %r" %
                         path)

    # Make sure we're just getting the module name
    # and not any of the original quote characters.
    quote_characters = ("'", '"')
    if module[0] in quote_characters:
        module = module[1:]

    if module[-1] in quote_characters:
        module = module[:-1]

    return module
Exemple #10
0
def _text_to_wchar(text):
    """
    Converts ``text`` to ``wchar_t[len(text)]``.

    :param text:
        The text convert to ``wchar_t``.

    :raises InputError:
        Raised if the type of ``text`` is not a text type.  For
        Python 2 this function expects unicode and for Python 3
        it expects a string.
    """
    if not isinstance(text, text_type):
        raise InputError("text",
                         text,
                         message="Expected %r for `text`" % text_type)

    ffi, _ = dist.load()
    return ffi.new("wchar_t[%d]" % len(text), text)
Exemple #11
0
 def test_ivar_expected_types(self):
     error = InputError("name", "value", (str, int))
     self.assertEqual(error.expected_types, (str, int))
Exemple #12
0
 def test_str(self):
     error = InputError("name", "value", (str, int))
     self.assertEqual(str(error), error.message)
 def test_attribute_message(self):
     error = InputError("", None, message="Foobar")
     self.assertEqual(error.message, "Foobar")
 def test_attribute_allowed_values(self):
     error = InputError("", "", allowed_values=(1, ))
     self.assertEqual(error.allowed_values, (1, ))
 def test_attribute_allowed_types(self):
     error = InputError("", "", allowed_types=(int, ))
     self.assertEqual(error.allowed_types, (int, ))
 def test_attribute_test_value(self):
     error = InputError("", "foo", allowed_types=(int, ))
     self.assertEqual(error.value, "foo")
 def test_both_allowed_values_and_types_defined(self):
     with self.assertRaises(ValueError):
         InputError("", None, allowed_types=(int, ), allowed_values=(2, ))
Exemple #18
0
 def test_custom_message(self):
     error = InputError("name",
                        "value", (str, int),
                        message="Hello, world.")
     self.assertEqual(str(error), "Hello, world.")
Exemple #19
0
 def test_ivar_value(self):
     error = InputError("name", "value", (str, int))
     self.assertEqual(error.value, "value")
Exemple #20
0
def CreateProcess(  # pylint: disable=too-many-arguments,too-many-branches
        lpCommandLine,
        lpApplicationName=None,
        lpProcessAttributes=None,
        lpThreadAttributes=None,
        bInheritHandles=True,
        dwCreationFlags=None,
        lpEnvironment=None,
        lpCurrentDirectory=None,
        lpStartupInfo=None):
    """
    Creates a new process and its primary thread.  The process will be
    created in the same security context as the original process.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/ms682425

    :param str lpCommandLine:
        The command line to be executed.  The maximum length of this parameter
        is 32768.  If no value is provided for ``lpApplicationName`` then
        the module name portion of ``lpCommandLine`` cannot exceed
        ``MAX_PATH``.

    :keyword pywincffi.wintypes.STARTUPINFO lpStartupInfo:
        See Microsoft's documentation for additional information.

        .. warning::

            The STARTUPINFOEX structure is not currently supported
            for this input.

    :keyword str lpApplicationName:
        The name of the module or application to be executed.  This can be
        either the fully qualified path name or a partial name.  The system
        path will not be searched.  If no value is provided for this keyword
        then the input to ``lpCommandLine`` will be used by Windows instead.

    :keyword pywincffi.wintypes.SECUREITY_ATTRIBUTES lpProcessAttributes:
        Determines whether the returned handle to the new process object
        can be inherited by child processes.  By default, the handle cannot be
        inherited.

    :keyword pywincffi.wintypes.SECUREITY_ATTRIBUTES lpThreadAttributes:
        Determines if the returned handle to the new thread object can
        be inherited by child processes.  By default, the thread cannot be
        inherited.

    :keyword bool bInheritHandles:
        If True (the default) the handles inherited by the calling process
        are inherited by the new process.

    :keyword int dwCreationFlags:
        Controls the priority class and creation of the process.  By default
        the process will flag will default to
        ``NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT``

    :keyword dict lpEnvironment:
        The environment for the new process.  By default the the process
        will be created with the same environment as the parent process.

        .. note::

            All keys and values in the environment must be either unicode
            (Python 2) or strings (Python 3).

        .. note::

            This keyword will completely override the current if you wish to
            update the current environment instead then you will need to make
            and update a copy.

        .. warning::

            Excluding certain system environment variables such as ``PATH`` or
            ``SYSTEMROOT`` may result in crashes or unexpected behaviors
            depending on the program being run.


    :keyword str lpCurrentDirectory:
        The full path to the current directory for the process.  If not
         provided then the process will have the same working directory
         as the parent process.

    :raises InputError:
        Raised if ``lpCommandLine`` is too long or there are other input
        problems.

    :rtype: :class:`pywincffi.kernel32.process.CreateProcessResult`
    :return:
        Returns a named tuple containing ``lpCommandLine`` and
        ``lpProcessInformation``.  The ``lpProcessInformation`` will
        be an instance of
        :class:`pywincffi.wintypes.structures.PROCESS_INFORMATION`
    """
    ffi, library = dist.load()

    if len(lpCommandLine) > library.MAX_COMMAND_LINE:
        raise InputError("lpCommandLine",
                         lpCommandLine,
                         text_type,
                         message="lpCommandLine's length "
                         "cannot exceed %s" % library.MAX_COMMAND_LINE)

    if lpApplicationName is None:
        lpApplicationName = ffi.NULL

        # If lpApplication name is not set then lpCommandLine's
        # module name cannot exceed MAX_PATH.  Rather than letting
        # this hit the Windows API and possibly fail we're check
        # before hand so we can provide a better exception.
        module = module_name(text_type(lpCommandLine))
        if len(module) > library.MAX_PATH:
            raise InputError(
                "lpCommandLine",
                lpCommandLine,
                text_type,
                message="lpCommandLine's module name length cannot "
                "exceed %s if `lpApplicationName` "
                "is not set. Module name was %r" % (library.MAX_PATH, module))
    else:
        input_check("lpApplicationName",
                    lpApplicationName,
                    allowed_types=(text_type, ))

    input_check("lpProcessAttributes",
                lpProcessAttributes,
                allowed_types=(SECURITY_ATTRIBUTES, NoneType))
    lpProcessAttributes = wintype_to_cdata(lpProcessAttributes)

    input_check("lpThreadAttributes",
                lpThreadAttributes,
                allowed_types=(SECURITY_ATTRIBUTES, NoneType))
    lpThreadAttributes = wintype_to_cdata(lpThreadAttributes)

    input_check("bInheritHandles",
                bInheritHandles,
                allowed_values=(True, False))

    if dwCreationFlags is None:
        dwCreationFlags = \
            library.NORMAL_PRIORITY_CLASS | library.CREATE_UNICODE_ENVIRONMENT

    input_check("dwCreationFlags",
                dwCreationFlags,
                allowed_types=(integer_types, ))

    if lpEnvironment is not None:
        lpEnvironment = _text_to_wchar(_environment_to_string(lpEnvironment))
    else:
        lpEnvironment = ffi.NULL

    if lpCurrentDirectory is not None:
        input_check("lpCurrentDirectory",
                    lpCurrentDirectory,
                    allowed_types=(text_type, ))
    else:
        lpCurrentDirectory = ffi.NULL

    if lpStartupInfo is not None:
        # TODO need to add support for STARTUPINFOEX (undocumented)
        input_check("lpStartupInfo",
                    lpStartupInfo,
                    allowed_types=(STARTUPINFO, ))
    else:
        lpStartupInfo = STARTUPINFO()

    lpProcessInformation = PROCESS_INFORMATION()
    code = library.CreateProcess(lpApplicationName, lpCommandLine,
                                 lpProcessAttributes, lpThreadAttributes,
                                 bInheritHandles, dwCreationFlags,
                                 lpEnvironment, lpCurrentDirectory,
                                 wintype_to_cdata(lpStartupInfo),
                                 wintype_to_cdata(lpProcessInformation))
    error_check("CreateProcess", code=code, expected=NON_ZERO)

    return CreateProcessResult(lpCommandLine=lpCommandLine,
                               lpProcessInformation=lpProcessInformation)
Exemple #21
0
def input_check(name, value, allowed_types=None, allowed_values=None):
    """
    A small wrapper around :func:`isinstance`.  This is mainly meant
    to be used inside of other functions to pre-validate input rater
    than using assertions.  It's better to fail early with bad input
    so more reasonable error message can be provided instead of from
    somewhere deep in cffi or Windows.

    :param str name:
        The name of the input being checked.  This is provided
        so error messages make more sense and can be attributed
        to specific input arguments.

    :param value:
        The value we're performing the type check on.

    :keyword allowed_types:
        The allowed type or types for ``value``.  This argument
        also supports a special value, ``pywincffi.core.checks.Enums.HANDLE``,
        which will check to ensure ``value`` is a handle object.

    :keyword tuple allowed_values:
        A tuple of allowed values.  When provided ``value`` must
        be in this tuple otherwise :class:`InputError` will be
        raised.

    :raises pywincffi.exceptions.InputError:
        Raised if ``value`` is not an instance of ``allowed_types``
    """
    assert isinstance(name, string_types)
    assert allowed_values is None or isinstance(allowed_values, tuple)
    ffi, _ = dist.load()

    logger.debug(
        "input_check(name=%r, value=%r, allowed_types=%r, allowed_values=%r",
        name, value, allowed_types, allowed_values)

    if allowed_types is None and isinstance(allowed_values, tuple):
        if value not in allowed_values:
            raise InputError(name,
                             value,
                             allowed_types,
                             allowed_values=allowed_values,
                             ffi=ffi)

    elif allowed_types is Enums.UTF8:
        try:
            value.encode("utf-8")
        except (ValueError, AttributeError):
            raise InputError(name, value, allowed_types, ffi=ffi)

    elif allowed_types is Enums.PYFILE:
        if not isinstance(value, FileType):
            raise InputError(name, value, "file type", ffi=ffi)

        # Make sure the file descriptor itself is valid.  If it's
        # not then we may have trouble working with the file
        # object. Certain operations, such as library.handle_from_fd
        # may also cause some bad side effects like crashing the
        # interpreter without this check.
        try:
            os.fstat(value.fileno())
        except (OSError, ValueError):
            raise InputError(name,
                             value,
                             "file type (with valid file descriptor)",
                             ffi=ffi)

    else:
        if not isinstance(value, allowed_types):
            raise InputError(name, value, allowed_types, ffi=ffi)