Skip to main content
Added details
Source Link

I integrated a number of suggestions by @ratchet freak and @Adriano Repetti:

  • I switched to automatic closing of handles, RAII-style (in a slightly simpler manner than suggested by @ratchet: I added implicit conversions to HINTERNET, one of the few legitimate use of conversion operators IMHO).
  • I added logic to handle ERROR_INSUFFICIENT_BUFFER errors returned by InternetReadFile() (see note below).
  • I added proper error handling when navigating the JSON response.
  • I added the INTERNET_FLAG_NO_UI flag to the HttpOpenRequest call.
  • I moved a couple of magic strings to constants.

Regarding the handling of ERROR_INSUFFICIENT_BUFFER errors: while they don't seem to actually occur (for this particular URL maybe?), I've followed @Adriano's advice and handled them properly using an exponentially growing buffer.

I integrated a number of suggestions by @ratchet freak and @Adriano Repetti:

  • I switched to automatic closing of handles, RAII-style.
  • I added logic to handle ERROR_INSUFFICIENT_BUFFER errors returned by InternetReadFile().
  • I added proper error handling when navigating the JSON response.
  • I added the INTERNET_FLAG_NO_UI flag to the HttpOpenRequest call.
  • I moved a couple of magic strings to constants.

I integrated a number of suggestions by @ratchet and @Adriano:

  • I switched to automatic closing of handles, RAII-style (in a slightly simpler manner than suggested by @ratchet: I added implicit conversions to HINTERNET, one of the few legitimate use of conversion operators IMHO).
  • I added logic to handle ERROR_INSUFFICIENT_BUFFER errors returned by InternetReadFile() (see note below).
  • I added proper error handling when navigating the JSON response.
  • I added the INTERNET_FLAG_NO_UI flag to the HttpOpenRequest call.
  • I moved a couple of magic strings to constants.

Regarding the handling of ERROR_INSUFFICIENT_BUFFER errors: while they don't seem to actually occur (for this particular URL maybe?), I've followed @Adriano's advice and handled them properly using an exponentially growing buffer.

Fixed buffer size that I had many very small for testing purposes
Source Link
// Code in public domain, do whatever you want with it.

#include "rapidjson/document.h"

#include <Windows.h>
#include <wininet.h>
#include <tchar.h>

#include <cstddef>
#include <ctime>
#include <exception>
#include <iostream>
#include <string>
#include <vector>

namespace json = rapidjson;

class HInternet
{
  public:
    HInternet(const HINTERNET handle)
      : m_handle(handle)
    {
    }

    ~HInternet()
    {
        if (m_handle != nullptr)
            InternetCloseHandle(m_handle);
    }

    operator HINTERNET() const
    {
        return m_handle;
    }

  private:
    const HINTERNET m_handle;
};

DWORD query_release_information(std::string& response)
{
    static const TCHAR* Host = _T("api.github.com");
    static const TCHAR* Path = _T("/repos/appleseedhq/appleseed-max/releases");

    response.clear();

    const HInternet session =
        InternetOpen(
            _T("appleseed"),
            INTERNET_OPEN_TYPE_DIRECT,
            nullptr,
            nullptr,
            0);
    if (!session)
        return GetLastError();

    const HInternet connection =
        InternetConnect(
            session,
            Host,
            INTERNET_DEFAULT_HTTPS_PORT,
            nullptr,
            nullptr,
            INTERNET_SERVICE_HTTP,
            0,
            0);
    if (!connection)
        return GetLastError();

    static const TCHAR* AcceptTypes[] = { _T("application/json"), nullptr };
    const HInternet request =
        HttpOpenRequest(
            connection,
            _T("GET"),
            Path,
            nullptr,
            nullptr,
            AcceptTypes,
            INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD,
            0);
    if (!request)
        return GetLastError();

    if (!HttpSendRequest(request, nullptr, 0, nullptr, 0))
        return GetLastError();

    std::vector<char> buffer(164096);

    while (true)
    {
        DWORD bytes_read;
        if (!InternetReadFile(request, &buffer[0], buffer.size(), &bytes_read))
        {
            const DWORD last_error = GetLastError();
            if (last_error == ERROR_INSUFFICIENT_BUFFER)
                buffer.resize(buffer.size() * 2);
            else return last_error;
        }

        if (bytes_read > 0)
            response.append(&buffer[0], bytes_read);
        else return ERROR_SUCCESS;
    }
}

