1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree.
7  */
8 
9 #include "CurlClient.h"
10 
11 #include <iostream>
12 #include <sys/stat.h>
13 
14 #include <folly/FileUtil.h>
15 #include <folly/String.h>
16 #include <folly/io/async/SSLContext.h>
17 #include <folly/io/async/SSLOptions.h>
18 #include <folly/portability/GFlags.h>
19 #include <proxygen/lib/http/HTTPMessage.h>
20 #include <proxygen/lib/http/codec/HTTP2Codec.h>
21 #include <proxygen/lib/http/session/HTTPUpstreamSession.h>
22 
23 using namespace folly;
24 using namespace proxygen;
25 using namespace std;
26 
27 DECLARE_int32(recv_window);
28 
29 namespace CurlService {
30 
CurlClient(EventBase * evb,HTTPMethod httpMethod,const URL & url,const proxygen::URL * proxy,const HTTPHeaders & headers,const string & inputFilename,bool h2c,unsigned short httpMajor,unsigned short httpMinor)31 CurlClient::CurlClient(EventBase* evb,
32                        HTTPMethod httpMethod,
33                        const URL& url,
34                        const proxygen::URL* proxy,
35                        const HTTPHeaders& headers,
36                        const string& inputFilename,
37                        bool h2c,
38                        unsigned short httpMajor,
39                        unsigned short httpMinor)
40     : evb_(evb),
41       httpMethod_(httpMethod),
42       url_(url),
43       inputFilename_(inputFilename),
44       h2c_(h2c),
45       httpMajor_(httpMajor),
46       httpMinor_(httpMinor) {
47   if (proxy != nullptr) {
48     proxy_ = std::make_unique<URL>(proxy->getUrl());
49   }
50 
51   outputStream_ = std::make_unique<std::ostream>(std::cout.rdbuf());
52   headers.forEach([this](const string& header, const string& val) {
53     request_.getHeaders().add(header, val);
54   });
55 }
56 
saveResponseToFile(const std::string & outputFilename)57 bool CurlClient::saveResponseToFile(const std::string& outputFilename) {
58   std::streambuf* buf;
59   if (outputFilename.empty()) {
60     return false;
61   }
62   uint16_t tries = 0;
63   while (tries < std::numeric_limits<uint16_t>::max()) {
64     std::string suffix = (tries == 0) ? "" : folly::to<std::string>("_", tries);
65     auto filename = folly::to<std::string>(outputFilename, suffix);
66     struct stat statBuf;
67     if (stat(filename.c_str(), &statBuf) == -1) {
68       outputFile_ =
69           std::make_unique<ofstream>(filename, ios::out | ios::binary);
70       if (*outputFile_ && outputFile_->good()) {
71         buf = outputFile_->rdbuf();
72         outputStream_ = std::make_unique<std::ostream>(buf);
73         return true;
74       }
75     }
76     tries++;
77   }
78   return false;
79 }
80 
parseHeaders(const std::string & headersString)81 HTTPHeaders CurlClient::parseHeaders(const std::string& headersString) {
82   vector<StringPiece> headersList;
83   HTTPHeaders headers;
84   folly::split(",", headersString, headersList);
85   for (const auto& headerPair : headersList) {
86     vector<StringPiece> nv;
87     folly::split('=', headerPair, nv);
88     if (nv.size() > 0) {
89       if (nv[0].empty()) {
90         continue;
91       }
92       std::string value("");
93       for (size_t i = 1; i < nv.size(); i++) {
94         value += folly::to<std::string>(nv[i], '=');
95       }
96       if (nv.size() > 1) {
97         value.pop_back();
98       } // trim anything else
99       headers.add(nv[0], value);
100     }
101   }
102   return headers;
103 }
104 
initializeSsl(const string & caPath,const string & nextProtos,const string & certPath,const string & keyPath)105 void CurlClient::initializeSsl(const string& caPath,
106                                const string& nextProtos,
107                                const string& certPath,
108                                const string& keyPath) {
109   sslContext_ = std::make_shared<folly::SSLContext>();
110   sslContext_->setOptions(SSL_OP_NO_COMPRESSION);
111   folly::ssl::setCipherSuites<folly::ssl::SSLCommonOptions>(*sslContext_);
112   if (!caPath.empty()) {
113     sslContext_->loadTrustedCertificates(caPath.c_str());
114   }
115   if (!certPath.empty() && !keyPath.empty()) {
116     sslContext_->loadCertKeyPairFromFiles(certPath.c_str(), keyPath.c_str());
117   }
118   list<string> nextProtoList;
119   folly::splitTo<string>(
120       ',', nextProtos, std::inserter(nextProtoList, nextProtoList.begin()));
121   sslContext_->setAdvertisedNextProtocols(nextProtoList);
122   h2c_ = false;
123 }
124 
sslHandshakeFollowup(HTTPUpstreamSession * session)125 void CurlClient::sslHandshakeFollowup(HTTPUpstreamSession* session) noexcept {
126   AsyncSSLSocket* sslSocket =
127       dynamic_cast<AsyncSSLSocket*>(session->getTransport());
128 
129   const unsigned char* nextProto = nullptr;
130   unsigned nextProtoLength = 0;
131   sslSocket->getSelectedNextProtocol(&nextProto, &nextProtoLength);
132   if (nextProto) {
133     VLOG(1) << "Client selected next protocol "
134             << string((const char*)nextProto, nextProtoLength);
135   } else {
136     VLOG(1) << "Client did not select a next protocol";
137   }
138 
139   // Note: This ssl session can be used by defining a member and setting
140   // something like sslSession_ = sslSocket->getSSLSession() and then
141   // passing it to the connector::connectSSL() method
142 }
143 
setFlowControlSettings(int32_t recvWindow)144 void CurlClient::setFlowControlSettings(int32_t recvWindow) {
145   recvWindow_ = recvWindow;
146 }
147 
connectSuccess(HTTPUpstreamSession * session)148 void CurlClient::connectSuccess(HTTPUpstreamSession* session) {
149 
150   if (url_.isSecure()) {
151     sslHandshakeFollowup(session);
152   }
153 
154   session->setFlowControl(recvWindow_, recvWindow_, recvWindow_);
155   sendRequest(session->newTransaction(this));
156   session->closeWhenIdle();
157 }
158 
setupHeaders()159 void CurlClient::setupHeaders() {
160   request_.setMethod(httpMethod_);
161   request_.setHTTPVersion(httpMajor_, httpMinor_);
162   if (proxy_) {
163     request_.setURL(url_.getUrl());
164   } else {
165     request_.setURL(url_.makeRelativeURL());
166   }
167   request_.setSecure(url_.isSecure());
168   if (h2c_) {
169     HTTP2Codec::requestUpgrade(request_);
170   }
171 
172   if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_USER_AGENT)) {
173     request_.getHeaders().add(HTTP_HEADER_USER_AGENT, "proxygen_curl");
174   }
175   if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_HOST)) {
176     request_.getHeaders().add(HTTP_HEADER_HOST, url_.getHostAndPort());
177   }
178   if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_ACCEPT)) {
179     request_.getHeaders().add("Accept", "*/*");
180   }
181   if (loggingEnabled_) {
182     request_.dumpMessage(4);
183   }
184 }
185 
sendRequest(HTTPTransaction * txn)186 void CurlClient::sendRequest(HTTPTransaction* txn) {
187   txn_ = txn;
188   setupHeaders();
189   txn_->sendHeaders(request_);
190 
191   if (httpMethod_ == HTTPMethod::POST) {
192     inputFile_ =
193         std::make_unique<ifstream>(inputFilename_, ios::in | ios::binary);
194     sendBodyFromFile();
195   } else {
196     txn_->sendEOM();
197   }
198 }
199 
sendBodyFromFile()200 void CurlClient::sendBodyFromFile() {
201   const uint16_t kReadSize = 4096;
202   CHECK(inputFile_);
203   // Reading from the file by chunks
204   // Important note: It's pretty bad to call a blocking i/o function like
205   // ifstream::read() in an eventloop - but for the sake of this simple
206   // example, we'll do it.
207   // An alternative would be to put this into some folly::AsyncReader
208   // object.
209   while (inputFile_->good() && !egressPaused_) {
210     unique_ptr<IOBuf> buf = IOBuf::createCombined(kReadSize);
211     inputFile_->read((char*)buf->writableData(), kReadSize);
212     buf->append(inputFile_->gcount());
213     txn_->sendBody(move(buf));
214   }
215   if (!egressPaused_) {
216     txn_->sendEOM();
217   }
218 }
219 
printMessageImpl(proxygen::HTTPMessage * msg,const std::string & tag)220 void CurlClient::printMessageImpl(proxygen::HTTPMessage* msg,
221                                   const std::string& tag) {
222   if (!loggingEnabled_) {
223     return;
224   }
225   cout << tag;
226   msg->dumpMessage(10);
227 }
228 
connectError(const folly::AsyncSocketException & ex)229 void CurlClient::connectError(const folly::AsyncSocketException& ex) {
230   LOG_IF(ERROR, loggingEnabled_)
231       << "Coudln't connect to " << url_.getHostAndPort() << ":" << ex.what();
232 }
233 
setTransaction(HTTPTransaction *)234 void CurlClient::setTransaction(HTTPTransaction*) noexcept {
235 }
236 
detachTransaction()237 void CurlClient::detachTransaction() noexcept {
238 }
239 
onHeadersComplete(unique_ptr<HTTPMessage> msg)240 void CurlClient::onHeadersComplete(unique_ptr<HTTPMessage> msg) noexcept {
241   response_ = std::move(msg);
242   printMessageImpl(response_.get());
243   if (!headersLoggingEnabled_) {
244     return;
245   }
246   response_->describe(*outputStream_);
247   *outputStream_ << std::endl;
248 }
249 
onBody(std::unique_ptr<folly::IOBuf> chain)250 void CurlClient::onBody(std::unique_ptr<folly::IOBuf> chain) noexcept {
251   if (!loggingEnabled_) {
252     return;
253   }
254   CHECK(outputStream_);
255   if (chain) {
256     const IOBuf* p = chain.get();
257     do {
258       outputStream_->write((const char*)p->data(), p->length());
259       outputStream_->flush();
260       p = p->next();
261     } while (p != chain.get());
262   }
263 }
264 
onTrailers(std::unique_ptr<HTTPHeaders>)265 void CurlClient::onTrailers(std::unique_ptr<HTTPHeaders>) noexcept {
266   LOG_IF(INFO, loggingEnabled_) << "Discarding trailers";
267 }
268 
onEOM()269 void CurlClient::onEOM() noexcept {
270   LOG_IF(INFO, loggingEnabled_) << "Got EOM";
271   if (eomFunc_) {
272     eomFunc_.value()();
273   }
274 }
275 
onUpgrade(UpgradeProtocol)276 void CurlClient::onUpgrade(UpgradeProtocol) noexcept {
277   LOG_IF(INFO, loggingEnabled_) << "Discarding upgrade protocol";
278 }
279 
onError(const HTTPException & error)280 void CurlClient::onError(const HTTPException& error) noexcept {
281   LOG_IF(ERROR, loggingEnabled_) << "An error occurred: " << error.what();
282 }
283 
onEgressPaused()284 void CurlClient::onEgressPaused() noexcept {
285   VLOG_IF(1, loggingEnabled_) << "Egress paused";
286   egressPaused_ = true;
287 }
288 
onEgressResumed()289 void CurlClient::onEgressResumed() noexcept {
290   VLOG_IF(1, loggingEnabled_) << "Egress resumed";
291   egressPaused_ = false;
292   if (inputFile_) {
293     sendBodyFromFile();
294   }
295 }
296 
onPushedTransaction(proxygen::HTTPTransaction * pushedTxn)297 void CurlClient::onPushedTransaction(
298     proxygen::HTTPTransaction* pushedTxn) noexcept {
299   //
300   pushTxnHandlers_.emplace_back(std::make_unique<CurlPushHandler>(this));
301   pushedTxn->setHandler(pushTxnHandlers_.back().get());
302   // Add implementation of the push transaction reception here
303 }
304 
getServerName() const305 const string& CurlClient::getServerName() const {
306   const string& res = request_.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST);
307   if (res.empty()) {
308     return url_.getHost();
309   }
310   return res;
311 }
312 
313 // CurlPushHandler methods
setTransaction(proxygen::HTTPTransaction * txn)314 void CurlClient::CurlPushHandler::setTransaction(
315     proxygen::HTTPTransaction* txn) noexcept {
316   LOG_IF(INFO, parent_->loggingEnabled_) << "Received pushed transaction";
317   pushedTxn_ = txn;
318 }
319 
detachTransaction()320 void CurlClient::CurlPushHandler::detachTransaction() noexcept {
321   LOG_IF(INFO, parent_->loggingEnabled_) << "Detached pushed transaction";
322 }
323 
onHeadersComplete(std::unique_ptr<proxygen::HTTPMessage> msg)324 void CurlClient::CurlPushHandler::onHeadersComplete(
325     std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {
326   if (!seenOnHeadersComplete_) {
327     seenOnHeadersComplete_ = true;
328     promise_ = std::move(msg);
329     parent_->printMessageImpl(promise_.get(), "[PP] ");
330   } else {
331     response_ = std::move(msg);
332     parent_->printMessageImpl(response_.get(), "[PR] ");
333   }
334 }
335 
onBody(std::unique_ptr<folly::IOBuf> chain)336 void CurlClient::CurlPushHandler::onBody(
337     std::unique_ptr<folly::IOBuf> chain) noexcept {
338   parent_->onBody(std::move(chain));
339 }
340 
onEOM()341 void CurlClient::CurlPushHandler::onEOM() noexcept {
342   LOG_IF(INFO, parent_->loggingEnabled_) << "Got PushTxn EOM";
343 }
344 
onError(const proxygen::HTTPException & error)345 void CurlClient::CurlPushHandler::onError(
346     const proxygen::HTTPException& error) noexcept {
347   parent_->onError(error);
348 }
349 
350 } // namespace CurlService
351