BBS2chProxyPoster.cpp 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  1. #include "BBS2chProxyPoster.h"
  2. #include <vector>
  3. #include <map>
  4. #include <sstream>
  5. #include <algorithm>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <unistd.h>
  10. #ifdef _WIN32
  11. #include <windows.h>
  12. #endif
  13. #ifdef USE_LUA
  14. #include <lua.hpp>
  15. #endif
  16. #include "BBS2chProxyAuth.h"
  17. #include "BBS2chProxyKeyManager.h"
  18. #include "hmac.h"
  19. #include "stringEncodingConverter.h"
  20. #include "parson/parson.h"
  21. extern char *proxy_server;
  22. extern long proxy_port;
  23. extern long proxy_type;
  24. extern long timeout;
  25. extern char *user_agent;
  26. extern int force_ipv4;
  27. extern char *appKey;
  28. extern char *hmacKey;
  29. extern unsigned int api_mode;
  30. extern BBS2chProxyHttpHeaders bbscgi_headers;
  31. extern std::vector<std::string> bbscgi_postorder;
  32. extern unsigned int bbscgi_utf8;
  33. extern char *lua_script;
  34. extern int allow_chunked;
  35. extern CURLSH *curl_share;
  36. extern int manage_bbscgi_cookies;
  37. extern unsigned int curl_version_number;
  38. extern void log_printf(int level, const char *format ...);
  39. extern double getCurrentTime(void);
  40. static size_t header_callback_bbscgi(char *buffer, size_t size, size_t nitems, void *userdata)
  41. {
  42. BBS2chProxy5chPoster *poster = static_cast<BBS2chProxy5chPoster *>(userdata);
  43. BBS2chProxyConnection *conn = poster->connectionDelegate;
  44. if(poster->_status > 255) return size*nitems;
  45. else if(poster->_status == -1) {
  46. if(!memcmp(buffer,"\r\n",2)) {
  47. poster->_status = 0;
  48. }
  49. return size*nitems;
  50. }
  51. PBBS2chProxyHttpHeaderEntry parsedHeader = BBS2chProxyHttpHeaders::parse(buffer, size*nitems);
  52. if (parsedHeader) { // Looks like a header
  53. const std::string &headerName = parsedHeader->getLowercasedName();
  54. //fprintf(stderr, "%s\n", parsedHeader->getFull().c_str());
  55. if (headerName == "connection") {
  56. poster->_responseHeaders.append("Connection: Close\r\n");
  57. return size*nitems;
  58. }
  59. else if (headerName == "transfer-encoding") {
  60. if (parsedHeader->contains("chunked")) {
  61. if (allow_chunked && !conn->isClientHttp1_0) {
  62. poster->_isResponseChunked = true;
  63. poster->_responseHeaders.append(buffer, size*nitems);
  64. }
  65. return size*nitems;
  66. }
  67. }
  68. else if (headerName == "set-cookie") {
  69. poster->_hasSetCookie = true;
  70. if (conn->force5ch) {
  71. std::string value = parsedHeader->getFull(true);
  72. size_t start = value.find("domain=");
  73. if (start != std::string::npos) {
  74. start += 7;
  75. size_t end = value.find(";", start);
  76. size_t pos = value.find(".5ch.net", start);
  77. if (pos != std::string::npos && (end == std::string::npos || pos < end)) {
  78. value[start+1] = '2';
  79. poster->_responseHeaders.append(value);
  80. return size*nitems;
  81. }
  82. }
  83. }
  84. }
  85. else if (headerName == "x-chx-error") {
  86. const std::string &headerValue = parsedHeader->getValue();
  87. const char *ptr = headerValue.c_str();
  88. log_printf(0, "Posting to bbs.cgi has been cancelled. Reason: %s\n", ptr);
  89. if (*ptr == 'E') {
  90. int code = atoi(ptr+1);
  91. if ((code >= 3310 && code <= 3311) || /* inconsistent with User-Agent or something? */
  92. (code >= 3320 && code <= 3324) || /* key expiration? */
  93. (code >= 3390 && code <= 3392)) /* 3390: BAN, 3391-3392: key expiration? */
  94. {
  95. BBS2chProxyConnection::keyManager.setKey("", poster->_monaKeyForRequest, poster->_userAgentForRequest, code);
  96. if (poster->_isFirstRun) {
  97. poster->_status = 1;
  98. }
  99. }
  100. } else if (poster->_manageCookies) {
  101. int code = atoi(ptr);
  102. if ((code == 1932) && poster->_isFirstRun) { // was 1930, 1931
  103. poster->_status = 2;
  104. }
  105. }
  106. }
  107. else if (headerName == "x-monakey") {
  108. BBS2chProxyConnection::keyManager.setKey(parsedHeader->getValue(), poster->_monaKeyForRequest, poster->_userAgentForRequest, 0);
  109. }
  110. poster->_responseHeaders.append(buffer, size*nitems);
  111. return size*nitems;
  112. }
  113. else {
  114. if (!strncasecmp("HTTP/", buffer, 5)) {
  115. const char *ptr = buffer + 5;
  116. const char *end = buffer + size*nitems;
  117. while (ptr < end && *ptr != ' ') ptr++;
  118. while (ptr < end && *ptr == ' ') ptr++;
  119. if (ptr < end) {
  120. int code = atoi(ptr);
  121. if (code == 100) {
  122. poster->_status = -1;
  123. return size*nitems;
  124. }
  125. }
  126. }
  127. poster->_responseHeaders.append(buffer, size*nitems);
  128. if (!memcmp(buffer, "\r\n", 2)) {
  129. if (poster->_status == 0) {
  130. conn->socketToClient->write(poster->_responseHeaders.data(), poster->_responseHeaders.size());
  131. poster->_status = 256;
  132. }
  133. else return 0;
  134. }
  135. return size*nitems;
  136. }
  137. }
  138. static size_t write_callback_proxy(char *buffer, size_t size, size_t nitems, void *userdata)
  139. {
  140. BBS2chProxy5chPoster *poster = static_cast<BBS2chProxy5chPoster *>(userdata);
  141. BBS2chProxyConnection *conn = poster->connectionDelegate;
  142. if(poster->_isResponseChunked) {
  143. char buf[64];
  144. snprintf(buf, 64, "%lx\r\n", size*nitems);
  145. conn->socketToClient->write(buf, strlen(buf));
  146. }
  147. size_t ret = conn->socketToClient->write(buffer, size*nitems);
  148. if(poster->_isResponseChunked) conn->socketToClient->writeString("\r\n");
  149. return ret;
  150. }
  151. static size_t header_callback_download(char *buffer, size_t size, size_t nitems, void *userdata)
  152. {
  153. BBS2chProxyHttpHeaders *headers = static_cast<BBS2chProxyHttpHeaders *>(userdata);
  154. PBBS2chProxyHttpHeaderEntry parsedHeader = BBS2chProxyHttpHeaders::parse(buffer, size*nitems);
  155. if (parsedHeader.get()) { // Looks like a header
  156. const std::string &headerName = parsedHeader->getLowercasedName();
  157. if (headerName == "connection") {
  158. if (headers) headers->add(parsedHeader->getName(), "close");
  159. return size*nitems;
  160. }
  161. else if (headerName == "transfer-encoding") {
  162. if (parsedHeader->contains("chunked")) {
  163. return size*nitems;
  164. }
  165. }
  166. if (headers) headers->add(parsedHeader->getName(), parsedHeader->getValue());
  167. }
  168. else if (headers) headers->setStatusLine(buffer, size*nitems);
  169. return size*nitems;
  170. }
  171. static size_t write_callback_download(char *buffer, size_t size, size_t nitems, void *userdata)
  172. {
  173. std::vector<char> *data = static_cast<std::vector<char> *>(userdata);
  174. size_t downloaded = size*nitems;
  175. if (data) data->insert(data->end(), buffer, buffer+downloaded);
  176. return downloaded;
  177. }
  178. static bool isValidAsUTF8(const char *input, size_t inputLength) {
  179. for (int i=0; i<inputLength; i++) {
  180. unsigned char c1 = input[i];
  181. if (c1 < 0x80) continue;
  182. else if (c1 >= 0xc2 && c1 <= 0xdf) {
  183. if (i >= inputLength - 1) return false;
  184. unsigned char c2 = input[++i];
  185. if (c2 < 0x80 || c2 > 0xbf) return false;
  186. unsigned int unicode = c2 & 0xf;
  187. unicode |= (((c2 >> 4) & 0x3) | ((c1 & 0x3) << 2)) << 4;
  188. unicode |= ((c1 >> 2) & 0x7) << 8;
  189. if (unicode < 0x80 || unicode > 0x7ff) return false;
  190. }
  191. else if (c1 >= 0xe0 && c1 <= 0xef) {
  192. if (i >= inputLength - 2) return false;
  193. unsigned char c2 = input[++i];
  194. if (c2 < 0x80 || c2 > 0xbf) return false;
  195. unsigned char c3 = input[++i];
  196. if (c3 < 0x80 || c3 > 0xbf) return false;
  197. unsigned int unicode = c3 & 0xf;
  198. unicode |= (((c3 >> 4) & 0x3) | ((c2 & 0x3) << 2)) << 4;
  199. unicode |= ((c2 >> 2) & 0xf) << 8;
  200. unicode |= (c1 & 0xf) << 12;
  201. if (unicode < 0x800 || unicode > 0xffff) return false;
  202. else if (unicode >= 0xd800 && unicode <= 0xdfff) return false; /* for surrogate pairs */
  203. }
  204. else if (c1 >= 0xf0 && c1 <= 0xf7) {
  205. if (i >= inputLength - 3) return false;
  206. unsigned char c2 = input[++i];
  207. if (c2 < 0x80 || c2 > 0xbf) return false;
  208. unsigned char c3 = input[++i];
  209. if (c3 < 0x80 || c3 > 0xbf) return false;
  210. unsigned char c4 = input[++i];
  211. if (c4 < 0x80 || c4 > 0xbf) return false;
  212. unsigned int unicode = c4 & 0xf;
  213. unicode |= (((c4 >> 4) & 0x3) | ((c3 & 0x3) << 2)) << 4;
  214. unicode |= ((c3 >> 2) & 0xf) << 8;
  215. unicode |= (c2 & 0xf) << 12;
  216. unicode |= (((c2 >> 4) & 0x3) | ((c1 & 0x3) << 2)) << 16;
  217. unicode |= ((c1 >> 2) & 0x1) << 20;
  218. if (unicode < 0x10000 || unicode > 0x10ffff) return false;
  219. }
  220. else return false;
  221. }
  222. return true;
  223. }
  224. static void appendPostSignature(BBS2chProxyFormData &body, const std::string &userAgent, const std::string &monaKey, BBS2chProxyHttpHeaders &headers, bool forTalk)
  225. {
  226. char nonce[32];
  227. std::string message;
  228. if (!forTalk) {
  229. snprintf(nonce, 32, "%.3f", getCurrentTime());
  230. message.append(body["bbs"]);
  231. message.append("<>");
  232. message.append(body["key"]);
  233. message.append("<>");
  234. message.append(body["time"]);
  235. message.append("<>");
  236. message.append(body["FROM"]);
  237. message.append("<>");
  238. message.append(body["mail"]);
  239. message.append("<>");
  240. message.append(body["MESSAGE"]);
  241. message.append("<>");
  242. message.append(body["subject"]);
  243. message.append("<>");
  244. message.append(userAgent);
  245. message.append("<>");
  246. message.append(monaKey);
  247. message.append("<>");
  248. message.append("<>");
  249. message.append(nonce);
  250. } else {
  251. message.append(body["bbs"]);
  252. message.append("<>");
  253. message.append(body["key"]);
  254. message.append("<>");
  255. message.append(body["subject"]);
  256. message.append("<>");
  257. message.append(body["MESSAGE"]);
  258. message.append("<>");
  259. message.append(body["time"]);
  260. message.append("<>");
  261. message.append(body["sid"]);
  262. message.append("<>");
  263. }
  264. unsigned char digest[32];
  265. char digestStr[65];
  266. static const char *table = "0123456789abcdef";
  267. proxy2ch_HMAC_SHA256(hmacKey, strlen(hmacKey), message.data(), message.length(), digest);
  268. for (int i=0; i<32; i++) {
  269. unsigned char c = digest[i];
  270. unsigned char upper = (c >> 4) & 0xf;
  271. unsigned char lower = c & 0xf;
  272. digestStr[i*2] = table[upper];
  273. digestStr[i*2+1] = table[lower];
  274. }
  275. digestStr[64] = 0;
  276. if (!forTalk) {
  277. headers.set("X-APIKey", appKey);
  278. log_printf(1, "Appended header \"X-APIKey: %s\"\n", appKey);
  279. headers.set("X-PostSig", digestStr);
  280. log_printf(1, "Appended header \"X-PostSig: %s\"\n", digestStr);
  281. headers.set("X-PostNonce", nonce);
  282. log_printf(1, "Appended header \"X-PostNonce: %s\"\n", nonce);
  283. headers.set("X-MonaKey", monaKey);
  284. log_printf(1, "Appended header \"X-MonaKey: %s\"\n", monaKey.c_str());
  285. } else {
  286. headers.set("X-Write-Token", digestStr);
  287. log_printf(1, "Appended header \"X-Write-Token: %s\"\n", digestStr);
  288. if (monaKey != "00000000-0000-0000-0000-000000000000") {
  289. headers.set("X-Write-Key", monaKey);
  290. log_printf(1, "Appended header \"X-Write-Key: %s\"\n", monaKey.c_str());
  291. }
  292. }
  293. }
  294. static bool convertBodyToUTF8(BBS2chProxyFormData &body)
  295. {
  296. bool shouldConvertToUTF8 = true;
  297. bool shouldCheckWholeBody = true;
  298. const std::string &submit = body.get("submit");
  299. if (body.getEncoded("submit").size() != submit.size()) {
  300. if (isValidAsUTF8(submit.data(), submit.size())) {
  301. shouldConvertToUTF8 = false;
  302. }
  303. shouldCheckWholeBody = false;
  304. }
  305. if (shouldCheckWholeBody) {
  306. shouldConvertToUTF8 = false;
  307. for (BBS2chProxyFormData::iterator it = body.begin(); it != body.end(); ++it) {
  308. if (it->second.empty()) continue;
  309. if (!isValidAsUTF8(it->second.get().data(), it->second.get().size())) {
  310. shouldConvertToUTF8 = true;
  311. break;
  312. }
  313. }
  314. }
  315. if (shouldConvertToUTF8) {
  316. for (BBS2chProxyFormData::iterator it = body.begin(); it != body.end(); ++it) {
  317. if (it->second.empty()) continue;
  318. char *converted = convertShiftJISToUTF8(it->second.get().data(), it->second.get().size());
  319. if (converted) {
  320. it->second = std::string(converted);
  321. free(converted);
  322. }
  323. }
  324. }
  325. return shouldConvertToUTF8;
  326. }
  327. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  328. static void appendCurlCookiesToJar(CURL *curl, BBS2chProxyKeyManager::CookieJar &jar)
  329. {
  330. struct curl_slist *cookies = NULL;
  331. if (!curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies) && cookies) {
  332. struct curl_slist *each = cookies;
  333. jar.lock();
  334. jar.clear();
  335. while (each) {
  336. BBS2chProxyKeyManager::Cookie cookie(each->data);
  337. jar.set(cookie);
  338. each = each->next;
  339. }
  340. jar.unlock();
  341. BBS2chProxyConnection::keyManager.flushCookies();
  342. curl_slist_free_all(cookies);
  343. }
  344. }
  345. #endif
  346. #ifdef USE_LUA
  347. extern "C" {
  348. static int lua_hmacSHA256(lua_State *l)
  349. {
  350. static const char *table = "0123456789abcdef";
  351. size_t keyLength, dataLength;
  352. const char *key = luaL_checklstring(l, 1, &keyLength);
  353. const char *data = luaL_checklstring(l, 2, &dataLength);
  354. if (!key || !data) return 0;
  355. unsigned char digest[32];
  356. char digestStr[65];
  357. proxy2ch_HMAC_SHA256(key, keyLength, data, dataLength, digest);
  358. for (int i=0; i<32; i++) {
  359. unsigned char c = digest[i];
  360. unsigned char upper = (c >> 4) & 0xf;
  361. unsigned char lower = c & 0xf;
  362. digestStr[i*2] = table[upper];
  363. digestStr[i*2+1] = table[lower];
  364. }
  365. digestStr[64] = 0;
  366. lua_pushstring(l, digestStr);
  367. return 1;
  368. }
  369. static int lua_decodeURIComponent(lua_State *l)
  370. {
  371. size_t length;
  372. const char *input = luaL_checklstring(l, 1, &length);
  373. if (!input) return 0;
  374. bool decodePlus = true;
  375. if (!lua_isnoneornil(l, 2)) {
  376. decodePlus = (lua_toboolean(l, 2));
  377. }
  378. std::string output = BBS2chProxyFormData::decodeURIComponent(input, length, decodePlus);
  379. lua_pushstring(l, output.c_str());
  380. return 1;
  381. }
  382. static int lua_encodeURIComponent(lua_State *l)
  383. {
  384. size_t length;
  385. const char *input = luaL_checklstring(l, 1, &length);
  386. if (!input) return 0;
  387. bool spaceAsPlus = true;
  388. if (!lua_isnoneornil(l, 2)) {
  389. spaceAsPlus = (lua_toboolean(l, 2));
  390. }
  391. std::string output = BBS2chProxyFormData::encodeURIComponent(input, length, spaceAsPlus);
  392. lua_pushstring(l, output.c_str());
  393. return 1;
  394. }
  395. static int lua_convertShiftJISToUTF8(lua_State *l)
  396. {
  397. size_t length;
  398. const char *input = luaL_checklstring(l, 1, &length);
  399. if (!input) return 0;
  400. if (length > 0) {
  401. char *output = convertShiftJISToUTF8(input, length);
  402. if (!output) lua_pushnil(l);
  403. else {
  404. lua_pushstring(l, output);
  405. free(output);
  406. }
  407. }
  408. else lua_pushstring(l, "");
  409. return 1;
  410. }
  411. static int lua_isExpiredKey(lua_State *l)
  412. {
  413. size_t length;
  414. const char *input = luaL_checklstring(l, 1, &length);
  415. if (!input) return 0;
  416. if (BBS2chProxyConnection::keyManager.isExpired(input)) {
  417. lua_pushboolean(l, 1);
  418. }
  419. else lua_pushboolean(l, 0);
  420. return 1;
  421. }
  422. static int lua_isValidAsUTF8(lua_State *l)
  423. {
  424. size_t length;
  425. const char *input = luaL_checklstring(l, 1, &length);
  426. if (!input) return 0;
  427. lua_pushboolean(l, isValidAsUTF8(input, length));
  428. return 1;
  429. }
  430. static int lua_getMonaKey(lua_State *l)
  431. {
  432. size_t length;
  433. const char *input = luaL_checklstring(l, 1, &length);
  434. if (!input) return 0;
  435. const std::string &key = BBS2chProxyConnection::keyManager.getKey(input);
  436. lua_pushstring(l, key.c_str());
  437. return 1;
  438. }
  439. static int lua_getSID(lua_State *l)
  440. {
  441. const std::string &sid = BBS2chProxyConnection::auth.getSID();
  442. lua_pushstring(l, sid.c_str());
  443. return 1;
  444. }
  445. }
  446. #endif
  447. IBBS2chProxyPoster::IBBS2chProxyPoster(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  448. : _requestHeaders(headers), _requestBody(body), _verbose(0), connectionDelegate(delegate), _manageCookies(false)
  449. {
  450. prepareHeadersAndBody();
  451. }
  452. BBS2chProxy5chPoster::BBS2chProxy5chPoster(BBS2chProxyURL &url, BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  453. : IBBS2chProxyPoster(headers, body, delegate), _isFirstRun(true), _status(0), _isResponseChunked(false), _hasSetCookie(false)
  454. {
  455. _url = url.absoluteString();
  456. }
  457. BBS2chProxyTalkPoster::BBS2chProxyTalkPoster(BBS2chProxyURL &url, BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  458. : IBBS2chProxyPoster(headers, body, delegate)
  459. {
  460. _url = "https://api.talk-platform.com/v1/bbs.cgi";
  461. _requestBody.remove("sid");
  462. }
  463. BBS2chProxyTalkTo5chPoster::BBS2chProxyTalkTo5chPoster(BBS2chProxyURL &url, BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  464. : BBS2chProxy5chPoster(url, headers, body, delegate)
  465. {
  466. }
  467. void IBBS2chProxyPoster::prepareHeadersAndBody()
  468. {
  469. _host = _requestHeaders.get("Host");
  470. _board = _requestBody.get("bbs");
  471. _thread = _requestBody.get("key");
  472. if (!bbscgi_postorder.empty()) {
  473. _requestBody.reorder(bbscgi_postorder);
  474. log_printf(1, "Reordered request body is: %s\n", _requestBody.toString().c_str());
  475. }
  476. _requestHeaders.remove("Host");
  477. if (user_agent) _requestHeaders.set("User-Agent", user_agent);
  478. if (!bbscgi_headers.empty()) {
  479. for (std::map<std::string, PBBS2chProxyHttpHeaderEntry>::iterator it = bbscgi_headers.getMap().begin(); it != bbscgi_headers.getMap().end(); it++) {
  480. /* we create a copy of entry here, because the original entry shouldn't be modified */
  481. PBBS2chProxyHttpHeaderEntry entry(new BBS2chProxyHttpHeaderEntry(*it->second.get()));
  482. if (!_host.empty()) {
  483. entry->replaceValue("%HOST%", _host);
  484. }
  485. if (!_board.empty()) {
  486. entry->replaceValue("%BOARD%", _board);
  487. }
  488. if (!_thread.empty()) {
  489. entry->replaceValue("%THREAD%", _thread);
  490. }
  491. _requestHeaders.set(entry);
  492. log_printf(1, "Appended custom header \"%s\"\n", entry->getFull().c_str());
  493. }
  494. }
  495. }
  496. curl_slist* IBBS2chProxyPoster::prepareCurlHandle(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, curl_slist* headersForCurl)
  497. {
  498. CURL *curl = connectionDelegate->curl;
  499. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  500. if (_manageCookies) {
  501. log_printf(1, "Cookies are managed by proxy2ch, most of existing \"Cookie: \" headers are ignored.\n");
  502. curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); //enable cookie engine explicitly
  503. curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); //erase all cookies explicitly
  504. if (headers.has("Cookie")) {
  505. const std::string &value = headers.getEntry("Cookie")->getValue();
  506. std::vector<std::string> list;
  507. size_t offset = 0;
  508. while (1) {
  509. size_t pos = value.find("; ", offset);
  510. if (pos == std::string::npos) {
  511. list.push_back(value.substr(offset));
  512. break;
  513. }
  514. list.push_back(value.substr(offset, pos - offset));
  515. offset = pos + 2;
  516. }
  517. std::string newCookie;
  518. for (std::vector<std::string>::iterator it = list.begin(); it != list.end(); ++it) {
  519. size_t pos = it->find('=');
  520. if (pos == std::string::npos) continue;
  521. std::string name = it->substr(0, pos);
  522. if (name == "DMDM" || name == "MDMD" || name == "sid") {
  523. if (newCookie.size()) newCookie += "; ";
  524. newCookie += *it;
  525. }
  526. }
  527. if (newCookie.size()) curl_easy_setopt(curl, CURLOPT_COOKIE, newCookie.c_str());
  528. headers.remove("Cookie");
  529. }
  530. BBS2chProxyKeyManager::CookieJar &jar = BBS2chProxyConnection::keyManager.getCookieJar(_userAgentForRequest);
  531. jar.lock();
  532. std::vector<BBS2chProxyKeyManager::Cookie> &list = jar.getList();
  533. for (std::vector<BBS2chProxyKeyManager::Cookie>::iterator it = list.begin(); it != list.end(); ++it) {
  534. if (!it->isExpired()) curl_easy_setopt(curl, CURLOPT_COOKIELIST, it->valueInNetscapeFormat().c_str());
  535. }
  536. jar.unlock();
  537. }
  538. #endif
  539. headersForCurl = headers.appendToCurlSlist(headersForCurl);
  540. if (!headers.has("Expect")) headersForCurl = curl_slist_append(headersForCurl, "Expect:");
  541. if (!headers.has("Accept")) headersForCurl = curl_slist_append(headersForCurl, "Accept:");
  542. if (curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  543. curl_easy_setopt(curl, CURLOPT_URL, _url.c_str());
  544. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  545. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  546. curl_easy_setopt(curl, CURLOPT_POST, 1L);
  547. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.toString().c_str());
  548. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  549. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  550. curl_easy_setopt(curl, CURLOPT_VERBOSE, _verbose);
  551. if (force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  552. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  553. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headersForCurl);
  554. if (!_nic.empty()) curl_easy_setopt(curl, CURLOPT_INTERFACE, _nic.c_str());
  555. if (!_forceProxy.empty()) {
  556. curl_easy_setopt(curl, CURLOPT_PROXY, _forceProxy.c_str());
  557. }
  558. else if (proxy_server) {
  559. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  560. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  561. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  562. }
  563. return headersForCurl;
  564. }
  565. void IBBS2chProxyPoster::runLuaScript(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body)
  566. {
  567. #ifdef USE_LUA
  568. lua_State* l = luaL_newstate();
  569. luaL_openlibs(l);
  570. if (luaL_loadfile(l, lua_script) != LUA_OK) {
  571. log_printf(0, "Lua: Failed to open script %s:\n %s\n", lua_script, lua_tostring(l, -1));
  572. goto lua_end;
  573. }
  574. lua_newtable(l);
  575. lua_pushcfunction(l, lua_hmacSHA256);
  576. lua_setfield(l, -2, "hmacSHA256");
  577. lua_pushcfunction(l, lua_decodeURIComponent);
  578. lua_setfield(l, -2, "decodeURIComponent");
  579. lua_pushcfunction(l, lua_encodeURIComponent);
  580. lua_setfield(l, -2, "encodeURIComponent");
  581. lua_pushcfunction(l, lua_convertShiftJISToUTF8);
  582. lua_setfield(l, -2, "convertShiftJISToUTF8");
  583. lua_pushcfunction(l, lua_isExpiredKey);
  584. lua_setfield(l, -2, "isExpiredKey");
  585. lua_pushcfunction(l, lua_isValidAsUTF8);
  586. lua_setfield(l, -2, "isValidAsUTF8");
  587. lua_pushcfunction(l, lua_getMonaKey);
  588. lua_setfield(l, -2, "getMonaKey");
  589. lua_pushcfunction(l, lua_getSID);
  590. lua_setfield(l, -2, "getSID");
  591. lua_pushstring(l, BBS2chProxyConnection::keyManager.getKey().c_str());
  592. lua_setfield(l, -2, "monaKey");
  593. lua_pushinteger(l, connectionDelegate->serverPort);
  594. lua_setfield(l, -2, "port");
  595. lua_setglobal(l, "proxy2ch");
  596. BBS2chProxyHttpHeaders::getClassDefinitionForLua(l);
  597. lua_setglobal(l, "HttpHeaders");
  598. if (lua_pcall(l, 0, 0, 0) != LUA_OK) {
  599. log_printf(0, "Lua: Failed to run script %s:\n %s\n", lua_script, lua_tostring(l, -1));
  600. goto lua_end;
  601. }
  602. lua_getglobal(l, "willSendRequestToBbsCgi");
  603. if (!lua_isfunction(l, -1)) {
  604. log_printf(0, "Lua: willSendRequestToBbsCgi function does not exist in the script\n");
  605. goto lua_end;
  606. }
  607. lua_newtable(l);
  608. headers.getUserdataForLua(l);
  609. lua_setfield(l, -2, "headers");
  610. lua_pushstring(l, body.toString().c_str());
  611. lua_setfield(l, -2, "body");
  612. lua_pushstring(l, _host.c_str());
  613. lua_pushstring(l, _board.c_str());
  614. lua_pushstring(l, _thread.c_str());
  615. if (lua_pcall(l, 4, 1, 0) != LUA_OK) {
  616. log_printf(0, "Lua: Failed to call willSendRequestToBbsCgi function:\n %s\n", lua_tostring(l, -1));
  617. goto lua_end;
  618. }
  619. if (!lua_istable(l, -1)) {
  620. log_printf(0, "Lua: A return type of willSendRequestToBbsCgi function should be a table\n");
  621. goto lua_end;
  622. }
  623. lua_pushstring(l, "body");
  624. lua_rawget(l, -2);
  625. if (lua_isstring(l, -1)) {
  626. size_t length;
  627. const char *newBody = lua_tolstring(l, -1, &length);
  628. body = BBS2chProxyFormData(newBody, length);
  629. log_printf(1, "Lua: Set request body \"%s\"\n", newBody);
  630. }
  631. lua_pop(l, 1);
  632. lua_pushstring(l, "headers");
  633. lua_rawget(l, -2);
  634. if (lua_istable(l, -1)) {
  635. headers = BBS2chProxyHttpHeaders();
  636. lua_pushnil(l);
  637. while (lua_next(l, -2)) {
  638. if (lua_isstring(l, -1) && lua_isstring(l, -2)) {
  639. const char *name = lua_tostring(l, -2);
  640. const char *value = lua_tostring(l, -1);
  641. headers.add(name, value);
  642. log_printf(1, "Lua: Set request header \"%s: %s\"\n", name, value);
  643. }
  644. lua_pop(l, 1);
  645. }
  646. }
  647. else if (lua_isuserdata(l, -1)) {
  648. if (lua_getmetatable(l, -1)) {
  649. #if LUA_VERSION_NUM > 502
  650. if (lua_getfield(l, -1, "_type") == LUA_TSTRING)
  651. #else
  652. if (lua_getfield(l, -1, "_type"), lua_type(l, -1) == LUA_TSTRING)
  653. #endif
  654. {
  655. if (!strcmp(lua_tostring(l, -1), "HttpHeaders")) {
  656. BBS2chProxyHttpHeaders *newHeaders = *((BBS2chProxyHttpHeaders **)lua_touserdata(l, -3));
  657. if (newHeaders != &headers) {
  658. headers = *newHeaders;
  659. }
  660. for (BBS2chProxyHttpHeaders::iterator it = headers.begin(); it != headers.end(); ++it) {
  661. log_printf(1, "Lua: Set request header \"%s\"\n", it->second.c_str());
  662. }
  663. }
  664. }
  665. lua_pop(l, 2);
  666. }
  667. }
  668. lua_pop(l, 1);
  669. lua_pushstring(l, "options");
  670. lua_rawget(l, -2);
  671. if (lua_istable(l, -1)) {
  672. lua_pushstring(l, "interface");
  673. lua_rawget(l, -2);
  674. if (lua_isstring(l, -1)) {
  675. _nic = std::string(lua_tostring(l, -1));
  676. }
  677. lua_pop(l, 1);
  678. lua_pushstring(l, "verbose");
  679. lua_rawget(l, -2);
  680. if (lua_isboolean(l, -1)) {
  681. _verbose = lua_toboolean(l, -1);
  682. }
  683. lua_pop(l, 1);
  684. lua_pushstring(l, "proxy");
  685. lua_rawget(l, -2);
  686. if (lua_isstring(l, -1)) {
  687. _forceProxy = std::string(lua_tostring(l, -1));
  688. }
  689. lua_pop(l, 1);
  690. lua_pushstring(l, "manageCookies");
  691. lua_rawget(l, -2);
  692. if (lua_isboolean(l, -1)) {
  693. if (curl_version_number >= 0x074d00) _manageCookies = lua_toboolean(l, -1);
  694. }
  695. lua_pop(l, 1);
  696. }
  697. lua_end:
  698. lua_close(l);
  699. #endif
  700. }
  701. void BBS2chProxy5chPoster::makeSignature(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body)
  702. {
  703. bool isPink = _host.find("bbspink.com") != std::string::npos;
  704. bool shouldSign = appKey && (((api_mode & 2) && !isPink) || (api_mode & 4));
  705. bool shouldConvertBodyToUTF8 = (bbscgi_utf8 == 1 && shouldSign) || (bbscgi_utf8 == 2);
  706. _userAgentForRequest = headers.get("User-Agent");
  707. if (headers.has("X-MonaKey")) {
  708. _monaKeyForRequest = headers.get("X-MonaKey");
  709. }
  710. if (shouldConvertBodyToUTF8 && !headers.has("X-PostSig")) {
  711. if (convertBodyToUTF8(body)) {
  712. log_printf(1, "Converted request body to UTF-8: %s\n", body.toString().c_str());
  713. }
  714. else {
  715. log_printf(1, "Request body seems already to be UTF-8, will be sent without conversion\n");
  716. }
  717. std::string contentType = headers.get("Content-Type");
  718. std::transform(contentType.begin(), contentType.end(), contentType.begin(), tolower);
  719. if (contentType.find("charset=utf-8") == std::string::npos) {
  720. headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
  721. log_printf(1, "Appended header \"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\"\n");
  722. }
  723. }
  724. if (shouldSign && (!lua_script || !headers.has("X-PostSig"))) {
  725. if (!_userAgentForRequest.empty()) {
  726. _monaKeyForRequest = BBS2chProxyConnection::keyManager.getKey(_userAgentForRequest);
  727. appendPostSignature(body, _userAgentForRequest, _monaKeyForRequest, headers, false);
  728. } else {
  729. log_printf(0, "API: User-Agent muse be set explicitly to post with API.\n");
  730. }
  731. }
  732. if (!_monaKeyForRequest.empty()) {
  733. double wait = BBS2chProxyConnection::keyManager.secondsToWaitBeforePosting(_monaKeyForRequest);
  734. if (wait > 0) {
  735. log_printf(1, "Sleeping for %.1f seconds to avoid posting too fast...\n", wait);
  736. #ifdef _WIN32
  737. Sleep(wait * 1e+3);
  738. #else
  739. usleep(wait * 1e+6);
  740. #endif
  741. }
  742. }
  743. }
  744. long BBS2chProxy5chPoster::post()
  745. {
  746. CURL *curl = connectionDelegate->curl;
  747. long statusCode = 0;
  748. for (int run=0; run<2; run++) {
  749. BBS2chProxyHttpHeaders _headers = _requestHeaders;
  750. BBS2chProxyFormData _body = _requestBody;
  751. curl_slist *headersForCurl = NULL;
  752. _verbose = 0;
  753. _status = 0;
  754. _monaKeyForRequest.clear();
  755. _nic.clear();
  756. _forceProxy.clear();
  757. _isFirstRun = run == 0;
  758. _responseHeaders.clear();
  759. _isResponseChunked = false;
  760. _hasSetCookie = false;
  761. _manageCookies = manage_bbscgi_cookies;
  762. #ifdef USE_LUA
  763. if (lua_script) {
  764. runLuaScript(_headers, _body);
  765. }
  766. #endif
  767. makeSignature(_headers, _body);
  768. headersForCurl = prepareCurlHandle(_headers, _body, headersForCurl);
  769. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_bbscgi);
  770. curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
  771. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_proxy);
  772. curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
  773. CURLcode res = curl_easy_perform(curl);
  774. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  775. if (_manageCookies && _hasSetCookie) {
  776. appendCurlCookiesToJar(curl, BBS2chProxyConnection::keyManager.getCookieJar(_userAgentForRequest));
  777. }
  778. #endif
  779. if (res != CURLE_OK) {
  780. if (res == CURLE_WRITE_ERROR && _status == 1) {
  781. log_printf(1, "MonaKey should be reset. Sending the same request automatically...\n");
  782. curl_easy_reset(curl);
  783. curl_slist_free_all(headersForCurl);
  784. continue;
  785. }
  786. else if (res == CURLE_WRITE_ERROR && _status == 2) {
  787. log_printf(1, "Acorn cookie is rotten. Sending the same request automatically...\n");
  788. curl_easy_reset(curl);
  789. curl_slist_free_all(headersForCurl);
  790. continue;
  791. }
  792. else {
  793. log_printf(0, "curl error: %s (%s)\n", curl_easy_strerror(res), _url.c_str());
  794. if (!_status) connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  795. statusCode = 503;
  796. }
  797. }
  798. else {
  799. if (_isResponseChunked) {
  800. connectionDelegate->socketToClient->writeString("0\r\n\r\n");
  801. }
  802. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  803. }
  804. curl_easy_reset(curl);
  805. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  806. if (_manageCookies) {
  807. /* since curl_easy_reset() doesn't reset cookie engine, reset explicitly.
  808. curl < 7.77.0 doesn't have a way to disable cookie engine,
  809. so once enabled it will be enabled permanently. See https://github.com/curl/curl/issues/6889 */
  810. curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); //force erase all cookies
  811. curl_easy_setopt(curl, CURLOPT_COOKIEFILE, NULL); //force disable cookie engine
  812. }
  813. #endif
  814. curl_slist_free_all(headersForCurl);
  815. break;
  816. }
  817. return statusCode;
  818. }
  819. void BBS2chProxyTalkPoster::makeSignature(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body)
  820. {
  821. bool shouldSign = appKey && (api_mode & 8);
  822. bool shouldConvertBodyToUTF8 = (bbscgi_utf8 == 1 && shouldSign) || (bbscgi_utf8 == 2);
  823. _userAgentForRequest = headers.get("User-Agent");
  824. if (headers.has("X-Write-Key")) {
  825. _monaKeyForRequest = headers.get("X-Write-Key");
  826. }
  827. if (shouldConvertBodyToUTF8 && !headers.has("X-Write-Token")) {
  828. if (convertBodyToUTF8(body)) {
  829. log_printf(1, "Converted request body to UTF-8: %s\n", body.toString().c_str());
  830. }
  831. else {
  832. log_printf(1, "Request body seems already to be UTF-8, will be sent without conversion\n");
  833. }
  834. std::string contentType = headers.get("Content-Type");
  835. std::transform(contentType.begin(), contentType.end(), contentType.begin(), tolower);
  836. if (contentType.find("charset=utf-8") == std::string::npos) {
  837. headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
  838. log_printf(1, "Appended header \"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\"\n");
  839. }
  840. }
  841. if (shouldSign && (!lua_script || !headers.has("X-Write-Token"))) {
  842. if (!_userAgentForRequest.empty()) {
  843. body.append("sid", BBS2chProxyConnection::auth.getSID());
  844. body.append("appkey", appKey);
  845. body.append("anonymous", body.has("subject") ? "true" : "false");
  846. _monaKeyForRequest = BBS2chProxyConnection::keyManager.getKey(_userAgentForRequest);
  847. appendPostSignature(body, _userAgentForRequest, _monaKeyForRequest, headers, true);
  848. } else {
  849. log_printf(0, "API: User-Agent muse be set explicitly to post with API.\n");
  850. }
  851. }
  852. if (!_monaKeyForRequest.empty()) {
  853. double wait = BBS2chProxyConnection::keyManager.secondsToWaitBeforePosting(_monaKeyForRequest);
  854. if (wait > 0) {
  855. log_printf(1, "Sleeping for %.1f seconds to avoid posting too fast...\n", wait);
  856. #ifdef _WIN32
  857. Sleep(wait * 1e+3);
  858. #else
  859. usleep(wait * 1e+6);
  860. #endif
  861. }
  862. }
  863. }
  864. long BBS2chProxyTalkPoster::post()
  865. {
  866. CURL *curl = connectionDelegate->curl;
  867. long statusCode = 0;
  868. for (int run=0; run<2; run++) {
  869. BBS2chProxyHttpHeaders _headers = _requestHeaders;
  870. BBS2chProxyFormData _body = _requestBody;
  871. curl_slist *headersForCurl = NULL;
  872. std::string acceptEncoding;
  873. _verbose = 0;
  874. _nic.clear();
  875. _forceProxy.clear();
  876. _manageCookies = false;
  877. #ifdef USE_LUA
  878. if (lua_script) {
  879. runLuaScript(_headers, _body);
  880. _manageCookies = false;
  881. }
  882. #endif
  883. makeSignature(_headers, _body);
  884. if (_headers.has("Accept-Encoding")) {
  885. acceptEncoding = _headers.get("Accept-Encoding");
  886. _headers.remove("Accept-Encoding");
  887. }
  888. headersForCurl = prepareCurlHandle(_headers, _body, headersForCurl);
  889. BBS2chProxyHttpHeaders receivedHeaders;
  890. std::vector<char> receivedBody;
  891. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_download);
  892. curl_easy_setopt(curl, CURLOPT_HEADERDATA, &receivedHeaders);
  893. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  894. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &receivedBody);
  895. if (!acceptEncoding.empty()) curl_easy_setopt(curl, CURLOPT_ENCODING, acceptEncoding.c_str());
  896. CURLcode res = curl_easy_perform(curl);
  897. bool responseSent = false;
  898. if (res == CURLE_OK) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  899. if (receivedHeaders.hasNameAndValue("Content-Type", "application/json")) {
  900. receivedBody.push_back('\0');
  901. JSON_Value *json = json_parse_string(&receivedBody.front());
  902. if (json && json_type(json) == JSONObject) {
  903. JSON_Object *root = json_object(json);
  904. const char *error = json_object_dotget_string(root, "error.message");
  905. if (error) {
  906. log_printf(0, "Posting to bbs.cgi has been cancelled. Reason: %s\n", error);
  907. std::string out = "<html>\n<head>\n<title>ERROR!</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\">\n</head>\n<body bgcolor=\"#EFEFEF\">\n<font size=\"+1\" color=\"#FF0000\"><b>ERROR: ";
  908. out += error;
  909. out += "</b></font>\n</body>\n</html>";
  910. char *outSJIS = convertUTF8ToShiftJIS(out.c_str(), out.size());
  911. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  912. connectionDelegate->socketToClient->writeString("Content-Type: text/html; charset=Shift_JIS\r\n\r\n");
  913. if (outSJIS) {
  914. connectionDelegate->socketToClient->write(outSJIS, strlen(outSJIS));
  915. free(outSJIS);
  916. }
  917. statusCode = 200;
  918. responseSent = true;
  919. } else if (statusCode == 200) {
  920. int resNum = json_object_get_number(root, "commentNumber");
  921. std::string out = "<html lang=\"ja\">\n<head>\n<title>書きこみました。</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\">\n</head>\n<body>書きこみが終わりました。<br><br>\n画面を切り替えるまでしばらくお待ち下さい。<br><br>\n</body>\n</html>";
  922. char *outSJIS = convertUTF8ToShiftJIS(out.c_str(), out.size());
  923. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  924. if (resNum) {
  925. std::ostringstream ss;
  926. ss << "X-Resnum: " << resNum << "\r\n";
  927. connectionDelegate->socketToClient->writeString(ss.str());
  928. }
  929. connectionDelegate->socketToClient->writeString("Content-Type: text/html; charset=Shift_JIS\r\n\r\n");
  930. if (outSJIS) {
  931. connectionDelegate->socketToClient->write(outSJIS, strlen(outSJIS));
  932. free(outSJIS);
  933. }
  934. responseSent = true;
  935. }
  936. }
  937. if (json) json_value_free(json);
  938. }
  939. else if (receivedHeaders.has("X-Write-Key")) {
  940. BBS2chProxyConnection::keyManager.setKey(receivedHeaders.get("X-Write-Key"), _monaKeyForRequest, _userAgentForRequest, 0);
  941. const std::string &extendToken = receivedHeaders.get("X-Write-Key-Extend-Token");
  942. if (run == 0) {
  943. log_printf(1, "MonaKey has been updated. Sending the same request automatically...\n");
  944. if (!extendToken.empty()) {
  945. _requestHeaders.set("X-Write-Key-Extend-Token", extendToken);
  946. }
  947. curl_easy_reset(curl);
  948. curl_slist_free_all(headersForCurl);
  949. continue;
  950. } else if (!extendToken.empty()) {
  951. log_printf(0, "WARNING: you must send header \"X-Write-Key-Extend-Token: %s\" to complete posting.\n", extendToken.c_str());
  952. }
  953. std::string out = "<html><head><title>■ 書き込み確認 ■</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\"></head><body bgcolor=\"#EEEEEE\"><font size=\"+1\" color=\"#FF0000\"><b>書きこみ&クッキー確認</b></font><br><br><b>投稿確認<br><b style=\"color: #F00; font-size: larger;\">この書き込みで本当にいいですか?<br>\n<form method=\"POST\" action=\"../test/bbs.cgi\" accept-charset=\"Shift_JIS\"><input type=hidden name=FROM value=><input type=hidden name=mail value=><input type=hidden name=MESSAGE value=><input type=hidden name=bbs value=><input type=hidden name=time value=><input type=hidden name=key value=><input type=submit value=\"上記全てを承諾して書き込む\" name=\"submit\"></form></body></html>";
  954. char *outSJIS = convertUTF8ToShiftJIS(out.c_str(), out.size());
  955. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  956. connectionDelegate->socketToClient->writeString("Content-Type: text/html; charset=Shift_JIS\r\n\r\n");
  957. if (outSJIS) {
  958. connectionDelegate->socketToClient->write(outSJIS, strlen(outSJIS));
  959. free(outSJIS);
  960. }
  961. statusCode = 200;
  962. responseSent = true;
  963. }
  964. if (!responseSent) {
  965. log_printf(5, "bbscgi response: %s\n", receivedHeaders.getStatusLine().c_str());
  966. for (BBS2chProxyHttpHeaders::iterator it = receivedHeaders.begin(); it != receivedHeaders.end(); ++it) {
  967. log_printf(5, "bbscgi response: %s\n", it->second.c_str());
  968. }
  969. receivedBody.push_back('\0');
  970. log_printf(5, "bbscgi response: %s\n", &receivedBody.front());
  971. connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  972. statusCode = 503;
  973. }
  974. curl_easy_reset(curl);
  975. curl_slist_free_all(headersForCurl);
  976. break;
  977. }
  978. return statusCode;
  979. }
  980. long BBS2chProxyTalkTo5chPoster::post()
  981. {
  982. CURL *curl = connectionDelegate->curl;
  983. long statusCode = 0;
  984. for (int run=0; run<2; run++) {
  985. BBS2chProxyHttpHeaders _headers = _requestHeaders;
  986. BBS2chProxyFormData _body = _requestBody;
  987. curl_slist *headersForCurl = NULL;
  988. _verbose = 0;
  989. _status = 0;
  990. _monaKeyForRequest = "";
  991. _nic.clear();
  992. _forceProxy.clear();
  993. _manageCookies = manage_bbscgi_cookies;
  994. #ifdef USE_LUA
  995. if (lua_script) {
  996. runLuaScript(_headers, _body);
  997. }
  998. #endif
  999. makeSignature(_headers, _body);
  1000. headersForCurl = prepareCurlHandle(_headers, _body, headersForCurl);
  1001. BBS2chProxyHttpHeaders receivedHeaders;
  1002. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_download);
  1003. curl_easy_setopt(curl, CURLOPT_HEADERDATA, &receivedHeaders);
  1004. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  1005. curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
  1006. CURLcode res = curl_easy_perform(curl);
  1007. if (res == CURLE_OK) {
  1008. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  1009. if (receivedHeaders.has("Set-Cookie")) {
  1010. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  1011. if (_manageCookies) {
  1012. appendCurlCookiesToJar(curl, BBS2chProxyConnection::keyManager.getCookieJar(_userAgentForRequest));
  1013. }
  1014. #endif
  1015. std::vector<std::string>& values = receivedHeaders.getEntry("Set-Cookie")->getValueList();
  1016. for (std::vector<std::string>::iterator it = values.begin(); it != values.end(); it++) {
  1017. std::string &value = *it;
  1018. size_t start = value.find("domain=");
  1019. if (start == std::string::npos) continue;
  1020. start += 7;
  1021. size_t end = value.find(";", start);
  1022. size_t pos = value.find(".5ch.net", start);
  1023. size_t domainLen = 8;
  1024. if (pos == std::string::npos || (end != std::string::npos && pos >= end)) {
  1025. pos = value.find(".bbspink.com", start);
  1026. domainLen = 12;
  1027. }
  1028. if (pos != std::string::npos && (end == std::string::npos || pos < end)) {
  1029. value.replace(start, pos+domainLen-start, ".talk-platform.com");
  1030. }
  1031. }
  1032. }
  1033. if (statusCode == 200 && !receivedHeaders.has("X-Chx-Error")) {
  1034. std::ostringstream ss;
  1035. ss << "{\"boardCode\":\"" << "5channel_" << _board << "\",\"threadNumber\":" << _thread << ",\"commentNumber\":" << receivedHeaders.get("X-Resnum") << "}";
  1036. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  1037. if (receivedHeaders.has("Set-Cookie")) connectionDelegate->socketToClient->writeString(receivedHeaders.getFull("Set-Cookie", true));
  1038. connectionDelegate->socketToClient->writeString("Content-Type: application/json\r\n\r\n");
  1039. connectionDelegate->socketToClient->writeString(ss.str());
  1040. } else if (statusCode == 200) {
  1041. const std::string &errorHeader = receivedHeaders.get("X-Chx-Error");
  1042. if (_manageCookies && atoi(errorHeader.c_str()) == 1930) {
  1043. log_printf(1, "Acorn cookie is rotten. Sending the same request automatically...\n");
  1044. curl_easy_reset(curl);
  1045. curl_slist_free_all(headersForCurl);
  1046. continue;
  1047. }
  1048. std::ostringstream ss;
  1049. ss << "{\"error\":{\"message\":\"" << errorHeader << "\"}}";
  1050. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  1051. if (receivedHeaders.has("Set-Cookie")) connectionDelegate->socketToClient->writeString(receivedHeaders.getFull("Set-Cookie", true));
  1052. connectionDelegate->socketToClient->writeString("Content-Type: application/json\r\n\r\n");
  1053. connectionDelegate->socketToClient->writeString(ss.str());
  1054. } else {
  1055. connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  1056. statusCode = 503;
  1057. }
  1058. } else {
  1059. connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  1060. statusCode = 503;
  1061. }
  1062. curl_easy_reset(curl);
  1063. curl_slist_free_all(headersForCurl);
  1064. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  1065. if (_manageCookies) {
  1066. /* since curl_easy_reset() doesn't reset cookie engine, reset explicitly.
  1067. curl < 7.77.0 doesn't have a way to disable cookie engine,
  1068. so once enabled it will be enabled permanently. See https://github.com/curl/curl/issues/6889 */
  1069. curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); //force erase all cookies
  1070. curl_easy_setopt(curl, CURLOPT_COOKIEFILE, NULL); //force disable cookie engine
  1071. }
  1072. #endif
  1073. break;
  1074. }
  1075. return statusCode;
  1076. }