struct JSONMemberNotFound : public std::exception {};

const json::Value& get_member(const json::Value& parent, const json::Value::Ch* member)
{
    if (!parent.HasMember(member))
        throw JSONMemberNotFound();
    return parent[member];
}

const json::Value& get_index(const json::Value& parent, const std::size_t index)
{
    if (!parent.IsArray() || index >= parent.Size())
        throw JSONMemberNotFound();
    return parent[index];
}

int main()
{
    std::string json;
    if (query_release_information(json) != ERROR_SUCCESS)
        return -1;

    json::Document doc;
    if (doc.Parse(json.c_str()).HasParseError())
        return -1;

    try
    {
        const auto& release = get_index(doc, 0);
        const auto& tag_name = get_member(release, "tag_name");
        const auto& published_date = get_member(release, "published_at");
        const auto& assets = get_member(release, "assets");
        const auto& asset = get_index(assets, 0);
        const auto& asset_download_url = get_member(asset, "browser_download_url");

        int year, month, day, hours, minutes, seconds;
        std::sscanf(
            published_date.GetString(),
            "%d-%d-%dT%d:%d:%d:%dZ",
            &year, &month, &day, &hours, &minutes, &seconds);

        std::tm time;
        time.tm_year = year - 1900;
        time.tm_mon = month - 1;
        time.tm_mday = day;
        time.tm_hour = hours;
        time.tm_min = minutes;
        time.tm_sec = seconds;

        const std::time_t t = std::mktime(&time);
        const std::tm* local_time = std::localtime(&t);

        char buffer[1024];
        std::strftime(buffer, sizeof(buffer), "%b %d %Y", local_time);

        std::cout << buffer << std::endl;

        return 0;
    }
    catch (const JSONMemberNotFound&)
    {
        return -1;
    }
}
// Code in public domain, do whatever you want with it.

#include "rapidjson/document.h"

#include <Windows.h>
#include <wininet.h>
#include <tchar.h>

#include <cstddef>
#include <ctime>
#include <exception>
#include <iostream>
#include <string>
#include <vector>

namespace json = rapidjson;

class HInternet
{
  public:
    HInternet(const HINTERNET handle)
      : m_handle(handle)
    {
    }

    ~HInternet()
    {
        if (m_handle != nullptr)
            InternetCloseHandle(m_handle);
    }

    operator HINTERNET() const
    {
        return m_handle;
    }

  private:
    const HINTERNET m_handle;
};

DWORD query_release_information(std::string& response)
{
    static const TCHAR* Host = _T("api.github.com");
    static const TCHAR* Path = _T("/repos/appleseedhq/appleseed-max/releases");

    response.clear();

    const HInternet session =
        InternetOpen(
            _T("appleseed"),
            INTERNET_OPEN_TYPE_DIRECT,
            nullptr,
            nullptr,
            0);
    if (!session)
        return GetLastError();

    const HInternet connection =
        InternetConnect(
            session,
            Host,
            INTERNET_DEFAULT_HTTPS_PORT,
            nullptr,
            nullptr,
            INTERNET_SERVICE_HTTP,
            0,
            0);
    if (!connection)
        return GetLastError();

    static const TCHAR* AcceptTypes[] = { _T("application/json"), nullptr };
    const HInternet request =
        HttpOpenRequest(
            connection,
            _T("GET"),
            Path,
            nullptr,
            nullptr,
            AcceptTypes,
            INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD,
            0);
    if (!request)
        return GetLastError();

    if (!HttpSendRequest(request, nullptr, 0, nullptr, 0))
        return GetLastError();

    std::vector<char> buffer(16);

    while (true)
    {
        DWORD bytes_read;
        if (!InternetReadFile(request, &buffer[0], buffer.size(), &bytes_read))
        {
            const DWORD last_error = GetLastError();
            if (last_error == ERROR_INSUFFICIENT_BUFFER)
                buffer.resize(buffer.size() * 2);
            else return last_error;
        }

        if (bytes_read > 0)
            response.append(&buffer[0], bytes_read);
        else return ERROR_SUCCESS;
    }
}

