Http.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <utility>
  19. #include "Http.h"
  20. #include <kopano/mapi_ptr.h>
  21. #include <kopano/stringutil.h>
  22. #include <kopano/ECConfig.h>
  23. using namespace std;
  24. /**
  25. * Parse the incoming URL into known pieces:
  26. *
  27. * /<service>/[username][/foldername][/uid.ics]
  28. * service: ical or caldav, mandatory
  29. * username: open store of this user, "public" for public folders. optional: default comes from HTTP Authenticate header.
  30. * foldername: folder name in store to open. multiple forms possible (normal name, prefix_guid, prefix_entryid).
  31. *
  32. * @param[in] strUrl incoming url
  33. * @param[out] lpulFlag url flags (service type and public marker)
  34. * @param[out] lpstrUrlUser owner of lpstrFolder
  35. * @param[out] lpstrFolder folder id or name
  36. *
  37. * @return
  38. */
  39. HRESULT HrParseURL(const std::string &strUrl, ULONG *lpulFlag, std::string *lpstrUrlUser, std::string *lpstrFolder)
  40. {
  41. HRESULT hr = hrSuccess;
  42. std::string strService;
  43. std::string strFolder;
  44. std::string strUrlUser;
  45. vector<std::string> vcUrlTokens;
  46. vector<std::string>::const_iterator iterToken;
  47. ULONG ulFlag = 0;
  48. vcUrlTokens = tokenize(strUrl, L'/', true);
  49. if (vcUrlTokens.empty())
  50. // root should be present, no flags are set. mostly used on OPTIONS command
  51. goto exit;
  52. if (vcUrlTokens.back().rfind(".ics") != string::npos)
  53. // Guid is retrieved using StripGuid().
  54. vcUrlTokens.pop_back();
  55. else
  56. // request is for folder not a calendar entry
  57. ulFlag |= REQ_COLLECTION;
  58. if (vcUrlTokens.empty())
  59. goto exit;
  60. if (vcUrlTokens.size() > 3) {
  61. // sub folders are not allowed
  62. hr = MAPI_E_TOO_COMPLEX;
  63. goto exit;
  64. }
  65. iterToken = vcUrlTokens.cbegin();
  66. //change case of Service name ICAL -> ical CALDaV ->caldav
  67. strService = strToLower(*iterToken++);
  68. if (!strService.compare("ical"))
  69. ulFlag |= SERVICE_ICAL;
  70. else if (!strService.compare("caldav"))
  71. ulFlag |= SERVICE_CALDAV;
  72. else
  73. ulFlag |= SERVICE_UNKNOWN;
  74. if (iterToken == vcUrlTokens.cend())
  75. goto exit;
  76. //change case of folder owner USER -> user, UseR -> user
  77. strUrlUser = strToLower(*iterToken++);
  78. // check if the request is for public folders and set the bool flag
  79. // @note: request for public folder not have user's name in the url
  80. if (!strUrlUser.compare("public"))
  81. ulFlag |= REQ_PUBLIC;
  82. if (iterToken == vcUrlTokens.cend())
  83. goto exit;
  84. // @todo subfolder/folder/ is not allowed! only subfolder/item.ics
  85. for (; iterToken != vcUrlTokens.end(); ++iterToken)
  86. strFolder = strFolder + *iterToken + "/";
  87. strFolder.erase(strFolder.length() - 1);
  88. exit:
  89. if (lpulFlag)
  90. *lpulFlag = ulFlag;
  91. if (lpstrUrlUser)
  92. lpstrUrlUser->swap(strUrlUser);
  93. if (lpstrFolder)
  94. lpstrFolder->swap(strFolder);
  95. return hr;
  96. }
  97. Http::Http(ECChannel *lpChannel, ECConfig *lpConfig)
  98. {
  99. m_lpChannel = lpChannel;
  100. m_lpConfig = lpConfig;
  101. m_ulKeepAlive = 0;
  102. m_ulRetCode = 0;
  103. }
  104. /**
  105. * Reads the http headers from the channel and Parses them
  106. *
  107. * @return HRESULT
  108. * @retval MAPI_E_INVALID_PARAMETER The http hearders are invalid
  109. */
  110. HRESULT Http::HrReadHeaders()
  111. {
  112. HRESULT hr;
  113. std::string strBuffer;
  114. ULONG n = 0;
  115. std::map<std::string, std::string>::iterator iHeader = mapHeaders.end();
  116. ec_log_debug("Receiving headers:");
  117. do
  118. {
  119. hr = m_lpChannel->HrReadLine(&strBuffer);
  120. if (hr != hrSuccess)
  121. return hr;
  122. if (strBuffer.empty())
  123. break;
  124. if (n == 0) {
  125. m_strAction = strBuffer;
  126. } else {
  127. std::string::size_type pos = strBuffer.find(':');
  128. std::string::size_type start = 0;
  129. if (strBuffer[0] == ' ' || strBuffer[0] == '\t') {
  130. if (iHeader == mapHeaders.end())
  131. continue;
  132. // continue header
  133. while (strBuffer[start] == ' ' || strBuffer[start] == '\t')
  134. ++start;
  135. iHeader->second += strBuffer.substr(start);
  136. } else {
  137. // new header
  138. auto r = mapHeaders.insert(make_pair<string,string>(strBuffer.substr(0,pos), strBuffer.substr(pos+2)));
  139. iHeader = r.first;
  140. }
  141. }
  142. if (strBuffer.find("Authorization") != string::npos)
  143. ec_log_debug("< Authorization: <value hidden>");
  144. else
  145. ec_log_debug("< "+strBuffer);
  146. ++n;
  147. } while(hr == hrSuccess);
  148. hr = HrParseHeaders();
  149. if (hr != hrSuccess)
  150. ec_log_debug("parsing headers failed: 0x%08X", hr);
  151. return hr;
  152. }
  153. /**
  154. * Parse the http headers
  155. * @return HRESULT
  156. * @retval MAPI_E_INVALID_PARAMETER The http headers are invalid
  157. */
  158. // @todo this does way too much.
  159. HRESULT Http::HrParseHeaders()
  160. {
  161. HRESULT hr;
  162. std::string strAuthdata;
  163. std::string strUserAgent;
  164. std::vector<std::string> items;
  165. std::string user_pass;
  166. size_t colon_pos;
  167. items = tokenize(m_strAction, ' ', true);
  168. if (items.size() != 3) {
  169. ec_log_debug("HrParseHeaders invalid != 3 tokens");
  170. return MAPI_E_INVALID_PARAMETER;
  171. }
  172. m_strMethod = items[0];
  173. m_strURL = items[1];
  174. m_strHttpVer = items[2];
  175. // converts %20 -> ' '
  176. m_strPath = urlDecode(m_strURL);
  177. // find the content-type
  178. // Content-Type: text/xml;charset=UTF-8
  179. hr = HrGetHeaderValue("Content-Type", &m_strCharSet);
  180. if (hr == hrSuccess && m_strCharSet.find("charset") != std::string::npos)
  181. m_strCharSet = m_strCharSet.substr(m_strCharSet.find("charset")+ strlen("charset") + 1, m_strCharSet.length());
  182. else
  183. m_strCharSet = m_lpConfig->GetSetting("default_charset"); // really should be UTF-8
  184. hr = HrGetHeaderValue("User-Agent", &strUserAgent);
  185. if (hr == hrSuccess) {
  186. size_t space = strUserAgent.find(" ");
  187. if (space != std::string::npos) {
  188. m_strUserAgent = strUserAgent.substr(0, space);
  189. m_strUserAgentVersion = strUserAgent.substr(space + 1);
  190. }
  191. else {
  192. m_strUserAgent = strUserAgent;
  193. }
  194. }
  195. // find the Authorisation data (Authorization: Basic wr8y273yr2y3r87y23ry7=)
  196. hr = HrGetHeaderValue("Authorization", &strAuthdata);
  197. if (hr != hrSuccess) {
  198. hr = HrGetHeaderValue("WWW-Authenticate", &strAuthdata);
  199. if (hr != hrSuccess)
  200. return S_OK; /* ignore empty Authorization */
  201. }
  202. items = tokenize(strAuthdata, ' ', true);
  203. // we only support basic authentication
  204. if (items.size() != 2 || items[0].compare("Basic") != 0) {
  205. ec_log_debug("HrParseHeaders login failed");
  206. return MAPI_E_LOGON_FAILED;
  207. }
  208. user_pass = base64_decode(items[1]);
  209. if((colon_pos = user_pass.find(":")) == std::string::npos) {
  210. ec_log_debug("HrParseHeaders password missing");
  211. return MAPI_E_LOGON_FAILED;
  212. }
  213. m_strUser = user_pass.substr(0, colon_pos);
  214. m_strPass = user_pass.substr(colon_pos+1, std::string::npos);
  215. return hrSuccess;
  216. }
  217. /**
  218. * Returns the user name set in the request
  219. * @param[in] strUser Return string for username in request
  220. * @return HRESULT
  221. * @retval MAPI_E_NOT_FOUND No username set in the request
  222. */
  223. HRESULT Http::HrGetUser(std::wstring *strUser)
  224. {
  225. if (m_strUser.empty())
  226. return MAPI_E_NOT_FOUND;
  227. return X2W(m_strUser, strUser);
  228. }
  229. /**
  230. * Returns the method set in the url
  231. * @param[out] strMethod Return string for method set in request
  232. * @return HRESULT
  233. * @retval MAPI_E_NOT_FOUND Empty method in request
  234. */
  235. HRESULT Http::HrGetMethod(std::string *strMethod)
  236. {
  237. if (m_strMethod.empty())
  238. return MAPI_E_NOT_FOUND;
  239. strMethod->assign(m_strMethod);
  240. return hrSuccess;
  241. }
  242. /**
  243. * Returns the password sent by user
  244. * @param[out] strPass The password is returned
  245. * @return HRESULT
  246. * @retval MAPI_E_NOT_FOUND Empty password in request
  247. */
  248. HRESULT Http::HrGetPass(std::wstring *strPass)
  249. {
  250. if (m_strPass.empty())
  251. return MAPI_E_NOT_FOUND;
  252. return X2W(m_strPass, strPass);
  253. }
  254. /**
  255. * return the original, non-decoded, url
  256. *
  257. * @param strURL us-ascii encoded url
  258. *
  259. * @return
  260. */
  261. HRESULT Http::HrGetRequestUrl(std::string *strURL)
  262. {
  263. strURL->assign(m_strURL);
  264. return hrSuccess;
  265. }
  266. /**
  267. * Returns the full decoded path of request(e.g. /caldav/user name/folder)
  268. * (eg. %20 is converted to ' ')
  269. * @param[out] strReqPath Return string for path
  270. * @return HRESULT
  271. * @retval MAPI_E_NOT_FOUND Empty path in request
  272. */
  273. HRESULT Http::HrGetUrl(std::string *strUrl)
  274. {
  275. if (m_strPath.empty())
  276. return MAPI_E_NOT_FOUND;
  277. strUrl->assign(urlDecode(m_strPath));
  278. return hrSuccess;
  279. }
  280. /**
  281. * Returns body of the request
  282. * @param[in] strBody Return string for body of the request
  283. * @return HRESULT
  284. * @retval MAPI_E_NOT_FOUND No body present in request
  285. */
  286. HRESULT Http::HrGetBody(std::string *strBody)
  287. {
  288. if (m_strReqBody.empty())
  289. return MAPI_E_NOT_FOUND;
  290. strBody->assign(m_strReqBody);
  291. return hrSuccess;
  292. }
  293. /**
  294. * Returns the Depth set in request
  295. * @param[out] ulDepth Return string for depth set in request
  296. * @return HRESULT
  297. * @retval MAPI_E_NOT_FOUND No depth value set in request
  298. */
  299. HRESULT Http::HrGetDepth(ULONG *ulDepth)
  300. {
  301. HRESULT hr = hrSuccess;
  302. std::string strDepth;
  303. /*
  304. * Valid input: [0, 1, infinity]
  305. */
  306. hr = HrGetHeaderValue("Depth", &strDepth);
  307. if (hr != hrSuccess)
  308. *ulDepth = 0; // default is no subfolders. default should become a parameter .. is action dependant
  309. else if (strDepth.compare("infinity") == 0)
  310. *ulDepth = 2;
  311. else {
  312. *ulDepth = atoi(strDepth.c_str());
  313. if (*ulDepth > 1)
  314. *ulDepth = 1;
  315. }
  316. return hr;
  317. }
  318. /**
  319. * Checks the etag of a MAPI object against If-(None)-Match headers
  320. *
  321. * @param[in] lpProp Object to check etag (PR_LAST_MODIFICATION_TIME) to
  322. *
  323. * @return continue request or return 412
  324. * @retval true continue request
  325. * @retval false return 412 to client
  326. */
  327. bool Http::CheckIfMatch(LPMAPIPROP lpProp)
  328. {
  329. bool ret = false;
  330. bool invert = false;
  331. string strIf;
  332. SPropValuePtr ptrLastModTime;
  333. string strValue;
  334. if (lpProp != nullptr &&
  335. HrGetOneProp(lpProp, PR_LAST_MODIFICATION_TIME, &~ptrLastModTime) == hrSuccess) {
  336. time_t stamp;
  337. FileTimeToUnixTime(ptrLastModTime->Value.ft, &stamp);
  338. strValue = stringify_int64(stamp, false);
  339. }
  340. if (HrGetHeaderValue("If-Match", &strIf) == hrSuccess) {
  341. if (strIf.compare("*") == 0 && !ptrLastModTime)
  342. // we have an object without a last mod time, not allowed
  343. return false;
  344. } else if (HrGetHeaderValue("If-None-Match", &strIf) == hrSuccess) {
  345. if (strIf.compare("*") == 0 && !!ptrLastModTime)
  346. // we have an object which has a last mod time, not allowed
  347. return false;
  348. invert = true;
  349. } else {
  350. return true;
  351. }
  352. // check all etags for a match
  353. for (auto &i : tokenize(strIf, ',', true)) {
  354. if (i.at(0) == '"' || i.at(0) == '\'')
  355. i.assign(i.begin() + 1, i.end() - 1);
  356. if (i.compare(strValue) == 0) {
  357. ret = true;
  358. break;
  359. }
  360. }
  361. if (invert)
  362. ret = !ret;
  363. return ret;
  364. }
  365. /**
  366. * Returns Charset of the request
  367. * @param[out] strCharset Return string Charset of the request
  368. *
  369. * @return HRESULT
  370. * @retval MAPI_E_NOT_FOUND No charset set in request
  371. */
  372. HRESULT Http::HrGetCharSet(std::string *strCharset)
  373. {
  374. if (m_strCharSet.empty())
  375. return MAPI_E_NOT_FOUND;
  376. strCharset->assign(m_strCharSet);
  377. return hrSuccess;
  378. }
  379. /**
  380. * Returns the Destination value found in the header
  381. *
  382. * Specifies the destination of entry in MOVE request,
  383. * to move mapi message from one folder to another
  384. * for eg.
  385. *
  386. * Destination: https://kopano.com:8080/caldav/USER/FOLDER-ID/ENTRY-GUID.ics
  387. *
  388. * @param[out] strDestination Return string destination of the request
  389. *
  390. * @return HRESULT
  391. * @retval MAPI_E_NOT_FOUND No destination set in request
  392. */
  393. HRESULT Http::HrGetDestination(std::string *strDestination)
  394. {
  395. HRESULT hr;
  396. std::string strHost;
  397. std::string strDest;
  398. string::size_type pos;
  399. // example: Host: server:port
  400. hr = HrGetHeaderValue("Host", &strHost);
  401. if(hr != hrSuccess) {
  402. ec_log_debug("Http::HrGetDestination host header missing");
  403. return hr;
  404. }
  405. // example: Destination: http://server:port/caldav/username/folderid/entry.ics
  406. hr = HrGetHeaderValue("Destination", &strDest);
  407. if (hr != hrSuccess) {
  408. ec_log_debug("Http::HrGetDestination destination header missing");
  409. return hr;
  410. }
  411. pos = strDest.find(strHost);
  412. if (pos == string::npos) {
  413. ec_log_err("Refusing to move calendar item from %s to different host on url %s", strHost.c_str(), strDest.c_str());
  414. return MAPI_E_CALL_FAILED;
  415. }
  416. strDest.erase(0, pos + strHost.length());
  417. *strDestination = std::move(strDest);
  418. return hrSuccess;
  419. }
  420. /**
  421. * Reads request body from the channel
  422. * @return HRESULT
  423. * @retval MAPI_E_NOT_FOUND Empty body
  424. */
  425. HRESULT Http::HrReadBody()
  426. {
  427. HRESULT hr = hrSuccess;
  428. int ulContLength;
  429. std::string strLength;
  430. // find the Content-Length
  431. if (HrGetHeaderValue("Content-Length", &strLength) != hrSuccess) {
  432. ec_log_debug("Http::HrReadBody content-length missing");
  433. return MAPI_E_NOT_FOUND;
  434. }
  435. ulContLength = atoi((char*)strLength.c_str());
  436. if (ulContLength <= 0) {
  437. ec_log_debug("Http::HrReadBody content-length invalid %d", ulContLength);
  438. return MAPI_E_NOT_FOUND;
  439. }
  440. hr = m_lpChannel->HrReadBytes(&m_strReqBody, ulContLength);
  441. if (!m_strUser.empty())
  442. ec_log_debug("Request body:\n%s\n", m_strReqBody.c_str());
  443. return hr;
  444. }
  445. /**
  446. * Check for errors in the http request
  447. * @return HRESULT
  448. * @retval MAPI_E_INVALID_PARAMETER Unsupported http request
  449. */
  450. HRESULT Http::HrValidateReq()
  451. {
  452. static const char *const lpszMethods[] = {
  453. "ACL", "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS",
  454. "PROPFIND", "REPORT", "MKCALENDAR", "PROPPATCH", "MOVE", NULL,
  455. };
  456. bool bFound = false;
  457. int i;
  458. if (m_strMethod.empty()) {
  459. static const HRESULT hr = MAPI_E_INVALID_PARAMETER;
  460. ec_log_err("HTTP request method is empty: %08X", hr);
  461. return hr;
  462. }
  463. if (!parseBool(m_lpConfig->GetSetting("enable_ical_get")) && m_strMethod == "GET") {
  464. ec_log_err("Denying iCalendar GET since it is disabled");
  465. return MAPI_E_NO_ACCESS;
  466. }
  467. for (i = 0; lpszMethods[i] != NULL; ++i) {
  468. if (m_strMethod.compare(lpszMethods[i]) == 0) {
  469. bFound = true;
  470. break;
  471. }
  472. }
  473. if (bFound == false) {
  474. static const HRESULT hr = MAPI_E_INVALID_PARAMETER;
  475. ec_log_err("HTTP request '%s' not implemented: %08X", m_strMethod.c_str(), hr);
  476. return hr;
  477. }
  478. // validate authentication data
  479. if (m_strUser.empty() || m_strPass.empty())
  480. // hr still success, since http request is valid
  481. ec_log_debug("Request missing authorization data");
  482. return hrSuccess;
  483. }
  484. /**
  485. * Sets keep-alive time for the http response
  486. *
  487. * @param[in] ulKeepAlive Numerical value set as keep-alive time of http connection
  488. * @return HRESULT Always set as hrSuccess
  489. */
  490. HRESULT Http::HrSetKeepAlive(int ulKeepAlive)
  491. {
  492. m_ulKeepAlive = ulKeepAlive;
  493. return hrSuccess;
  494. }
  495. /**
  496. * Flush all headers and body to client(i.e Send all data to client)
  497. *
  498. * Sends the data in chunked http if the data is large and client uses http 1.1
  499. * Example of Chunked http
  500. * 1A[CRLF] - size in hex
  501. * xxxxxxxxxxxx..[CRLF] - data
  502. * 1A[CRLF] - size in hex
  503. * xxxxxxxxxxxx..[CRLF] - data
  504. * 0[CRLF] - end of response
  505. * [CRLF]
  506. *
  507. * @return HRESULT
  508. * @retval MAPI_E_END_OF_SESSION States that client as set connection type as closed
  509. */
  510. HRESULT Http::HrFinalize()
  511. {
  512. HRESULT hr = hrSuccess;
  513. HrResponseHeader("Content-Length", stringify(m_strRespBody.length()));
  514. // force chunked http for long size response, should check version >= 1.1 to disable chunking
  515. if (m_strRespBody.size() < HTTP_CHUNK_SIZE || m_strHttpVer.compare("1.1") != 0)
  516. {
  517. hr = HrFlushHeaders();
  518. if (hr != hrSuccess && hr != MAPI_E_END_OF_SESSION) {
  519. ec_log_debug("Http::HrFinalize flush fail %d", hr);
  520. m_ulRetCode = 0;
  521. return hr;
  522. }
  523. if (!m_strRespBody.empty()) {
  524. m_lpChannel->HrWriteString(m_strRespBody);
  525. ec_log_debug("Response body:\n%s", m_strRespBody.c_str());
  526. }
  527. }
  528. else
  529. {
  530. const char *lpstrBody = m_strRespBody.data();
  531. char lpstrLen[10];
  532. std::string::size_type szBodyLen = m_strRespBody.size(); // length of data to be sent to the client
  533. std::string::size_type szBodyWritten = 0; // length of data sent to client
  534. unsigned int szPart = HTTP_CHUNK_SIZE; // default lenght of chunk data to be written
  535. HrResponseHeader("Transfer-Encoding", "chunked");
  536. hr = HrFlushHeaders();
  537. if (hr != hrSuccess && hr != MAPI_E_END_OF_SESSION) {
  538. ec_log_debug("Http::HrFinalize flush fail(2) %d", hr);
  539. m_ulRetCode = 0;
  540. return hr;
  541. }
  542. while (szBodyWritten < szBodyLen)
  543. {
  544. if ((szBodyWritten + HTTP_CHUNK_SIZE) > szBodyLen)
  545. szPart = szBodyLen - szBodyWritten; // change length of data for last chunk
  546. // send hex length of data and data part
  547. snprintf(lpstrLen, sizeof(lpstrLen), "%X", szPart);
  548. m_lpChannel->HrWriteLine(lpstrLen);
  549. m_lpChannel->HrWriteLine((char*)lpstrBody, szPart);
  550. szBodyWritten += szPart;
  551. lpstrBody += szPart;
  552. }
  553. // end of response
  554. snprintf(lpstrLen, 10, "0\r\n");
  555. m_lpChannel->HrWriteLine(lpstrLen);
  556. // just the first part of the body in the log. header shows it's chunked.
  557. ec_log_debug("%s", m_strRespBody.c_str());
  558. }
  559. // if http_log_enable?
  560. char szTime[32];
  561. time_t now = time(NULL);
  562. tm local;
  563. string strAgent;
  564. localtime_r(&now, &local);
  565. // @todo we're in C LC_TIME locale to get the correct (month) format, but the timezone will be GMT, which is not wanted.
  566. strftime(szTime, ARRAY_SIZE(szTime), "%d/%b/%Y:%H:%M:%S %z", &local);
  567. HrGetHeaderValue("User-Agent", &strAgent);
  568. ec_log_notice("%s - %s [%s] \"%s\" %d %d \"-\" \"%s\"", m_lpChannel->peer_addr(), m_strUser.empty() ? "-" : m_strUser.c_str(), szTime, m_strAction.c_str(), m_ulRetCode, (int)m_strRespBody.length(), strAgent.c_str());
  569. m_ulRetCode = 0;
  570. return hr;
  571. }
  572. /**
  573. * Converts common hr codes to http error codes
  574. *
  575. * @param hr HRESULT
  576. *
  577. * @return Error from HrResponseHeader(unsigned int, string)
  578. */
  579. HRESULT Http::HrToHTTPCode(HRESULT hr)
  580. {
  581. if (hr == hrSuccess)
  582. return HrResponseHeader(200, "Ok");
  583. else if (hr == MAPI_E_NO_ACCESS)
  584. return HrResponseHeader(403, "Forbidden");
  585. else if (hr == MAPI_E_NOT_FOUND)
  586. return HrResponseHeader(404, "Not Found");
  587. // @todo other codes?
  588. return HrResponseHeader(500, "Unhanded error " + stringify(hr, true));
  589. }
  590. /**
  591. * Sets http response headers
  592. * @param[in] ulCode Http header status code
  593. * @param[in] strResponse Http header status string
  594. *
  595. * @return HRESULT
  596. * @retval MAPI_E_CALL_FAILED The status is already set
  597. *
  598. */
  599. HRESULT Http::HrResponseHeader(unsigned int ulCode, std::string strResponse)
  600. {
  601. m_ulRetCode = ulCode;
  602. // do not set headers if once set
  603. if (!m_strRespHeader.empty())
  604. return MAPI_E_CALL_FAILED;
  605. m_strRespHeader = "HTTP/1.1 " + stringify(ulCode) + " " + strResponse;
  606. return hrSuccess;
  607. }
  608. /**
  609. * Adds response header to the list of headers
  610. * @param[in] strHeader Name of the header eg. Connection, Host, Date
  611. * @param[in] strValue Value of the header to be set
  612. * @return HRESULT Always set to hrSuccess
  613. */
  614. HRESULT Http::HrResponseHeader(std::string strHeader, std::string strValue)
  615. {
  616. m_lstHeaders.push_back(strHeader + ": " + strValue);
  617. return hrSuccess;
  618. }
  619. /**
  620. * Add string to http response body
  621. * @param[in] strResponse The string to be added to http response body
  622. * return HRESULT Always set to hrSuccess
  623. */
  624. HRESULT Http::HrResponseBody(std::string strResponse)
  625. {
  626. m_strRespBody += strResponse;
  627. // data send in HrFinalize()
  628. return hrSuccess;
  629. }
  630. /**
  631. * Request authorization information from the client
  632. *
  633. * @param[in] strMsg Message to be shown to the client
  634. * @return HRESULT
  635. */
  636. HRESULT Http::HrRequestAuth(std::string strMsg)
  637. {
  638. HRESULT hr = HrResponseHeader(401, "Unauthorized");
  639. if (hr != hrSuccess)
  640. return hr;
  641. strMsg = "Basic realm=\"" + strMsg + "\"";
  642. hr = HrResponseHeader("WWW-Authenticate", strMsg);
  643. if (hr != hrSuccess)
  644. return hr;
  645. return hrSuccess;
  646. }
  647. /**
  648. * Write all http headers to ECChannel
  649. * @retrun HRESULT
  650. * @retval MAPI_E_END_OF_SESSION If Connection type is set to close then the mapi session is ended
  651. */
  652. HRESULT Http::HrFlushHeaders()
  653. {
  654. HRESULT hr = hrSuccess;
  655. std::string strOutput;
  656. char lpszChar[128];
  657. time_t tmCurrenttime = time(NULL);
  658. std::string strConnection;
  659. HrGetHeaderValue("Connection", &strConnection);
  660. // Add misc. headers
  661. HrResponseHeader("Server","Kopano");
  662. struct tm dummy;
  663. strftime(lpszChar, 127, "%a, %d %b %Y %H:%M:%S GMT", gmtime_safe(&tmCurrenttime, &dummy));
  664. HrResponseHeader("Date", lpszChar);
  665. if (m_ulKeepAlive != 0 && strcasecmp(strConnection.c_str(), "keep-alive") == 0) {
  666. HrResponseHeader("Connection", "Keep-Alive");
  667. HrResponseHeader("Keep-Alive", stringify(m_ulKeepAlive, false));
  668. }
  669. else
  670. {
  671. HrResponseHeader("Connection", "close");
  672. hr = MAPI_E_END_OF_SESSION;
  673. }
  674. // create headers packet
  675. assert(m_ulRetCode != 0);
  676. if (m_ulRetCode == 0)
  677. HrResponseHeader(500, "Request handled incorrectly");
  678. ec_log_debug("> " + m_strRespHeader);
  679. strOutput += m_strRespHeader + "\r\n";
  680. m_strRespHeader.clear();
  681. for (const auto &h : m_lstHeaders) {
  682. ec_log_debug("> " + h);
  683. strOutput += h + "\r\n";
  684. }
  685. m_lstHeaders.clear();
  686. //as last line has a CRLF. The HrWriteLine adds one more CRLF.
  687. //this means the End of headder.
  688. m_lpChannel->HrWriteLine(strOutput);
  689. return hr;
  690. }
  691. HRESULT Http::X2W(const std::string &strIn, std::wstring *lpstrOut)
  692. {
  693. const char *lpszCharset = (m_strCharSet.empty() ? "UTF-8" : m_strCharSet.c_str());
  694. return TryConvert(m_converter, strIn, rawsize(strIn), lpszCharset, *lpstrOut);
  695. }
  696. HRESULT Http::HrGetHeaderValue(const std::string &strHeader, std::string *strValue)
  697. {
  698. auto iHeader = mapHeaders.find(strHeader);
  699. if (iHeader == mapHeaders.cend())
  700. return MAPI_E_NOT_FOUND;
  701. *strValue = iHeader->second;
  702. return hrSuccess;
  703. }
  704. HRESULT Http::HrGetUserAgent(std::string *strUserAgent)
  705. {
  706. if (m_strUserAgent.empty())
  707. return MAPI_E_NOT_FOUND;
  708. strUserAgent -> assign(m_strUserAgent);
  709. return hrSuccess;
  710. }
  711. HRESULT Http::HrGetUserAgentVersion(std::string *strUserAgentVersion)
  712. {
  713. if (m_strUserAgentVersion.empty())
  714. return MAPI_E_NOT_FOUND;
  715. strUserAgentVersion -> assign(m_strUserAgentVersion);
  716. return hrSuccess;
  717. }