<commit_before>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2019.  All rights reserved
 */
#include "../../StroikaPreComp.h"

#include <cstdio>

#if qPlatform_POSIX
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#elif qPlatform_Windows
#include <WinSock2.h>

#include <WS2tcpip.h>
#endif

#include "../../Characters/Format.h"
#include "../../Containers/Collection.h"
#include "../../Execution/Exceptions.h"
#include "../../Execution/Finally.h"
#if qPlatform_Windows
#include "../../../Foundation/Execution/Platform/Windows/Exception.h"
#include "Platform/Windows/WinSock.h"
#endif
#include "../../Execution/Exceptions.h"
#include "SocketAddress.h"

#include "DNS.h"

using namespace Stroika::Foundation;
using namespace Stroika::Foundation::Characters;
using namespace Stroika::Foundation::Containers;
using namespace Stroika::Foundation::Execution;
using namespace Stroika::Foundation::Memory;
using namespace Stroika::Foundation::IO;
using namespace Stroika::Foundation::IO::Network;

#if qPlatform_Windows
// API should return char* but MSFT returns WIDECHARS sometimes - undo that
#undef gai_strerror
#define gai_strerror gai_strerrorA
#endif // qW

// Comment this in to turn on aggressive noisy DbgTrace in this module
//#define   USE_NOISY_TRACE_IN_THIS_MODULE_       1

namespace {
    // @todo - somewhat rough draft - not sure if we need better default_error_condition, or equivilant() overrides
    class getaddrinfo_error_category_ : public error_category { // categorize an error
    public:
        virtual const char* name () const noexcept override
        {
            return "getaddrinfo";
        }
        virtual error_condition default_error_condition (int ev) const noexcept override
        {
            switch (ev) {
#if EAI_ADDRFAMILY
                case EAI_ADDRFAMILY:
                    return std::error_condition (errc::address_family_not_supported);
#endif
#if EAI_MEMORY
                case EAI_MEMORY:
                    return error_condition (errc::not_enough_memory);
#endif
            }
            return error_condition (errc::bad_message); // no idea what to return here
        }
        virtual string message (int _Errval) const override
        {
            return ::gai_strerror (_Errval);
        }
    };
    const getaddrinfo_error_category_ sgetaddrinfo_error_category_;
}

/*
 ********************************************************************************
 ************************** Network::GetInterfaces ******************************
 ********************************************************************************
 */
DNS DNS::Default ()
{
    static DNS sDefaultDNS_;
    return sDefaultDNS_;
}

DNS::DNS ()
{
#if qPlatform_Windows
    IO::Network::Platform::Windows::WinSock::AssureStarted ();
#endif
}

DNS::HostEntry DNS::GetHostEntry (const String& hostNameOrAddress) const
{
#if USE_NOISY_TRACE_IN_THIS_MODULE_
    Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs (L"DNS::HostEntry DNS::GetHostEntry", L"hostNameOrAddress=%s", Characters::ToString (hostNameOrAddress).c_str ())};
#endif
    HostEntry result;

    addrinfo hints{};
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = AI_CANONNAME;
#if defined(AI_IDN)
    hints.ai_flags |= AI_IDN;
#endif
#if defined(AI_CANONIDN)
    hints.ai_flags |= AI_CANONIDN;