struct JSONMemberNotFound : public std::exception {};

const json::Value& get_member(const json::Value& parent, const json::Value::Ch* member)
{
    if (!parent.HasMember(member))
        throw JSONMemberNotFound();
    return parent[member];
}

const json::Value& get_index(const json::Value& parent, const std::size_t index)
{
    if (!parent.IsArray() || index >= parent.Size())
        throw JSONMemberNotFound();
    return parent[index];
}

int main()
{
    std::string json;
    if (query_release_information(json) != ERROR_SUCCESS)
        return -1;

    json::Document doc;
    if (doc.Parse(json.c_str()).HasParseError())
        return -1;

    try
    {
        const auto& release = get_index(doc, 0);
        const auto& tag_name = get_member(release, "tag_name");
        const auto& published_date = get_member(release, "published_at");
        const auto& assets = get_member(release, "assets");
        const auto& asset = get_index(assets, 0);
        const auto& asset_download_url = get_member(asset, "browser_download_url");

        int year, month, day, hours, minutes, seconds;
        std::sscanf(
            published_date.GetString(),
            "%d-%d-%dT%d:%d:%d:%dZ",
            &year, &month, &day, &hours, &minutes, &seconds);

        std::tm time;
        time.tm_year = year - 1900;
        time.tm_mon = month - 1;
        time.tm_mday = day;
        time.tm_hour = hours;
        time.tm_min = minutes;
        time.tm_sec = seconds;

        const std::time_t t = std::mktime(&time);
        const std::tm* local_time = std::localtime(&t);

        char buffer[1024];
        std::strftime(buffer, sizeof(buffer), "%b %d %Y", local_time);

        std::cout << buffer << std::endl;

        return 0;
    }
    catch (const JSONMemberNotFound&)
    {
        return -1;
    }
}
// Code in public domain, do whatever you want with it.

#include "rapidjson/document.h"

#include <Windows.h>
#include <wininet.h>
#include <tchar.h>

#include <cstddef>
#include <ctime>
#include <exception>
#include <iostream>
#include <string>
#include <vector>

namespace json = rapidjson;

class HInternet
{
  public:
    HInternet(const HINTERNET handle)
      : m_handle(handle)
    {
    }

    ~HInternet()
    {
        if (m_handle != nullptr)
            InternetCloseHandle(m_handle);
    }

    operator HINTERNET() const
    {
        return m_handle;
    }

  private:
    const HINTERNET m_handle;
};

