]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/Downloader.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / Downloader.cc
index 045d1288bf577b119564f4f51675a9100f30a352..ee5433b6581a5a5647763429807e8559b07900e7 100644 (file)
@@ -1,19 +1,51 @@
+/*
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
 #include "squid.h"
 #include "client_side.h"
-#include "client_side_request.h"
 #include "client_side_reply.h"
+#include "client_side_request.h"
 #include "ClientRequestContext.h"
 #include "Downloader.h"
+#include "fatal.h"
 #include "http/one/RequestParser.h"
 #include "http/Stream.h"
 
-CBDATA_CLASS_INIT(DownloaderContext);
 CBDATA_CLASS_INIT(Downloader);
 
+/// Used to hold and pass the required info and buffers to the
+/// clientStream callbacks
+class DownloaderContext: public RefCountable
+{
+    MEMPROXY_CLASS(DownloaderContext);
+
+public:
+    typedef RefCount<DownloaderContext> Pointer;
+
+    DownloaderContext(Downloader *dl, ClientHttpRequest *h);
+    ~DownloaderContext();
+    void finished();
+
+    CbcPointer<Downloader> downloader;
+    ClientHttpRequest *http;
+    char requestBuffer[HTTP_REQBUF_SZ];
+};
+
+DownloaderContext::DownloaderContext(Downloader *dl, ClientHttpRequest *h):
+    downloader(dl),
+    http(h)
+{
+    debugs(33, 6, "DownloaderContext constructed, this=" << (void*)this);
+}
+
 DownloaderContext::~DownloaderContext()
 {
-    debugs(33, 5, HERE);
-    cbdataReference(downloader);
+    debugs(33, 6, "DownloaderContext destructed, this=" << (void*)this);
     if (http)
         finished();
 }
@@ -21,38 +53,53 @@ DownloaderContext::~DownloaderContext()
 void
 DownloaderContext::finished()
 {
-    cbdataReference(http);
     delete http;
-    http = NULL;
+    http = nullptr;
+}
+
+void
+Downloader::CbDialer::print(std::ostream &os) const
+{
+    os << " Http Status:" << status << Raw("body data", object.rawContent(), 64).hex();
 }
 
-Downloader::Downloader(SBuf &url, AsyncCall::Pointer &aCallback, unsigned int level):
+Downloader::Downloader(SBuf &url, AsyncCall::Pointer &aCallback, const XactionInitiator initiator, unsigned int level):
     AsyncJob("Downloader"),
     url_(url),
-    callback(aCallback),
-    status(Http::scNone),
-    level_(level)
+    callback_(aCallback),
+    level_(level),
+    initiator_(initiator)
 {
 }
 
 Downloader::~Downloader()
 {
-    debugs(33 , 2, HERE);
+    debugs(33, 6, this);
+}
+
+void
+Downloader::swanSong()
+{
+    debugs(33, 6, this);
+    if (context_) {
+        context_->finished();
+        context_ = nullptr;
+    }
 }
 
 bool
 Downloader::doneAll() const
 {
-    return (!callback || callback->canceled()) && AsyncJob::doneAll();
+    return (!callback_ || callback_->canceled()) && AsyncJob::doneAll();
 }
 
 static void
 downloaderRecipient(clientStreamNode * node, ClientHttpRequest * http,
                     HttpReply * rep, StoreIOBuffer receivedData)
 {
-    debugs(33, 6, HERE);
-     /* Test preconditions */
-    assert(node != NULL);
+    debugs(33, 6, MYNAME);
+    /* Test preconditions */
+    assert(node);
 
     /* TODO: handle this rather than asserting
      * - it should only ever happen if we cause an abort and
@@ -60,39 +107,36 @@ downloaderRecipient(clientStreamNode * node, ClientHttpRequest * http,
      * However, that itself shouldn't happen, so it stays as an assert for now.
      */
     assert(cbdataReferenceValid(node));
-    assert(node->node.next == NULL);
+    assert(!node->node.next);
     DownloaderContext::Pointer context = dynamic_cast<DownloaderContext *>(node->data.getRaw());
-    assert(context != NULL);
+    assert(context);
 
