// #include "http.h" // #include // #include "../../../include/debug.h" // #include "../../../include/global_defines.h" // /******************************************************** // * File impls // ********************************************************/ // MeatHttpClient* HttpFile::fromHeader() { // if(client == nullptr) { // //Debug_printv("Client was not present, creating"); // client = new MeatHttpClient(); // // let's just get the headers so we have some info // //Debug_printv("Client requesting head"); // //Debug_printv("before head url[%s]", url.c_str()); // client->HEAD(url); // //Debug_printv("after head url[%s]", client->url.c_str()); // resetURL(client->url); // } // return client; // } // bool HttpFile::isDirectory() { // if(fromHeader()->m_isDirectory) // return true; // if(fromHeader()->m_isWebDAV) { // // try webdav PROPFIND to get a listing // return true; // } // else // // otherwise return false // return false; // } // MStream* HttpFile::getSourceStream(std::ios_base::openmode mode) { // // has to return OPENED stream // //Debug_printv("Input stream requested: [%s]", url.c_str()); // // headers have to be supplied here, they might be set on this channel using // // i.e. CBM DOS commands like // // H+:Accept: */* // // H+:Accept-Encoding: gzip, deflate // // here you add them all to a map, like this: // std::map headers; // // headers["Accept"] = "*/*"; // // headers["Accept-Encoding"] = "gzip, deflate"; // // etc. // MStream* istream = new HttpIStream(url, mode, headers); // istream->open(); // return istream; // } // MStream* HttpFile::getDecodedStream(std::shared_ptr is) { // return is.get(); // DUMMY return value - we've overriden istreamfunction, so this one won't be used // } // time_t HttpFile::getLastWrite() { // if(fromHeader()->m_isWebDAV) { // return 0; // } // else // // take from webdav PROPFIND or fallback to Last-Modified // return 0; // } // time_t HttpFile::getCreationTime() { // if(fromHeader()->m_isWebDAV) { // return 0; // } // else // // take from webdav PROPFIND or fallback to Last-Modified // return 0; // } // bool HttpFile::exists() { // return fromHeader()->_exists; // } // uint32_t HttpFile::size() { // if(fromHeader()->m_isWebDAV) { // // take from webdav PROPFIND // return 0; // } // else // // fallback to what we had from the header // return fromHeader()->_size; // } // bool HttpFile::remove() { // if(fromHeader()->m_isWebDAV) { // // PROPPATCH allows deletion // return false; // } // return false; // } // bool HttpFile::mkDir() { // if(fromHeader()->m_isWebDAV) { // // MKCOL creates dir // return false; // } // return false; // } // bool HttpFile::rewindDirectory() { // if(fromHeader()->m_isWebDAV) { // // we can try if this is webdav, then // // PROPFIND allows listing dir // return false; // } // return false; // }; // MFile* HttpFile::getNextFileInDir() { // Debug_printv(""); // if(fromHeader()->m_isWebDAV) { // // we can try if this is webdav, then // // PROPFIND allows listing dir // return nullptr; // } // return nullptr; // }; // bool HttpFile::isText() { // return fromHeader()->isText; // } // /******************************************************** // * Istream impls // ********************************************************/ // bool HttpIStream::open() { // bool r = false; // m_http.setHeaders(headers); // if(mode == (std::ios_base::out | std::ios_base::app)) // r = m_http.PUT(url); // else if(mode == std::ios_base::out) // r = m_http.POST(url); // else // r = m_http.GET(url); // if ( r ) { // _size = m_http._size; // } // return r; // } // void HttpIStream::close() { // //Debug_printv("CLOSE called explicitly on this HTTP stream!"); // m_http.close(); // } // bool HttpIStream::seek(uint32_t pos) { // if ( !m_http._is_open ) // { // Debug_printv("error"); // return false; // } // return m_http.seek(pos); // } // uint32_t HttpIStream::read(uint8_t* buf, uint32_t size) { // uint32_t bytesRead = 0; // if ( size > available() ) // size = available(); // if ( size > 0 ) // { // bytesRead = m_http.read(buf, size); // _position += bytesRead; // } // return bytesRead; // }; // uint32_t HttpIStream::write(const uint8_t *buf, uint32_t size) { // uint32_t bytesWritten = m_http.write(buf, size); // _position += bytesWritten; // return bytesWritten; // } // bool HttpIStream::isOpen() { // return m_http._is_open; // }; // // uint32_t HttpIStream::size() { // // return m_http._size; // // }; // // uint32_t HttpIStream::available() { // // return m_http.m_bytesAvailable; // // }; // // uint32_t HttpIStream::position() { // // return m_http._position; // // } // // size_t HttpIStream::error() { // // return m_http.m_error; // // } // /******************************************************** // * Meat HTTP client impls // ********************************************************/ // bool MeatHttpClient::GET(std::string dstUrl) { // Debug_printv("GET"); // return open(dstUrl, HTTP_METHOD_GET); // } // bool MeatHttpClient::POST(std::string dstUrl) { // Debug_printv("POST"); // return open(dstUrl, HTTP_METHOD_POST); // } // bool MeatHttpClient::PUT(std::string dstUrl) { // Debug_printv("PUT"); // return open(dstUrl, HTTP_METHOD_PUT); // } // bool MeatHttpClient::HEAD(std::string dstUrl) { // Debug_printv("HEAD"); // bool rc = open(dstUrl, HTTP_METHOD_HEAD); // close(); // return rc; // } // bool MeatHttpClient::processRedirectsAndOpen(int range) { // wasRedirected = false; // _size = -1; // Debug_printv("reopening url[%s] from position:%d", url.c_str(), range); // lastRC = openAndFetchHeaders(lastMethod, range); // while(lastRC == HttpStatus_MovedPermanently || lastRC == HttpStatus_Found || lastRC == 303) // { // Debug_printv("--- Page moved, doing redirect to [%s]", url.c_str()); // close(); // lastRC = openAndFetchHeaders(lastMethod, range); // wasRedirected = true; // } // if(lastRC != HttpStatus_Ok && lastRC != 301 && lastRC != 206) { // Debug_printv("opening stream failed, httpCode=%d", lastRC); // close(); // return false; // } // // TODO - set m_isWebDAV somehow // _is_open = true; // _exists = true; // _position = 0; // Debug_printv("length[%d] avail[%d] isFriendlySkipper[%d] isText[%d] httpCode[%d]", _size, available(), isFriendlySkipper, isText, lastRC); // return true; // } // bool MeatHttpClient::open(std::string dstUrl, esp_http_client_method_t meth) { // url = dstUrl; // lastMethod = meth; // //m_error = 0; // return processRedirectsAndOpen(0); // }; // void MeatHttpClient::close() { // if(m_http != nullptr) { // if ( _is_open ) { // esp_http_client_close(m_http); // } // esp_http_client_cleanup(m_http); // //Debug_printv("HTTP Close and Cleanup"); // m_http = nullptr; // } // _is_open = false; // } // void MeatHttpClient::setOnHeader(const std::function &lambda) { // onHeader = lambda; // } // bool MeatHttpClient::seek(uint32_t pos) { // if(pos==_position) // return true; // if(isFriendlySkipper) { // esp_http_client_close(m_http); // bool op = processRedirectsAndOpen(pos); // Debug_printv("SEEK in HttpIStream %s: range request RC=%d", url.c_str(), lastRC); // if(!op) // return false; // // 200 = range not supported! according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests // if(lastRC == 206){ // Debug_printv("Seek successful"); // _position = pos; // return true; // } // } // if(lastMethod == HTTP_METHOD_GET) { // Debug_printv("Server doesn't support resume, reading from start and discarding"); // // server doesn't support resume, so... // if(pos<_position || pos == 0) { // // skipping backward let's simply reopen the stream... // esp_http_client_close(m_http); // bool op = open(url, lastMethod); // if(!op) // return false; // // and read pos bytes - requires some buffer // for(int i = 0; i0) { // _position+=bytesRead; // } // return bytesRead; // } // return 0; // }; // uint32_t MeatHttpClient::write(const uint8_t* buf, uint32_t size) { // if (_is_open) { // auto bytesWritten= esp_http_client_write(m_http, (char *)buf, size ); // _position+=bytesWritten; // return bytesWritten; // } // return 0; // }; // int MeatHttpClient::openAndFetchHeaders(esp_http_client_method_t meth, int resume) { // if ( url.size() < 5) // return 0; // mstr::replaceAll(url, " ", "%20"); // esp_http_client_config_t config = { // .url = url.c_str(), // .user_agent = USER_AGENT, // .method = meth, // .timeout_ms = 10000, // .max_redirection_count = 10, // .event_handler = _http_event_handler, // .user_data = this, // .keep_alive_enable = true, // .keep_alive_idle = 10, // .keep_alive_interval = 1 // }; // //Debug_printv("HTTP Init url[%s]", url.c_str()); // m_http = esp_http_client_init(&config); // for (const auto& pair : headers) { // std::cout << pair.first << ": " << pair.second << std::endl; // esp_http_client_set_header(m_http, pair.first.c_str(), pair.second.c_str()); // } // if(resume > 0) { // char str[40]; // snprintf(str, sizeof str, "bytes=%lu-", (unsigned long)resume); // esp_http_client_set_header(m_http, "range", str); // } // //Debug_printv("--- PRE OPEN"); // esp_err_t initOk = esp_http_client_open(m_http, 0); // or open? It's not entirely clear... // if(initOk == ESP_FAIL) // return 0; // //Debug_printv("--- PRE FETCH HEADERS"); // int lengthResp = esp_http_client_fetch_headers(m_http); // if(_size == -1 && lengthResp > 0) { // // only if we aren't chunked! // _size = lengthResp; // _position = 0; // } // //Debug_printv("--- PRE GET STATUS CODE"); // return esp_http_client_get_status_code(m_http); // } // esp_err_t MeatHttpClient::_http_event_handler(esp_http_client_event_t *evt) // { // MeatHttpClient* meatClient = (MeatHttpClient*)evt->user_data; // switch(evt->event_id) { // case HTTP_EVENT_ERROR: // This event occurs when there are any errors during execution // Debug_printv("HTTP_EVENT_ERROR"); // //meatClient->m_error = 1; // break; // case HTTP_EVENT_ON_CONNECTED: // Once the HTTP has been connected to the server, no data exchange has been performed // // Debug_printv("HTTP_EVENT_ON_CONNECTED"); // break; // case HTTP_EVENT_HEADER_SENT: // After sending all the headers to the server // // Debug_printv("HTTP_EVENT_HEADER_SENT"); // break; // case HTTP_EVENT_ON_HEADER: // Occurs when receiving each header sent from the server // // Does this server support resume? // // Accept-Ranges: bytes // if (mstr::equals("Accept-Ranges", evt->header_key, false)) // { // if(meatClient != nullptr) { // meatClient->isFriendlySkipper = mstr::equals("bytes", evt->header_value,false); // //Debug_printv("* Ranges info present '%s', comparison=%d!",evt->header_value, strcmp("bytes", evt->header_value)==0); // } // } // // what can we do UTF8<->PETSCII on this stream? // else if (mstr::equals("Content-Type", evt->header_key, false)) // { // std::string asString = evt->header_value; // bool isText = mstr::isText(asString); // if(meatClient != nullptr) { // meatClient->isText = isText; // //Debug_printv("* Content info present '%s', isText=%d!, type=%s", evt->header_value, isText, asString.c_str()); // } // } // else if(mstr::equals("Last-Modified", evt->header_key, false)) // { // // Last-Modified, value=Thu, 03 Dec 1992 08:37:20 - may be used to get file date // } // else if(mstr::equals("Content-Disposition", evt->header_key, false)) // { // // Content-Disposition, value=attachment; filename*=UTF-8''GeckOS-c64.d64 // // we can set isText from real file extension, too! // std::string value = evt->header_value; // if ( mstr::contains( value, (char *)"index.prg" ) ) // { // Debug_printv("HTTP Directory Listing [%s]", meatClient->url.c_str()); // meatClient->m_isDirectory = true; // } // } // else if(mstr::equals("Content-Length", evt->header_key, false)) // { // //Debug_printv("* Content len present '%s'", evt->header_value); // meatClient->_size = std::stoi(evt->header_value); // } // else if(mstr::equals("Location", evt->header_key, false)) // { // Debug_printv("* This page redirects from '%s' to '%s'", meatClient->url.c_str(), evt->header_value); // if ( mstr::startsWith(evt->header_value, (char *)"http") ) // { // //Debug_printv("match"); // meatClient->url = evt->header_value; // } // else // { // //Debug_printv("no match"); // if ( mstr::startsWith(evt->header_value, (char *)"/") ) // { // // Absolute path redirect // PeoplesUrlParser *u = PeoplesUrlParser::parseURL( meatClient->url ); // meatClient->url = u->root() + evt->header_value; // } // else // { // // Relative path redirect // meatClient->url += evt->header_value; // } // } // Debug_printv("new url '%s'", meatClient->url.c_str()); // } // // Allow override in lambda // meatClient->onHeader(evt->header_key, evt->header_value); // break; // #if __cplusplus > 201703L // //#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 3, 0) // case HTTP_EVENT_REDIRECT: // Debug_printv("* This page redirects from '%s' to '%s'", meatClient->url.c_str(), evt->header_value); // if ( mstr::startsWith(evt->header_value, (char *)"http") ) // { // //Debug_printv("match"); // meatClient->url = evt->header_value; // } // else // { // //Debug_printv("no match"); // meatClient->url += evt->header_value; // } // break; // #endif // case HTTP_EVENT_ON_DATA: // Occurs multiple times when receiving body data from the server. MAY BE SKIPPED IF BODY IS EMPTY! // //Debug_printv("HTTP_EVENT_ON_DATA, len=%d", evt->data_len); // { // // int status = esp_http_client_get_status_code(meatClient->m_http); // // if ((status == HttpStatus_Found || status == HttpStatus_MovedPermanently || status == 303) /*&& client->_redirect_count < (client->_max_redirects - 1)*/) // // { // // //Debug_printv("HTTP_EVENT_ON_DATA: Redirect response body, ignoring"); // // } // // else { // // //Debug_printv("HTTP_EVENT_ON_DATA: Got response body"); // // } // if (esp_http_client_is_chunked_response(evt->client)) { // int len; // esp_http_client_get_chunk_length(evt->client, &len); // meatClient->_size = len; // //Debug_printv("HTTP_EVENT_ON_DATA: Got chunked response, chunklen=%d, contentlen[%d]", len, meatClient->_size); // } // } // break; // case HTTP_EVENT_ON_FINISH: // // Occurs when finish a HTTP session // // This may get called more than once if esp_http_client decides to retry in order to handle a redirect or auth response // //Debug_printv("HTTP_EVENT_ON_FINISH %u\r\n", uxTaskGetStackHighWaterMark(nullptr)); // // Keep track of how many times we "finish" reading a response from the server // //Debug_printv("HTTP_EVENT_ON_FINISH"); // break; // case HTTP_EVENT_DISCONNECTED: // The connection has been disconnected // //Debug_printv("HTTP_EVENT_DISCONNECTED"); // //meatClient->m_bytesAvailable = 0; // break; // } // return ESP_OK; // }