DWORD query_release_information(std::string& response)
{
    static const TCHAR* Host = _T("api.github.com");
    static const TCHAR* Path = _T("/repos/appleseedhq/appleseed-max/releases");

    response.clear();

    const HInternet session =
        InternetOpen(
            _T("appleseed"),
            INTERNET_OPEN_TYPE_DIRECT,
            nullptr,
            nullptr,
            0);
    if (!session)
        return GetLastError();

    const HInternet connection =
        InternetConnect(
            session,
            Host,
            INTERNET_DEFAULT_HTTPS_PORT,
            nullptr,
            nullptr,
            INTERNET_SERVICE_HTTP,
            0,
            0);
    if (!connection)
        return GetLastError();

    static const TCHAR* AcceptTypes[] = { _T("application/json"), nullptr };
    const HInternet request =
        HttpOpenRequest(
            connection,
            _T("GET"),
            Path,
            nullptr,
            nullptr,
            AcceptTypes,
            INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD,
            0);
    if (!request)
        return GetLastError();

    if (!HttpSendRequest(request, nullptr, 0, nullptr, 0))
        return GetLastError();

    std::vector<char> buffer(4096);

    while (true)
    {
        DWORD bytes_read;
        if (!InternetReadFile(request, &buffer[0], buffer.size(), &bytes_read))
        {
            const DWORD last_error = GetLastError();
            if (last_error == ERROR_INSUFFICIENT_BUFFER)
                buffer.resize(buffer.size() * 2);
            else return last_error;
        }

        if (bytes_read > 0)
            response.append(&buffer[0], bytes_read);
        else return ERROR_SUCCESS;
    }
}

struct JSONMemberNotFound : public std::exception {};

const json::Value& get_member(const json::Value& parent, const json::Value::Ch* member)
{
    if (!parent.HasMember(member))
        throw JSONMemberNotFound();
    return parent[member];
}

const json::Value& get_index(const json::Value& parent, const std::size_t index)
{
    if (!parent.IsArray() || index >= parent.Size())
        throw JSONMemberNotFound();
    return parent[index];
}

int main()
{
    std::string json;
    if (query_release_information(json) != ERROR_SUCCESS)
        return -1;

    json::Document doc;
    if (doc.Parse(json.c_str()).HasParseError())
        return -1;

    try
    {
        const auto& release = get_index(doc, 0);
        const auto& tag_name = get_member(release, "tag_name");
        const auto& published_date = get_member(release, "published_at");
        const auto& assets = get_member(release, "assets");
        const auto& asset = get_index(assets, 0);
        const auto& asset_download_url = get_member(asset, "browser_download_url");

        int year, month, day, hours, minutes, seconds;
        std::sscanf(
            published_date.GetString(),
            "%d-%d-%dT%d:%d:%d:%dZ",
            &year, &month, &day, &hours, &minutes, &seconds);

        std::tm time;
        time.tm_year = year - 1900;
        time.tm_mon = month - 1;
        time.tm_mday = day;
        time.tm_hour = hours;
        time.tm_min = minutes;
        time.tm_sec = seconds;

        const std::time_t t = std::mktime(&time);
        const std::tm* local_time = std::localtime(&t);

        char buffer[1024];
        std::strftime(buffer, sizeof(buffer), "%b %d %Y", local_time);

        std::cout << buffer << std::endl;

        return 0;
    }
    catch (const JSONMemberNotFound&)
    {
        return -1;
    }
}
Source Link

I integrated a number of suggestions by @ratchet freak and @Adriano Repetti:

  • I switched to automatic closing of handles, RAII-style.
  • I added logic to handle ERROR_INSUFFICIENT_BUFFER errors returned by InternetReadFile().
  • I added proper error handling when navigating the JSON response.
  • I added the INTERNET_FLAG_NO_UI flag to the HttpOpenRequest call.
  • I moved a couple of magic strings to constants.

Regarding the use of std::put_time() and std::get_time() (which I considered when I wrote the initial code), what put me off is that, as far as I can tell, they don't actually work with strings, but with streams. I find that this introduces boilerplate without bringing much tangible benefits.

Regarding InternetGetLastResponseInfo() and the (good) suggestions to introduce a github class: I'm not willing to do too much error handling or formalization as this code is simply meant to retrieve whether a newer update of the product is available (in which case it should print a few information about the new release like the version, the publication date and maybe a download link). That's why I'm trying to keep this code as focused and straightforward as possible, until further needs arise. (But of course it should not crash the application, hence the proper error handling.)

Here is the revised code:

// Code in public domain, do whatever you want with it.

#include "rapidjson/document.h"

#include <Windows.h>
#include <wininet.h>
#include <tchar.h>