-    if (!cbdataReferenceValid(context->downloader))
-        return;
-
-    context->downloader->handleReply(node, http, rep, receivedData);
+    if (context->downloader.valid())
+        context->downloader->handleReply(node, http, rep, receivedData);
 }
 
 static void
 downloaderDetach(clientStreamNode * node, ClientHttpRequest * http)
 {
-    debugs(33, 5, HERE);
+    debugs(33, 5, MYNAME);
     clientStreamDetach(node, http);
 }
 
+/// Initializes and starts the HTTP GET request to the remote server
 bool
 Downloader::buildRequest()
-{ 
+{
     const HttpRequestMethod method = Http::METHOD_GET;
 
-    char *uri = strdup(url_.c_str());
-    HttpRequest *const request = HttpRequest::CreateFromUrl(uri, method);
+    const MasterXaction::Pointer mx = new MasterXaction(initiator_);
+    auto * const request = HttpRequest::FromUrl(url_, mx, method);
     if (!request) {
-        debugs(33, 5, "Invalid FTP URL: " << uri);
-        safe_free(uri);
+        debugs(33, 5, "Invalid URI: " << url_);
         return false; //earlyError(...)
     }
     request->http_ver = Http::ProtocolVersion();
     request->header.putStr(Http::HdrType::HOST, request->url.host());
     request->header.putTime(Http::HdrType::DATE, squid_curtime);
-    request->flags.internalClient = true;
     request->client_addr.setNoAddr();
 #if FOLLOW_X_FORWARDED_FOR
     request->indirect_client_addr.setNoAddr();
@@ -101,11 +145,16 @@ Downloader::buildRequest()
     request->my_addr.port(0);
     request->downloader = this;
 
-    ClientHttpRequest *const http = new ClientHttpRequest(NULL);
-    http->request = request;
-    HTTPMSGLOCK(http->request);
+    debugs(11, 2, "HTTP Client Downloader " << this << "/" << id);
+    debugs(11, 2, "HTTP Client REQUEST:\n---------\n" <<
+           request->method << " " << url_ << " " << request->http_ver << "\n" <<
+           "\n----------");
+
+    ClientHttpRequest *const http = new ClientHttpRequest(nullptr);
+    http->initRequest(request);
     http->req_sz = 0;
-    http->uri = uri;
+    // XXX: performance regression. c_str() reallocates
+    http->uri = xstrdup(url_.c_str());
 
     context_ = new DownloaderContext(this, http);
     StoreIOBuffer tempBuffer;
@@ -120,14 +169,6 @@ Downloader::buildRequest()
 
     // Build a ClientRequestContext to start doCallouts
     http->calloutContext = new ClientRequestContext(http);
-
-    // Do not check for redirect, tos,nfmark and sslBump
-    http->calloutContext->redirect_done = true;
-    http->calloutContext->tosToClientDone = true;
-    http->calloutContext->nfmarkToClientDone = true;
-    http->calloutContext->sslBumpCheckDone = true;
-    http->al->ssl.bumpMode = Ssl::bumpEnd; // SslBump does not apply; log -
-
     http->doCallouts();
     return true;
 }
@@ -135,63 +176,63 @@ Downloader::buildRequest()
 void
 Downloader::start()
 {
-    if (!buildRequest()) {
-        status = Http::scInternalServerError;
-        callBack();
-    }
+    if (!buildRequest())
+        callBack(Http::scInternalServerError);
 }
 
 void
 Downloader::handleReply(clientStreamNode * node, ClientHttpRequest *http, HttpReply *reply, StoreIOBuffer receivedData)
 {
-    // TODO: remove the following check:
     DownloaderContext::Pointer callerContext = dynamic_cast<DownloaderContext *>(node->data.getRaw());
+    // TODO: remove the following check:
     assert(callerContext == context_);
 
-    bool existingContent = reply ? reply->content_length : 0;
-    bool exceedSize = (existingContent > -1 && (size_t)existingContent > MaxObjectSize) || 
-        ((object.length() + receivedData.length) > MaxObjectSize);
+    debugs(33, 4, "Received " << receivedData.length <<
+           " object data, offset: " << receivedData.offset <<
+           " error flag:" << receivedData.flags.error);
 
-    if (exceedSize) {
-        status = Http::scInternalServerError;
-        callBack();
+    const bool failed = receivedData.flags.error;
+    if (failed) {
+        callBack(Http::scInternalServerError);
         return;
     }
 
-    debugs(33, 4, "Received " << receivedData.length <<
-           " object data, offset: " << receivedData.offset <<
-           " error flag:" << receivedData.flags.error);
+    const int64_t existingContent = reply ? reply->content_length : 0;
+    const size_t maxSize = MaxObjectSize > SBuf::maxSize ? SBuf::maxSize : MaxObjectSize;
+    const bool tooLarge = (existingContent > -1 && existingContent > static_cast<int64_t>(maxSize)) ||
+                          (maxSize < object_.length()) ||
+                          ((maxSize - object_.length()) < receivedData.length);
 
-    if (receivedData.length > 0) {
-        object.append(receivedData.data, receivedData.length);
-        http->out.size += receivedData.length;
-        http->out.offset += receivedData.length;
+    if (tooLarge) {
+        callBack(Http::scInternalServerError);
+        return;
     }
 
-    switch (clientStreamStatus (node, http)) {
+    object_.append(receivedData.data, receivedData.length);
+    http->out.size += receivedData.length;
+    http->out.offset += receivedData.length;
+
+    switch (clientStreamStatus(node, http)) {
     case STREAM_NONE: {
-        debugs(33, 3, HERE << "Get more data");
+        debugs(33, 3, "Get more data");
         StoreIOBuffer tempBuffer;
         tempBuffer.offset = http->out.offset;
         tempBuffer.data = context_->requestBuffer;
         tempBuffer.length = HTTP_REQBUF_SZ;
-        clientStreamRead (node, http, tempBuffer);
+        clientStreamRead(node, http, tempBuffer);
     }
-        break;
+    break;
     case STREAM_COMPLETE:
-        debugs(33, 3, HERE << "Object data transfer successfully complete");
-        status = Http::scOkay;
-        callBack();
+        debugs(33, 3, "Object data transfer successfully complete");
+        callBack(Http::scOkay);
         break;
     case STREAM_UNPLANNED_COMPLETE:
-        debugs(33, 3, HERE << "Object data transfer failed: STREAM_UNPLANNED_COMPLETE");
-        status = Http::scInternalServerError;
-        callBack();
+        debugs(33, 3, "Object data transfer failed: STREAM_UNPLANNED_COMPLETE");
+        callBack(Http::scInternalServerError);
         break;
     case STREAM_FAILED:
-        debugs(33, 3, HERE << "Object data transfer failed: STREAM_FAILED");
-        status = Http::scInternalServerError;
-        callBack();
+        debugs(33, 3, "Object data transfer failed: STREAM_FAILED");
+        callBack(Http::scInternalServerError);
         break;
     default:
         fatal("unreachable code");
@@ -202,29 +243,26 @@ void
 Downloader::downloadFinished()
 {
     debugs(33, 7, this);
-    context_->finished();
-    context_ = NULL;
     Must(done());
-    // Not really needed. Squid will delete this object because "doneAll" is true.
-    //deleteThis("completed");
 }
 
+/// Schedules for execution the "callback" with parameters the status
+/// and object.
 void
-Downloader::callBack()
-{
-     CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer());
-     Must(dialer);
-     dialer->status = status;
-     if (status == Http::scOkay)
-         dialer->object = object;
-     ScheduleCallHere(callback);
-     callback = nullptr;
-     // Calling deleteThis method here to finish Downloader
-     // may result to squid crash.
-     // This method called by handleReply method which maybe called
-     // by ClientHttpRequest::doCallouts. The doCallouts after this object
-     // deleted, may operate on non valid objects.
-     // Schedule a fake call here just to force squid to delete this object.
-     CallJobHere(33, 7, CbcPointer<Downloader>(this), Downloader, downloadFinished);
+Downloader::callBack(Http::StatusCode const statusCode)
+{
+    CbDialer *dialer = dynamic_cast<CbDialer*>(callback_->getDialer());
+    Must(dialer);
+    dialer->status = statusCode;
+    if (statusCode == Http::scOkay)
+        dialer->object = object_;
+    ScheduleCallHere(callback_);
+    callback_ = nullptr;
+
+    // We cannot deleteThis() because we may be called synchronously from
+    // doCallouts() via handleReply() (XXX), and doCallouts() may crash if we
+    // disappear. Instead, schedule an async call now so that later, when the
+    // call firing code discovers a done() job, it deletes us.
+    CallJobHere(33, 7, CbcPointer<Downloader>(this), Downloader, downloadFinished);
 }