size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) {
    assert(userp);
    auto baton = reinterpret_cast<HTTPCURLRequest *>(userp);
    MBGL_VERIFY_THREAD(baton->tid);

    if (!baton->response) {
        baton->response = std::make_unique<Response>();
    }

    const size_t length = size * nmemb;
    size_t begin = std::string::npos;
    if ((begin = headerMatches("last-modified: ", buffer, length)) != std::string::npos) {
        // Always overwrite the modification date; We might already have a value here from the
        // Date header, but this one is more accurate.
        const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
        baton->response->modified = curl_getdate(value.c_str(), nullptr);
    } else if ((begin = headerMatches("etag: ", buffer, length)) != std::string::npos) {
        baton->response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n
    } else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) {
        const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
        baton->response->expires = parseCacheControl(value.c_str());
    } else if ((begin = headerMatches("expires: ", buffer, length)) != std::string::npos) {
        const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
        baton->response->expires = curl_getdate(value.c_str(), nullptr);
    }

    return length;
}
void HTTPAndroidRequest::onResponse(JNIEnv* env, int code, jstring /* message */, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) {
    response = std::make_unique<Response>();
    using Error = Response::Error;

    if (etag != nullptr) {
        response->etag = mbgl::android::std_string_from_jstring(env, etag);
    }

    if (modified != nullptr) {
        response->modified = util::parseTimePoint(mbgl::android::std_string_from_jstring(env, modified).c_str());
    }

    if (cacheControl != nullptr) {
        response->expires = parseCacheControl(mbgl::android::std_string_from_jstring(env, cacheControl).c_str());
    }

    if (expires != nullptr) {
        response->expires = util::parseTimePoint(mbgl::android::std_string_from_jstring(env, expires).c_str());
    }

    if (code == 200) {
        if (body != nullptr) {
            jbyte* bodyData = env->GetByteArrayElements(body, nullptr);
            response->data = std::make_shared<std::string>(reinterpret_cast<char*>(bodyData), env->GetArrayLength(body));
            env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT);
        } else {
            response->data = std::make_shared<std::string>();
        }
    } else if (code == 204 || (code == 404 && resource.kind == Resource::Kind::Tile)) {
        response->noContent = true;
    } else if (code == 304) {
        response->notModified = true;
    } else if (code == 404) {
        response->error = std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
    } else if (code >= 500 && code < 600) {
        response->error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code));
    } else {
        response->error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code));
    }

    async.send();
}
void HTTPAndroidRequest::onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body) {
    response = std::make_unique<Response>();
    using Error = Response::Error;

    response->modified = parse_date(modified.c_str());
    response->etag = etag;
    response->expires = parseCacheControl(cacheControl.c_str());
    if (!expires.empty()) {
        response->expires = parse_date(expires.c_str());
    }
    response->data = std::make_shared<std::string>(body);

    if (code == 200) {
        // Nothing to do; this is what we want
    } else if (code == 304) {
        if (existingResponse) {
            if (existingResponse->error) {
                response->error = std::make_unique<Error>(*existingResponse->error);
            }
            response->data = existingResponse->data;
            response->modified = existingResponse->modified;
            // We're not updating `expired`, it was probably set during the request.
            response->etag = existingResponse->etag;
        } else {
            // This is an unsolicited 304 response and should only happen on malfunctioning
            // HTTP servers. It likely doesn't include any data, but we don't have much options.
        }
    } else if (code == 404) {
        response->error = std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
    } else if (code >= 500 && code < 600) {
        response->error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code));
    } else {
        response->error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code));
    }

    async.send();
}