#include <cstddef>
#include <ctime>
#include <exception>
#include <iostream>
#include <string>
#include <vector>

namespace json = rapidjson;

class HInternet
{
  public:
    HInternet(const HINTERNET handle)
      : m_handle(handle)
    {
    }

    ~HInternet()
    {
        if (m_handle != nullptr)
            InternetCloseHandle(m_handle);
    }

    operator HINTERNET() const
    {
        return m_handle;
    }

  private:
    const HINTERNET m_handle;
};

DWORD query_release_information(std::string& response)
{
    static const TCHAR* Host = _T("api.github.com");
    static const TCHAR* Path = _T("/repos/appleseedhq/appleseed-max/releases");

    response.clear();

    const HInternet session =
        InternetOpen(
            _T("appleseed"),
            INTERNET_OPEN_TYPE_DIRECT,
            nullptr,
            nullptr,
            0);
    if (!session)
        return GetLastError();

    const HInternet connection =
        InternetConnect(
            session,
            Host,
            INTERNET_DEFAULT_HTTPS_PORT,
            nullptr,
            nullptr,
            INTERNET_SERVICE_HTTP,
            0,
            0);
    if (!connection)
        return GetLastError();

    static const TCHAR* AcceptTypes[] = { _T("application/json"), nullptr };
    const HInternet request =
        HttpOpenRequest(
            connection,
            _T("GET"),
            Path,
            nullptr,
            nullptr,
            AcceptTypes,
            INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD,
            0);
    if (!request)
        return GetLastError();

    if (!HttpSendRequest(request, nullptr, 0, nullptr, 0))
        return GetLastError();

    std::vector<char> buffer(16);

    while (true)
    {
        DWORD bytes_read;
        if (!InternetReadFile(request, &buffer[0], buffer.size(), &bytes_read))
        {
            const DWORD last_error = GetLastError();
            if (last_error == ERROR_INSUFFICIENT_BUFFER)
                buffer.resize(buffer.size() * 2);
            else return last_error;
        }

        if (bytes_read > 0)
            response.append(&buffer[0], bytes_read);
        else return ERROR_SUCCESS;
    }
}

struct JSONMemberNotFound : public std::exception {};

const json::Value& get_member(const json::Value& parent, const json::Value::Ch* member)
{
    if (!parent.HasMember(member))
        throw JSONMemberNotFound();
    return parent[member];
}

const json::Value& get_index(const json::Value& parent, const std::size_t index)
{
    if (!parent.IsArray() || index >= parent.Size())
        throw JSONMemberNotFound();
    return parent[index];
}

int main()
{
    std::string json;
    if (query_release_information(json) != ERROR_SUCCESS)
        return -1;

    json::Document doc;
    if (doc.Parse(json.c_str()).HasParseError())
        return -1;

    try
    {
        const auto& release = get_index(doc, 0);
        const auto& tag_name = get_member(release, "tag_name");
        const auto& published_date = get_member(release, "published_at");
        const auto& assets = get_member(release, "assets");
        const auto& asset = get_index(assets, 0);
        const auto& asset_download_url = get_member(asset, "browser_download_url");

        int year, month, day, hours, minutes, seconds;
        std::sscanf(
            published_date.GetString(),
            "%d-%d-%dT%d:%d:%d:%dZ",
            &year, &month, &day, &hours, &minutes, &seconds);

        std::tm time;
        time.tm_year = year - 1900;
        time.tm_mon = month - 1;
        time.tm_mday = day;
        time.tm_hour = hours;
        time.tm_min = minutes;
        time.tm_sec = seconds;

        const std::time_t t = std::mktime(&time);
        const std::tm* local_time = std::localtime(&t);

        char buffer[1024];
        std::strftime(buffer, sizeof(buffer), "%b %d %Y", local_time);

        std::cout << buffer << std::endl;

        return 0;
    }
    catch (const JSONMemberNotFound&)
    {
        return -1;
    }
}