CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/FileLoaders/HTTPFileLoader.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>1819#include "Common/Log.h"20#include "Common/StringUtils.h"21#include "Core/Config.h"22#include "Core/FileLoaders/HTTPFileLoader.h"2324HTTPFileLoader::HTTPFileLoader(const ::Path &filename)25: url_(filename.ToString()), progress_(&cancel_), filename_(filename) {26}2728void HTTPFileLoader::Prepare() {29std::call_once(preparedFlag_, [this](){30client_.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));3132std::vector<std::string> responseHeaders;33Url resourceURL = url_;34int redirectsLeft = 20;35while (redirectsLeft > 0) {36responseHeaders.clear();37int code = SendHEAD(resourceURL, responseHeaders);38if (code == -400) {39// Already reported the error.40return;41}4243if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) {44Disconnect();4546std::string redirectURL;47if (http::GetHeaderValue(responseHeaders, "Location", &redirectURL)) {48Url url(resourceURL);49url = url.Relative(redirectURL);5051if (url.ToString() == url_.ToString() || url.ToString() == resourceURL.ToString()) {52ERROR_LOG(Log::Loader, "HTTP request failed, hit a redirect loop");53latestError_ = "Could not connect (redirect loop)";54return;55}5657resourceURL = url;58redirectsLeft--;59continue;60}6162// No Location header?63ERROR_LOG(Log::Loader, "HTTP request failed, invalid redirect");64latestError_ = "Could not connect (invalid response)";65return;66}6768if (code != 200) {69// Leave size at 0, invalid.70ERROR_LOG(Log::Loader, "HTTP request failed, got %03d for %s", code, filename_.c_str());71latestError_ = "Could not connect (invalid response)";72Disconnect();73return;74}7576// We got a good, non-redirect response.77redirectsLeft = 0;78url_ = resourceURL;79}8081// TODO: Expire cache via ETag, etc.82bool acceptsRange = false;83for (std::string header : responseHeaders) {84if (startsWithNoCase(header, "Content-Length:")) {85size_t size_pos = header.find_first_of(' ');86if (size_pos != header.npos) {87size_pos = header.find_first_not_of(' ', size_pos);88}89if (size_pos != header.npos) {90filesize_ = atoll(&header[size_pos]);91}92}93if (startsWithNoCase(header, "Accept-Ranges:")) {94std::string lowerHeader = header;95std::transform(lowerHeader.begin(), lowerHeader.end(), lowerHeader.begin(), tolower);96// TODO: Delimited.97if (lowerHeader.find("bytes") != lowerHeader.npos) {98acceptsRange = true;99}100}101}102103// TODO: Keepalive instead.104Disconnect();105106if (!acceptsRange) {107WARN_LOG(Log::Loader, "HTTP server did not advertise support for range requests.");108}109if (filesize_ == 0) {110ERROR_LOG(Log::Loader, "Could not determine file size for %s", filename_.c_str());111}112113// If we didn't end up with a filesize_ (e.g. chunked response), give up. File invalid.114});115}116117int HTTPFileLoader::SendHEAD(const Url &url, std::vector<std::string> &responseHeaders) {118if (!url.Valid()) {119ERROR_LOG(Log::Loader, "HTTP request failed, invalid URL: '%s'", url.ToString().c_str());120latestError_ = "Invalid URL";121return -400;122}123124if (!client_.Resolve(url.Host().c_str(), url.Port())) {125ERROR_LOG(Log::Loader, "HTTP request failed, unable to resolve: |%s| port %d", url.Host().c_str(), url.Port());126latestError_ = "Could not connect (name not resolved)";127return -400;128}129130double timeout = 20.0;131132client_.SetDataTimeout(timeout);133Connect(10.0);134if (!connected_) {135ERROR_LOG(Log::Loader, "HTTP request failed, failed to connect: %s port %d (resource: '%s')", url.Host().c_str(), url.Port(), url.Resource().c_str());136latestError_ = "Could not connect (refused to connect)";137return -400;138}139140http::RequestParams req(url.Resource(), "*/*");141int err = client_.SendRequest("HEAD", req, nullptr, &progress_);142if (err < 0) {143ERROR_LOG(Log::Loader, "HTTP request failed, failed to send request: %s port %d", url.Host().c_str(), url.Port());144latestError_ = "Could not connect (could not request data)";145Disconnect();146return -400;147}148149net::Buffer readbuf;150return client_.ReadResponseHeaders(&readbuf, responseHeaders, &progress_);151}152153HTTPFileLoader::~HTTPFileLoader() {154Disconnect();155}156157bool HTTPFileLoader::Exists() {158Prepare();159return url_.Valid() && filesize_ > 0;160}161162bool HTTPFileLoader::ExistsFast() {163return url_.Valid();164}165166bool HTTPFileLoader::IsDirectory() {167// Only files.168return false;169}170171s64 HTTPFileLoader::FileSize() {172Prepare();173return filesize_;174}175176Path HTTPFileLoader::GetPath() const {177return filename_;178}179180size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {181Prepare();182std::lock_guard<std::mutex> guard(readAtMutex_);183184s64 absoluteEnd = std::min(absolutePos + (s64)bytes, filesize_);185if (absolutePos >= filesize_ || bytes == 0) {186// Read outside of the file or no read at all, just fail immediately.187return 0;188}189190Connect(10.0);191if (!connected_) {192return 0;193}194195char requestHeaders[4096];196// Note that the Range header is *inclusive*.197snprintf(requestHeaders, sizeof(requestHeaders),198"Range: bytes=%lld-%lld\r\n", absolutePos, absoluteEnd - 1);199200http::RequestParams req(url_.Resource(), "*/*");201int err = client_.SendRequest("GET", req, requestHeaders, &progress_);202if (err < 0) {203latestError_ = "Invalid response reading data";204Disconnect();205return 0;206}207208net::Buffer readbuf;209std::vector<std::string> responseHeaders;210int code = client_.ReadResponseHeaders(&readbuf, responseHeaders, &progress_);211if (code != 206) {212ERROR_LOG(Log::Loader, "HTTP server did not respond with range, received code=%03d", code);213latestError_ = "Invalid response reading data";214Disconnect();215return 0;216}217218// TODO: Expire cache via ETag, etc.219// We don't support multipart/byteranges responses.220bool supportedResponse = false;221for (std::string header : responseHeaders) {222if (startsWithNoCase(header, "Content-Range:")) {223// TODO: More correctness. Whitespace can be missing or different.224s64 first = -1, last = -1, total = -1;225std::string lowerHeader = header;226std::transform(lowerHeader.begin(), lowerHeader.end(), lowerHeader.begin(), tolower);227if (sscanf(lowerHeader.c_str(), "content-range: bytes %lld-%lld/%lld", &first, &last, &total) >= 2) {228if (first == absolutePos && last == absoluteEnd - 1) {229supportedResponse = true;230} else {231ERROR_LOG(Log::Loader, "Unexpected HTTP range: got %lld-%lld, wanted %lld-%lld.", first, last, absolutePos, absoluteEnd - 1);232}233} else {234ERROR_LOG(Log::Loader, "Unexpected HTTP range response: %s", header.c_str());235}236}237}238239// TODO: Would be nice to read directly.240net::Buffer output;241int res = client_.ReadResponseEntity(&readbuf, responseHeaders, &output, &progress_);242if (res != 0) {243ERROR_LOG(Log::Loader, "Unable to read HTTP response entity: %d", res);244// Let's take anything we got anyway. Not worse than returning nothing?245}246247// TODO: Keepalive instead.248Disconnect();249250if (!supportedResponse) {251ERROR_LOG(Log::Loader, "HTTP server did not respond with the range we wanted.");252latestError_ = "Invalid response reading data";253return 0;254}255256size_t readBytes = output.size();257output.Take(readBytes, (char *)data);258filepos_ = absolutePos + readBytes;259return readBytes;260}261262void HTTPFileLoader::Connect(double timeout) {263if (!connected_) {264cancel_ = false;265connected_ = client_.Connect(3, timeout, &cancel_);266}267}268269270