]>
Commit | Line | Data |
---|---|---|
4b5ea8a6 | 1 | /* |
4ac4a490 | 2 | * Copyright (C) 1996-2017 The Squid Software Foundation and contributors |
4b5ea8a6 CT |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
55369ae6 AR |
9 | #include "squid.h" |
10 | #include "client_side.h" | |
55369ae6 | 11 | #include "client_side_reply.h" |
3945c91d | 12 | #include "client_side_request.h" |
cda7024f | 13 | #include "ClientRequestContext.h" |
55369ae6 | 14 | #include "Downloader.h" |
e6834b06 | 15 | #include "fatal.h" |
55369ae6 | 16 | #include "http/one/RequestParser.h" |
54fb1cbf | 17 | #include "http/Stream.h" |
55369ae6 | 18 | |
4b5ea8a6 CT |
19 | CBDATA_CLASS_INIT(Downloader); |
20 | ||
a175ef71 CT |
21 | /// Used to hold and pass the required info and buffers to the |
22 | /// clientStream callbacks | |
23 | class DownloaderContext: public RefCountable | |
24 | { | |
4b5ea8a6 | 25 | MEMPROXY_CLASS(DownloaderContext); |
a175ef71 CT |
26 | |
27 | public: | |
28 | typedef RefCount<DownloaderContext> Pointer; | |
29 | ||
4b5ea8a6 | 30 | DownloaderContext(Downloader *dl, ClientHttpRequest *h); |
a175ef71 CT |
31 | ~DownloaderContext(); |
32 | void finished(); | |
4b5ea8a6 CT |
33 | |
34 | CbcPointer<Downloader> downloader; | |
a175ef71 CT |
35 | ClientHttpRequest *http; |
36 | char requestBuffer[HTTP_REQBUF_SZ]; | |
37 | }; | |
38 | ||
4b5ea8a6 CT |
39 | DownloaderContext::DownloaderContext(Downloader *dl, ClientHttpRequest *h): |
40 | downloader(dl), | |
41 | http(h) | |
42 | { | |
43 | debugs(33, 6, "DownloaderContext constructed, this=" << (void*)this); | |
44 | } | |
55369ae6 | 45 | |
cda7024f CT |
46 | DownloaderContext::~DownloaderContext() |
47 | { | |
4b5ea8a6 | 48 | debugs(33, 6, "DownloaderContext destructed, this=" << (void*)this); |
cda7024f CT |
49 | if (http) |
50 | finished(); | |
51 | } | |
52 | ||
53 | void | |
54 | DownloaderContext::finished() | |
55 | { | |
cda7024f | 56 | delete http; |
4b5ea8a6 | 57 | http = nullptr; |
cda7024f CT |
58 | } |
59 | ||
4cab96c5 CT |
60 | void |
61 | Downloader::CbDialer::print(std::ostream &os) const | |
62 | { | |
63 | os << " Http Status:" << status << Raw("body data", object.rawContent(), 64).hex(); | |
cda7024f CT |
64 | } |
65 | ||
66 | Downloader::Downloader(SBuf &url, AsyncCall::Pointer &aCallback, unsigned int level): | |
55369ae6 | 67 | AsyncJob("Downloader"), |
55369ae6 | 68 | url_(url), |
4b5ea8a6 | 69 | callback_(aCallback), |
4e526b93 | 70 | level_(level) |
55369ae6 | 71 | { |
55369ae6 AR |
72 | } |
73 | ||
74 | Downloader::~Downloader() | |
75 | { | |
0860d5b6 CT |
76 | debugs(33, 6, this); |
77 | } | |
78 | ||
79 | void | |
80 | Downloader::swanSong() | |
81 | { | |
82 | debugs(33, 6, this); | |
83 | if (context_) { | |
84 | context_->finished(); | |
85 | context_ = nullptr; | |
86 | } | |
55369ae6 AR |
87 | } |
88 | ||
55369ae6 AR |
89 | bool |
90 | Downloader::doneAll() const | |
91 | { | |
4b5ea8a6 | 92 | return (!callback_ || callback_->canceled()) && AsyncJob::doneAll(); |
55369ae6 AR |
93 | } |
94 | ||
cda7024f CT |
95 | static void |
96 | downloaderRecipient(clientStreamNode * node, ClientHttpRequest * http, | |
97 | HttpReply * rep, StoreIOBuffer receivedData) | |
55369ae6 | 98 | { |
4b5ea8a6 | 99 | debugs(33, 6, MYNAME); |
3945c91d | 100 | /* Test preconditions */ |
4b5ea8a6 | 101 | assert(node); |
cda7024f CT |
102 | |
103 | /* TODO: handle this rather than asserting | |
104 | * - it should only ever happen if we cause an abort and | |
105 | * the callback chain loops back to here, so we can simply return. | |
106 | * However, that itself shouldn't happen, so it stays as an assert for now. | |
107 | */ | |
108 | assert(cbdataReferenceValid(node)); | |
4b5ea8a6 | 109 | assert(!node->node.next); |
cda7024f | 110 | DownloaderContext::Pointer context = dynamic_cast<DownloaderContext *>(node->data.getRaw()); |
4b5ea8a6 | 111 | assert(context); |
cda7024f | 112 | |
4b5ea8a6 CT |
113 | if (context->downloader.valid()) |
114 | context->downloader->handleReply(node, http, rep, receivedData); | |
55369ae6 AR |
115 | } |
116 | ||
cda7024f CT |
117 | static void |
118 | downloaderDetach(clientStreamNode * node, ClientHttpRequest * http) | |
55369ae6 | 119 | { |
4b5ea8a6 | 120 | debugs(33, 5, MYNAME); |
cda7024f | 121 | clientStreamDetach(node, http); |
55369ae6 AR |
122 | } |
123 | ||
4b5ea8a6 | 124 | /// Initializes and starts the HTTP GET request to the remote server |
cda7024f CT |
125 | bool |
126 | Downloader::buildRequest() | |
3945c91d | 127 | { |
55369ae6 AR |
128 | const HttpRequestMethod method = Http::METHOD_GET; |
129 | ||
4b5ea8a6 | 130 | char *uri = xstrdup(url_.c_str()); |
524a2eee | 131 | HttpRequest *const request = HttpRequest::CreateFromUrl(uri, method); |
55369ae6 | 132 | if (!request) { |
4b5ea8a6 CT |
133 | debugs(33, 5, "Invalid URI: " << url_); |
134 | xfree(uri); | |
cda7024f | 135 | return false; //earlyError(...) |
55369ae6 AR |
136 | } |
137 | request->http_ver = Http::ProtocolVersion(); | |
138 | request->header.putStr(Http::HdrType::HOST, request->url.host()); | |
139 | request->header.putTime(Http::HdrType::DATE, squid_curtime); | |
cda7024f CT |
140 | request->flags.internalClient = true; |
141 | request->client_addr.setNoAddr(); | |
142 | #if FOLLOW_X_FORWARDED_FOR | |
143 | request->indirect_client_addr.setNoAddr(); | |
144 | #endif /* FOLLOW_X_FORWARDED_FOR */ | |
145 | request->my_addr.setNoAddr(); /* undefined for internal requests */ | |
146 | request->my_addr.port(0); | |
147 | request->downloader = this; | |
148 | ||
4b5ea8a6 CT |
149 | debugs(11, 2, "HTTP Client Downloader " << this << "/" << id); |
150 | debugs(11, 2, "HTTP Client REQUEST:\n---------\n" << | |
151 | request->method << " " << url_ << " " << request->http_ver << "\n" << | |
152 | "\n----------"); | |
153 | ||
154 | ClientHttpRequest *const http = new ClientHttpRequest(nullptr); | |
55369ae6 AR |
155 | http->request = request; |
156 | HTTPMSGLOCK(http->request); | |
157 | http->req_sz = 0; | |
158 | http->uri = uri; | |
4f24bf30 | 159 | setLogUri (http, urlCanonicalClean(request)); |
55369ae6 | 160 | |
cda7024f | 161 | context_ = new DownloaderContext(this, http); |
55369ae6 | 162 | StoreIOBuffer tempBuffer; |
cda7024f | 163 | tempBuffer.data = context_->requestBuffer; |
55369ae6 AR |
164 | tempBuffer.length = HTTP_REQBUF_SZ; |
165 | ||
166 | ClientStreamData newServer = new clientReplyContext(http); | |
cda7024f | 167 | ClientStreamData newClient = context_.getRaw(); |
55369ae6 | 168 | clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach, |
cda7024f CT |
169 | clientReplyStatus, newServer, downloaderRecipient, |
170 | downloaderDetach, newClient, tempBuffer); | |
55369ae6 | 171 | |
cda7024f CT |
172 | // Build a ClientRequestContext to start doCallouts |
173 | http->calloutContext = new ClientRequestContext(http); | |
cda7024f CT |
174 | http->doCallouts(); |
175 | return true; | |
55369ae6 AR |
176 | } |
177 | ||
178 | void | |
cda7024f | 179 | Downloader::start() |
55369ae6 | 180 | { |
4b5ea8a6 CT |
181 | if (!buildRequest()) |
182 | callBack(Http::scInternalServerError); | |
55369ae6 AR |
183 | } |
184 | ||
185 | void | |
cda7024f | 186 | Downloader::handleReply(clientStreamNode * node, ClientHttpRequest *http, HttpReply *reply, StoreIOBuffer receivedData) |
55369ae6 | 187 | { |
cda7024f | 188 | DownloaderContext::Pointer callerContext = dynamic_cast<DownloaderContext *>(node->data.getRaw()); |
4b5ea8a6 | 189 | // TODO: remove the following check: |
cda7024f CT |
190 | assert(callerContext == context_); |
191 | ||
4b5ea8a6 CT |
192 | debugs(33, 4, "Received " << receivedData.length << |
193 | " object data, offset: " << receivedData.offset << | |
194 | " error flag:" << receivedData.flags.error); | |
55369ae6 | 195 | |
4b5ea8a6 CT |
196 | const bool failed = receivedData.flags.error; |
197 | if (failed) { | |
198 | callBack(Http::scInternalServerError); | |
55369ae6 AR |
199 | return; |
200 | } | |
201 | ||
4b5ea8a6 CT |
202 | const int64_t existingContent = reply ? reply->content_length : 0; |
203 | const size_t maxSize = MaxObjectSize > SBuf::maxSize ? SBuf::maxSize : MaxObjectSize; | |
204 | const bool tooLarge = (existingContent > -1 && existingContent > static_cast<int64_t>(maxSize)) || | |
205 | (maxSize < object_.length()) || | |
206 | ((maxSize - object_.length()) < receivedData.length); | |
55369ae6 | 207 | |
4b5ea8a6 CT |
208 | if (tooLarge) { |
209 | callBack(Http::scInternalServerError); | |
210 | return; | |
55369ae6 AR |
211 | } |
212 | ||
c31381d0 CT |
213 | object_.append(receivedData.data, receivedData.length); |
214 | http->out.size += receivedData.length; | |
215 | http->out.offset += receivedData.length; | |
55369ae6 | 216 | |
4b5ea8a6 | 217 | switch (clientStreamStatus(node, http)) { |
cda7024f | 218 | case STREAM_NONE: { |
4b5ea8a6 | 219 | debugs(33, 3, "Get more data"); |
cda7024f CT |
220 | StoreIOBuffer tempBuffer; |
221 | tempBuffer.offset = http->out.offset; | |
222 | tempBuffer.data = context_->requestBuffer; | |
223 | tempBuffer.length = HTTP_REQBUF_SZ; | |
4b5ea8a6 | 224 | clientStreamRead(node, http, tempBuffer); |
cda7024f | 225 | } |
3945c91d | 226 | break; |
55369ae6 | 227 | case STREAM_COMPLETE: |
4b5ea8a6 CT |
228 | debugs(33, 3, "Object data transfer successfully complete"); |
229 | callBack(Http::scOkay); | |
55369ae6 AR |
230 | break; |
231 | case STREAM_UNPLANNED_COMPLETE: | |
4b5ea8a6 CT |
232 | debugs(33, 3, "Object data transfer failed: STREAM_UNPLANNED_COMPLETE"); |
233 | callBack(Http::scInternalServerError); | |
55369ae6 AR |
234 | break; |
235 | case STREAM_FAILED: | |
4b5ea8a6 CT |
236 | debugs(33, 3, "Object data transfer failed: STREAM_FAILED"); |
237 | callBack(Http::scInternalServerError); | |
55369ae6 AR |
238 | break; |
239 | default: | |
240 | fatal("unreachable code"); | |
241 | } | |
242 | } | |
243 | ||
244 | void | |
245 | Downloader::downloadFinished() | |
246 | { | |
168d2b30 CT |
247 | debugs(33, 7, this); |
248 | Must(done()); | |
55369ae6 AR |
249 | } |
250 | ||
4b5ea8a6 CT |
251 | /// Schedules for execution the "callback" with parameters the status |
252 | /// and object. | |
55369ae6 | 253 | void |
4b5ea8a6 | 254 | Downloader::callBack(Http::StatusCode const statusCode) |
55369ae6 | 255 | { |
3945c91d SM |
256 | CbDialer *dialer = dynamic_cast<CbDialer*>(callback_->getDialer()); |
257 | Must(dialer); | |
258 | dialer->status = statusCode; | |
259 | if (statusCode == Http::scOkay) | |
260 | dialer->object = object_; | |
261 | ScheduleCallHere(callback_); | |
262 | callback_ = nullptr; | |
263 | ||
0860d5b6 CT |
264 | // We cannot deleteThis() because we may be called synchronously from |
265 | // doCallouts() via handleReply() (XXX), and doCallouts() may crash if we | |
266 | // disappear. Instead, schedule an async call now so that later, when the | |
267 | // call firing code discovers a done() job, it deletes us. | |
3945c91d | 268 | CallJobHere(33, 7, CbcPointer<Downloader>(this), Downloader, downloadFinished); |
55369ae6 AR |
269 | } |
270 |