#endif
    string tmp = hostNameOrAddress.AsUTF8<string> (); // BAD - SB tstring - or??? not sure what... - I think need to map to Punycode
    if (not tmp.empty () and tmp[0] == '[' and tmp[tmp.size () - 1] == ']' and isdigit (tmp[1])) {
        // only allowed [] around numeric ip addresses
        tmp = tmp.substr (1, tmp.size () - 2);
    }
    addrinfo*               res     = nullptr;
    int                     errCode = ::getaddrinfo (tmp.c_str (), nullptr, &hints, &res);
    [[maybe_unused]] auto&& cleanup = Execution::Finally ([res]() noexcept { ::freeaddrinfo (res); });
    if (errCode != 0) {
        // @todo - I think we need to capture erron as well if errCode == EAI_SYSTEM (see http://man7.org/linux/man-pages/man3/getaddrinfo.3.html)
        Throw (SystemErrorException (errCode, sgetaddrinfo_error_category_));
        // @todo get rid of the below - OBSOLETE CODE - LGP 2019-02-25
        //Throw (Exception (Format (L"DNS-Error: %s (%d)", String::FromNarrowSDKString (::gai_strerror (errCode)).c_str (), errCode)));
    }
    AssertNotNull (res); // else would have thrown

    // @todo proplerly support http://www.ietf.org/rfc/rfc3987.txt and UTF8 etc.
    // See http://linux.die.net/man/3/getaddrinfo for info on glibc support for AI_IDN etc..
    // and how todo on windows (or do myself portably?)
    // MAYBER done OK?
    //
    //  NI_IDN -- If this flag is used, then the name found in the lookup process is converted from IDN format
    //  to the locale's encoding if necessary. ASCII-only names are not affected by the conversion, which makes
    //  this flag usable in existing programs and environments.
    //

    if (res->ai_canonname != nullptr) {
        // utf8 part a WAG
        result.fCanonicalName = String::FromUTF8 (res->ai_canonname);
    }

    for (addrinfo* i = res; i != nullptr; i = i->ai_next) {
        if (i != res and i->ai_canonname != nullptr and i->ai_canonname[0] != '\0') {
            result.fAliases += String::FromUTF8 (i->ai_canonname);
        }<commit_msg>small cleanups to DNS error handling code<commit_after>/*
 * Copyright(c) Sophist Solutions, Inc. 1990-2019.  All rights reserved
 */
#include "../../StroikaPreComp.h"

#include <cstdio>

#if qPlatform_POSIX
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#elif qPlatform_Windows
#include <WinSock2.h>

#include <WS2tcpip.h>
#endif

#include "../../Characters/Format.h"
#include "../../Containers/Collection.h"
#include "../../Execution/Exceptions.h"
#include "../../Execution/Finally.h"
#if qPlatform_Windows
#include "../../../Foundation/Execution/Platform/Windows/Exception.h"
#include "Platform/Windows/WinSock.h"
#endif
#include "../../Execution/Exceptions.h"
#include "SocketAddress.h"

#include "DNS.h"

using namespace Stroika::Foundation;
using namespace Stroika::Foundation::Characters;
using namespace Stroika::Foundation::Containers;
using namespace Stroika::Foundation::Execution;
using namespace Stroika::Foundation::Memory;
using namespace Stroika::Foundation::IO;
using namespace Stroika::Foundation::IO::Network;

#if qPlatform_Windows
// API should return char* but MSFT returns WIDECHARS sometimes - undo that
#undef gai_strerror
#define gai_strerror gai_strerrorA
#endif // qW

// Comment this in to turn on aggressive noisy DbgTrace in this module
//#define   USE_NOISY_TRACE_IN_THIS_MODULE_       1

namespace {
    // @todo - somewhat rough draft - not sure if we need better default_error_condition, or equivilant() overrides
    class getaddrinfo_error_category_ : public error_category { // categorize an error
    public:
        virtual const char* name () const noexcept override
        {
            return "DNS error"; // used to return return "getaddrinfo"; - but the name DNS is more widely recognized, and even though this could be from another source, this name is more clear
        }
        virtual error_condition default_error_condition (int ev) const noexcept override
        {
            switch (ev) {
#if EAI_ADDRFAMILY
                case EAI_ADDRFAMILY:
                    return std::error_condition (errc::address_family_not_supported); // best approximartion I can find
#endif
#if EAI_NONAME
                case EAI_NONAME:
                    return error_condition (errc::no_such_device); // best approximartion I can find
#endif
#if EAI_MEMORY
                case EAI_MEMORY:
                    return error_condition (errc::not_enough_memory);
#endif
            }
            return error_condition (errc::bad_message); // no idea what to return here
        }
        virtual string message (int _Errval) const override
        {
            return ::gai_strerror (_Errval);
        }
    };
    const getaddrinfo_error_category_ sgetaddrinfo_error_category_;
}

/*
 ********************************************************************************
 ************************** Network::GetInterfaces ******************************
 ********************************************************************************
 */
DNS DNS::Default ()
{
    static const DNS kDefaultDNS_;
    return kDefaultDNS_;
}

DNS::DNS ()
{
#if qPlatform_Windows
    IO::Network::Platform::Windows::WinSock::AssureStarted ();
#endif
}

DNS::HostEntry DNS::GetHostEntry (const String& hostNameOrAddress) const
{
#if USE_NOISY_TRACE_IN_THIS_MODULE_
    Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs (L"DNS::HostEntry DNS::GetHostEntry", L"hostNameOrAddress=%s", Characters::ToString (hostNameOrAddress).c_str ())};
#endif
    HostEntry result;

    addrinfo hints{};
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags    = AI_CANONNAME;
#if defined(AI_IDN)
    hints.ai_flags |= AI_IDN;
#endif
#if defined(AI_CANONIDN)
    hints.ai_flags |= AI_CANONIDN;
#endif
    string tmp = hostNameOrAddress.AsUTF8<string> (); // BAD - SB tstring - or??? not sure what... - I think need to map to Punycode
    if (not tmp.empty () and tmp[0] == '[' and tmp[tmp.size () - 1] == ']' and isdigit (tmp[1])) {
        // only allowed [] around numeric ip addresses
        tmp = tmp.substr (1, tmp.size () - 2);
    }
    addrinfo*               res     = nullptr;
    int                     errCode = ::getaddrinfo (tmp.c_str (), nullptr, &hints, &res);
    [[maybe_unused]] auto&& cleanup = Execution::Finally ([res]() noexcept { ::freeaddrinfo (res); });
    if (errCode != 0) {
        // @todo - I think we need to capture erron as well if errCode == EAI_SYSTEM (see http://man7.org/linux/man-pages/man3/getaddrinfo.3.html)
        Throw (SystemErrorException (errCode, sgetaddrinfo_error_category_));
    }
    AssertNotNull (res); // else would have thrown

    // @todo proplerly support http://www.ietf.org/rfc/rfc3987.txt and UTF8 etc.
    // See http://linux.die.net/man/3/getaddrinfo for info on glibc support for AI_IDN etc..
    // and how todo on windows (or do myself portably?)
    // MAYBER done OK?
    //
    //  NI_IDN -- If this flag is used, then the name found in the lookup process is converted from IDN format
    //  to the locale's encoding if necessary. ASCII-only names are not affected by the conversion, which makes
    //  this flag usable in existing programs and environments.
    //

    if (res->ai_canonname != nullptr) {
        // utf8 part a WAG
        result.fCanonicalName = String::FromUTF8 (res->ai_canonname);
    }

    for (addrinfo* i = res; i != nullptr; i = i->ai_next) {
        if (i != res and i->ai_canonname != nullptr and i->ai_canonname[0] != '\0') {
            result.fAliases += String::FromUTF8 (i->ai_canonname);
        }
{'fraction_non_alphanumeric': 0.15424468278494818, 'fraction_numerical': 0.006544264679149246, 'mean_word_length': 3.2663823187281893, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '1558333', 'n_tokens_mistral': 3443, 'n_tokens_neox': 3307, 'n_words': 915}