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;
}
int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp,
                              void *socketp) {
    auto socket = reinterpret_cast<Socket *>(socketp);
    assert(userp);
    auto context = reinterpret_cast<HTTPCURLContext *>(userp);
    MBGL_VERIFY_THREAD(context->tid);

    if (!socket && action != CURL_POLL_REMOVE) {
        socket = new Socket(context, s);
        curl_multi_assign(context->multi, s, (void *)socket);
    }

    switch (action) {
    case CURL_POLL_IN:
        socket->start(UV_READABLE, perform);
        break;
    case CURL_POLL_OUT:
        socket->start(UV_WRITABLE, perform);
        break;
    case CURL_POLL_REMOVE:
        if (socket) {
            socket->stop();
            curl_multi_assign(context->multi, s, nullptr);
        }
        break;
    default:
        throw std::runtime_error("Unhandled CURL socket action");
    }

    return 0;
}
void RunLoop::addWatch(int fd, Event event, std::function<void(int, Event)>&& cb) {
    MBGL_VERIFY_THREAD(tid);

    if (event == Event::Read || event == Event::ReadWrite) {
        auto notifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
        QObject::connect(notifier.get(), SIGNAL(activated(int)), impl.get(), SLOT(onReadEvent(int)));
        impl->readPoll[fd] = WatchPair(std::move(notifier), std::move(cb));
    }
void RunLoop::run() {
    MBGL_VERIFY_THREAD(tid);

    if (impl->type == Type::Default) {
        QCoreApplication::instance()->exec();
    } else {
        impl->loop->exec();
    }
}
void HTTPCURLContext::onTimeout(UV_TIMER_PARAMS(req)) {
    assert(req->data);
    auto context = reinterpret_cast<HTTPCURLContext *>(req->data);
    MBGL_VERIFY_THREAD(context->tid);
    int running_handles;
    CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
    if (error != CURLM_OK) {
        throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
    }
    context->checkMultiInfo();
}
int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) {
    assert(userp);
    auto context = reinterpret_cast<HTTPCURLContext *>(userp);
    MBGL_VERIFY_THREAD(context->tid);
    if (timeout_ms < 0) {
        // A timeout of 0 ms means that the timer will invoked in the next loop iteration.
        timeout_ms = 0;
    }
    uv_timer_stop(context->timeout);
    uv_timer_start(context->timeout, onTimeout, timeout_ms, 0);
    return 0;
}
// This function is called when we have new data for a request. We just append it to the string
// containing the previous data.
size_t HTTPCURLRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) {
    assert(userp);
    auto impl = reinterpret_cast<HTTPCURLRequest *>(userp);
    MBGL_VERIFY_THREAD(impl->tid);

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

    impl->response->data.append((char *)contents, size * nmemb);
    return size * nmemb;
}
    void unsubscribe(Request *request) {
        MBGL_VERIFY_THREAD(tid);

        observers.erase(request);

        if (abandoned()) {
            // There are no observers anymore. We are initiating cancelation.
            if (source) {
                // First, remove this SharedRequestBase from the source.
                source->notify(this, observers, nullptr, FileCache::Hint::No);
            }

            // Then, initiate cancelation of this request
            cancel();
        }
    }
    std::vector<Request *> removeAllInEnvironment(const Environment &env) {
        MBGL_VERIFY_THREAD(tid);

        std::vector<Request *> result;

        // Removes all Requests in the supplied environment and returns a list
        // of them.
        util::erase_if(observers, [&](Request *req) -> bool {
            if (&req->env == &env) {
                result.push_back(req);
                return true;
            } else {
                return false;
            }
        });

        return result;
    }
Example #10
0
void RunLoop::addWatch(int fd, Event event, std::function<void(int, Event)>&& callback) {
    MBGL_VERIFY_THREAD(tid);

    Watch *watch = nullptr;
    auto watchPollIter = impl->watchPoll.find(fd);

    if (watchPollIter == impl->watchPoll.end()) {
        std::unique_ptr<Watch> watchPtr = std::make_unique<Watch>();

        watch = watchPtr.get();
        impl->watchPoll[fd] = std::move(watchPtr);

        if (uv_poll_init(impl->loop, &watch->poll, fd)) {
            throw std::runtime_error("Failed to init poll on file descriptor.");
        }
    } else {
        watch = watchPollIter->second.get();
    }

    watch->poll.data = watch;
    watch->fd = fd;
    watch->eventCallback = std::move(callback);

    int pollEvent = 0;
    switch (event) {
    case Event::Read:
        pollEvent = UV_READABLE;
        break;
    case Event::Write:
        pollEvent = UV_WRITABLE;
        break;
    case Event::ReadWrite:
        pollEvent = UV_READABLE | UV_WRITABLE;
        break;
    default:
        throw std::runtime_error("Unhandled event.");
    }

    if (uv_poll_start(&watch->poll, pollEvent, &Watch::onEvent)) {
        throw std::runtime_error("Failed to start poll on file descriptor.");
    }
}
void HTTPCURLContext::perform(uv_poll_t *req, int /* status */, int events) {
    assert(req->data);
    auto socket = reinterpret_cast<Socket *>(req->data);
    auto context = socket->context;
    MBGL_VERIFY_THREAD(context->tid);

    int flags = 0;

    if (events & UV_READABLE) {
        flags |= CURL_CSELECT_IN;
    }
    if (events & UV_WRITABLE) {
        flags |= CURL_CSELECT_OUT;
    }


    int running_handles = 0;
    curl_multi_socket_action(context->multi, socket->sockfd, flags, &running_handles);
    context->checkMultiInfo();
}
void HTTPCURLContext::checkMultiInfo() {
    MBGL_VERIFY_THREAD(tid);
    CURLMsg *message = nullptr;
    int pending = 0;

    while ((message = curl_multi_info_read(multi, &pending))) {
        switch (message->msg) {
        case CURLMSG_DONE: {
            HTTPCURLRequest *baton = nullptr;
            curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton);
            assert(baton);
            baton->handleResult(message->data.result);
        } break;

        default:
            // This should never happen, because there are no other message types.
            throw std::runtime_error("CURLMsg returned unknown message type");
        }
    }
}
HTTPCURLRequest::~HTTPCURLRequest() {
    MBGL_VERIFY_THREAD(tid);

    context->removeRequest(this);

    handleError(curl_multi_remove_handle(context->multi, handle));
    context->returnHandle(handle);
    handle = nullptr;

    if (timer) {
        // Stop the backoff timer to avoid re-triggering this request.
        uv_timer_stop(timer);
        uv::close(timer);
        timer = nullptr;
    }

    if (headers) {
        curl_slist_free_all(headers);
        headers = nullptr;
    }
}
Example #14
0
void RunLoop::removeWatch(int fd) {
    MBGL_VERIFY_THREAD(tid);

    auto watchPollIter = impl->watchPoll.find(fd);
    if (watchPollIter == impl->watchPoll.end()) {
        return;
    }

    Watch* watch = watchPollIter->second.release();
    impl->watchPoll.erase(watchPollIter);

    watch->closeCallback = [watch] {
        delete watch;
    };

    if (uv_poll_stop(&watch->poll)) {
        throw std::runtime_error("Failed to stop poll on file descriptor.");
    }

    uv_close(reinterpret_cast<uv_handle_t*>(&watch->poll), &Watch::onClose);
}
DefaultFileSource::~DefaultFileSource() {
    MBGL_VERIFY_THREAD(tid);
}
void HTTPCURLRequest::handleResult(CURLcode code) {
    MBGL_VERIFY_THREAD(tid);

    if (cancelled) {
        // In this case, it doesn't make sense to even process the response even further since
        // the request was canceled anyway.
        delete this;
        return;
    }

    // Make sure a response object exists in case we haven't got any headers
    // or content.
    if (!response) {
        response = std::make_unique<Response>();
    }

    // Add human-readable error code
    if (code != CURLE_OK) {
        response->status = Response::Error;
        response->message = std::string { curl_easy_strerror(code) } + ": " + error;

        switch (code) {
        case CURLE_COULDNT_RESOLVE_PROXY:
        case CURLE_COULDNT_RESOLVE_HOST:
        case CURLE_COULDNT_CONNECT:
            return finish(ResponseStatus::ConnectionError);

        case CURLE_OPERATION_TIMEDOUT:
            return finish(ResponseStatus::TemporaryError);

        default:
            return finish(ResponseStatus::PermanentError);
        }
    } else {
        long responseCode = 0;
        curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);

        if (responseCode == 304) {
            if (existingResponse) {
                // We're going to copy over the existing response's data.
                response->status = existingResponse->status;
                response->message = existingResponse->message;
                response->modified = existingResponse->modified;
                response->etag = existingResponse->etag;
                response->data = existingResponse->data;
                return finish(ResponseStatus::NotModified);
            } 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.
                response->status = Response::Successful;
                return finish(ResponseStatus::Successful);
            }
        } else if (responseCode == 200) {
            response->status = Response::Successful;
            return finish(ResponseStatus::Successful);
        } else if (responseCode == 404) {
            response->status = Response::NotFound;
            return finish(ResponseStatus::Successful);
        } else if (responseCode >= 500 && responseCode < 600) {
            // Server errors may be temporary, so back off exponentially.
            response->status = Response::Error;
            response->message = "HTTP status code " + util::toString(responseCode);
            return finish(ResponseStatus::TemporaryError);
        } else {
            // We don't know how to handle any other errors, so declare them as permanently failing.
            response->status = Response::Error;
            response->message = "HTTP status code " + util::toString(responseCode);
            return finish(ResponseStatus::PermanentError);
        }
    }

    throw std::runtime_error("Response hasn't been handled");
}
Example #17
0
// Called in the originating thread.
void Request::cancel() {
    MBGL_VERIFY_THREAD(tid)
    assert(async);
    assert(!canceled);
    canceled = util::make_unique<Canceled>();
}
    void subscribe(Request *request) {
        MBGL_VERIFY_THREAD(tid);

        observers.insert(request);
    }
RunLoop::~RunLoop() {
    MBGL_VERIFY_THREAD(tid);

    current.set(nullptr);
}