BBS2chProxyPoster.cpp 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335
  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 int bbscgi_confirmation;
  39. extern int bbscgi_fix_timestamp;
  40. extern void log_printf(int level, const char *format ...);
  41. extern double getCurrentTime(void);
  42. BBS2chProxy5chPoster::ConfirmationManager BBS2chProxy5chPoster::confirmationManager;
  43. static size_t header_callback_bbscgi(char *buffer, size_t size, size_t nitems, void *userdata)
  44. {
  45. BBS2chProxy5chPoster *poster = static_cast<BBS2chProxy5chPoster *>(userdata);
  46. BBS2chProxyConnection *conn = poster->connectionDelegate;
  47. if(poster->_status > 255) return size*nitems;
  48. else if(poster->_status == -1) {
  49. if(!memcmp(buffer,"\r\n",2)) {
  50. poster->_status = 0;
  51. }
  52. return size*nitems;
  53. }
  54. PBBS2chProxyHttpHeaderEntry parsedHeader = BBS2chProxyHttpHeaders::parse(buffer, size*nitems);
  55. if (parsedHeader) { // Looks like a header
  56. const std::string &headerName = parsedHeader->getLowercasedName();
  57. //fprintf(stderr, "%s\n", parsedHeader->getFull().c_str());
  58. if (headerName == "connection") {
  59. poster->_responseHeaders.append("Connection: Close\r\n");
  60. return size*nitems;
  61. }
  62. else if (headerName == "transfer-encoding") {
  63. if (parsedHeader->contains("chunked")) {
  64. if (allow_chunked && !conn->isClientHttp1_0) {
  65. poster->_isResponseChunked = true;
  66. poster->_responseHeaders.append(buffer, size*nitems);
  67. }
  68. return size*nitems;
  69. }
  70. }
  71. else if (headerName == "set-cookie") {
  72. poster->_hasSetCookie = true;
  73. if (conn->force5ch) {
  74. std::string value = parsedHeader->getFull(true);
  75. size_t start = value.find("domain=");
  76. if (start != std::string::npos) {
  77. start += 7;
  78. size_t end = value.find(";", start);
  79. size_t pos = value.find(".5ch.net", start);
  80. if (pos != std::string::npos && (end == std::string::npos || pos < end)) {
  81. value[start+1] = '2';
  82. poster->_responseHeaders.append(value);
  83. return size*nitems;
  84. }
  85. }
  86. }
  87. }
  88. else if (headerName == "x-chx-error") {
  89. const std::string &headerValue = parsedHeader->getValue();
  90. const char *ptr = headerValue.c_str();
  91. log_printf(0, "Posting to bbs.cgi has been cancelled. Reason: %s\n", ptr);
  92. if (*ptr == 'E') {
  93. int code = atoi(ptr+1);
  94. if ((code >= 3310 && code <= 3311) || /* inconsistent with User-Agent or something? */
  95. (code >= 3320 && code <= 3324) || /* key expiration? */
  96. (code >= 3390 && code <= 3392)) /* 3390: BAN, 3391-3392: key expiration? */
  97. {
  98. BBS2chProxyConnection::keyManager.setKey("", poster->_monaKeyForRequest, poster->_userAgentForRequest, code);
  99. if (poster->_isFirstRun) {
  100. poster->_status = 1;
  101. }
  102. }
  103. else if (code == 3000 || code == 3100 || code == 4000) { // MonaTicket is invalid
  104. std::string setCookie = "Set-Cookie: MonaTicket=; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/; domain=.";
  105. size_t pos = setCookie.size();
  106. if (conn->force5ch) setCookie += "2";
  107. else setCookie += "5";
  108. setCookie += "ch.net\r\n";
  109. poster->_responseHeaders.append(setCookie);
  110. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  111. if (poster->_manageCookies) {
  112. setCookie[pos] = '5';
  113. curl_easy_setopt(conn->curl, CURLOPT_COOKIELIST, setCookie.c_str());
  114. poster->_hasSetCookie = true;
  115. }
  116. #endif
  117. }
  118. } else if (!strncmp(ptr, "0000 Confirmation", 17) && poster->_confirmationHandlerMode > 0) {
  119. if (poster->_confirmationHandlerMode == 2 && poster->_isFirstRun) poster->_status = 3;
  120. else if (!poster->_hasAcceptEncoding) poster->_status = 4;
  121. } else if (poster->_manageCookies) {
  122. int code = atoi(ptr);
  123. if ((code == 1932) && poster->_isFirstRun) { // was 1930, 1931
  124. poster->_status = 2;
  125. }
  126. }
  127. }
  128. else if (headerName == "x-monakey") {
  129. BBS2chProxyConnection::keyManager.setKey(parsedHeader->getValue(), poster->_monaKeyForRequest, poster->_userAgentForRequest, 0);
  130. }
  131. else if (poster->_useTransparentContentEncoding && headerName == "content-encoding") {
  132. return size*nitems;
  133. }
  134. else if (poster->_useTransparentContentEncoding && headerName == "content-length") {
  135. return size*nitems;
  136. }
  137. poster->_responseHeaders.append(buffer, size*nitems);
  138. return size*nitems;
  139. }
  140. else {
  141. if (!strncasecmp("HTTP/", buffer, 5)) {
  142. const char *ptr = buffer + 5;
  143. const char *end = buffer + size*nitems;
  144. while (ptr < end && *ptr != ' ') ptr++;
  145. while (ptr < end && *ptr == ' ') ptr++;
  146. if (ptr < end) {
  147. int code = atoi(ptr);
  148. if (code == 100) {
  149. poster->_status = -1;
  150. return size*nitems;
  151. }
  152. }
  153. }
  154. poster->_responseHeaders.append(buffer, size*nitems);
  155. if (!memcmp(buffer, "\r\n", 2)) {
  156. if (poster->_status == 0 || poster->_status == 4) {
  157. conn->socketToClient->write(poster->_responseHeaders.data(), poster->_responseHeaders.size());
  158. if (poster->_status == 0) poster->_status = 256;
  159. else poster->_status = 513;
  160. } else if (poster->_status == 3) {
  161. poster->_status = 512;
  162. }
  163. else return 0;
  164. }
  165. return size*nitems;
  166. }
  167. }
  168. static size_t write_callback_proxy(char *buffer, size_t size, size_t nitems, void *userdata)
  169. {
  170. BBS2chProxy5chPoster *poster = static_cast<BBS2chProxy5chPoster *>(userdata);
  171. if (poster->_status >= 512) {
  172. poster->_responseBody.insert(poster->_responseBody.end(), buffer, buffer+size*nitems);
  173. if (poster->_status == 512) return size*nitems;
  174. }
  175. BBS2chProxyConnection *conn = poster->connectionDelegate;
  176. if(poster->_isResponseChunked) {
  177. char buf[64];
  178. snprintf(buf, 64, "%lx\r\n", size*nitems);
  179. conn->socketToClient->write(buf, strlen(buf));
  180. }
  181. size_t ret = conn->socketToClient->write(buffer, size*nitems);
  182. if(poster->_isResponseChunked) conn->socketToClient->writeString("\r\n");
  183. return ret;
  184. }
  185. static size_t header_callback_download(char *buffer, size_t size, size_t nitems, void *userdata)
  186. {
  187. BBS2chProxyHttpHeaders *headers = static_cast<BBS2chProxyHttpHeaders *>(userdata);
  188. PBBS2chProxyHttpHeaderEntry parsedHeader = BBS2chProxyHttpHeaders::parse(buffer, size*nitems);
  189. if (parsedHeader.get()) { // Looks like a header
  190. const std::string &headerName = parsedHeader->getLowercasedName();
  191. if (headerName == "connection") {
  192. if (headers) headers->add(parsedHeader->getName(), "close");
  193. return size*nitems;
  194. }
  195. else if (headerName == "transfer-encoding") {
  196. if (parsedHeader->contains("chunked")) {
  197. return size*nitems;
  198. }
  199. }
  200. else if (headerName == "content-encoding") {
  201. return size*nitems;
  202. }
  203. if (headers) headers->add(parsedHeader->getName(), parsedHeader->getValue());
  204. }
  205. else if (headers) headers->setStatusLine(buffer, size*nitems);
  206. return size*nitems;
  207. }
  208. static size_t write_callback_download(char *buffer, size_t size, size_t nitems, void *userdata)
  209. {
  210. std::vector<char> *data = static_cast<std::vector<char> *>(userdata);
  211. size_t downloaded = size*nitems;
  212. if (data) data->insert(data->end(), buffer, buffer+downloaded);
  213. return downloaded;
  214. }
  215. static bool isValidAsUTF8(const char *input, size_t inputLength) {
  216. for (int i=0; i<inputLength; i++) {
  217. unsigned char c1 = input[i];
  218. if (c1 < 0x80) continue;
  219. else if (c1 >= 0xc2 && c1 <= 0xdf) {
  220. if (i >= inputLength - 1) return false;
  221. unsigned char c2 = input[++i];
  222. if (c2 < 0x80 || c2 > 0xbf) return false;
  223. unsigned int unicode = c2 & 0xf;
  224. unicode |= (((c2 >> 4) & 0x3) | ((c1 & 0x3) << 2)) << 4;
  225. unicode |= ((c1 >> 2) & 0x7) << 8;
  226. if (unicode < 0x80 || unicode > 0x7ff) return false;
  227. }
  228. else if (c1 >= 0xe0 && c1 <= 0xef) {
  229. if (i >= inputLength - 2) return false;
  230. unsigned char c2 = input[++i];
  231. if (c2 < 0x80 || c2 > 0xbf) return false;
  232. unsigned char c3 = input[++i];
  233. if (c3 < 0x80 || c3 > 0xbf) return false;
  234. unsigned int unicode = c3 & 0xf;
  235. unicode |= (((c3 >> 4) & 0x3) | ((c2 & 0x3) << 2)) << 4;
  236. unicode |= ((c2 >> 2) & 0xf) << 8;
  237. unicode |= (c1 & 0xf) << 12;
  238. if (unicode < 0x800 || unicode > 0xffff) return false;
  239. else if (unicode >= 0xd800 && unicode <= 0xdfff) return false; /* for surrogate pairs */
  240. }
  241. else if (c1 >= 0xf0 && c1 <= 0xf7) {
  242. if (i >= inputLength - 3) return false;
  243. unsigned char c2 = input[++i];
  244. if (c2 < 0x80 || c2 > 0xbf) return false;
  245. unsigned char c3 = input[++i];
  246. if (c3 < 0x80 || c3 > 0xbf) return false;
  247. unsigned char c4 = input[++i];
  248. if (c4 < 0x80 || c4 > 0xbf) return false;
  249. unsigned int unicode = c4 & 0xf;
  250. unicode |= (((c4 >> 4) & 0x3) | ((c3 & 0x3) << 2)) << 4;
  251. unicode |= ((c3 >> 2) & 0xf) << 8;
  252. unicode |= (c2 & 0xf) << 12;
  253. unicode |= (((c2 >> 4) & 0x3) | ((c1 & 0x3) << 2)) << 16;
  254. unicode |= ((c1 >> 2) & 0x1) << 20;
  255. if (unicode < 0x10000 || unicode > 0x10ffff) return false;
  256. }
  257. else return false;
  258. }
  259. return true;
  260. }
  261. static void appendPostSignature(BBS2chProxyFormData &body, const std::string &userAgent, const std::string &monaKey, BBS2chProxyHttpHeaders &headers, bool forTalk)
  262. {
  263. char nonce[32];
  264. std::string message;
  265. if (!forTalk) {
  266. snprintf(nonce, 32, "%.3f", getCurrentTime());
  267. message.append(body["bbs"]);
  268. message.append("<>");
  269. message.append(body["key"]);
  270. message.append("<>");
  271. message.append(body["time"]);
  272. message.append("<>");
  273. message.append(body["FROM"]);
  274. message.append("<>");
  275. message.append(body["mail"]);
  276. message.append("<>");
  277. message.append(body["MESSAGE"]);
  278. message.append("<>");
  279. message.append(body["subject"]);
  280. message.append("<>");
  281. message.append(userAgent);
  282. message.append("<>");
  283. message.append(monaKey);
  284. message.append("<>");
  285. message.append("<>");
  286. message.append(nonce);
  287. } else {
  288. message.append(body["bbs"]);
  289. message.append("<>");
  290. message.append(body["key"]);
  291. message.append("<>");
  292. message.append(body["subject"]);
  293. message.append("<>");
  294. message.append(body["MESSAGE"]);
  295. message.append("<>");
  296. message.append(body["time"]);
  297. message.append("<>");
  298. message.append(body["sid"]);
  299. message.append("<>");
  300. }
  301. unsigned char digest[32];
  302. char digestStr[65];
  303. static const char *table = "0123456789abcdef";
  304. proxy2ch_HMAC_SHA256(hmacKey, strlen(hmacKey), message.data(), message.length(), digest);
  305. for (int i=0; i<32; i++) {
  306. unsigned char c = digest[i];
  307. unsigned char upper = (c >> 4) & 0xf;
  308. unsigned char lower = c & 0xf;
  309. digestStr[i*2] = table[upper];
  310. digestStr[i*2+1] = table[lower];
  311. }
  312. digestStr[64] = 0;
  313. if (!forTalk) {
  314. headers.set("X-APIKey", appKey);
  315. log_printf(1, "Appended header \"X-APIKey: %s\"\n", appKey);
  316. headers.set("X-PostSig", digestStr);
  317. log_printf(1, "Appended header \"X-PostSig: %s\"\n", digestStr);
  318. headers.set("X-PostNonce", nonce);
  319. log_printf(1, "Appended header \"X-PostNonce: %s\"\n", nonce);
  320. headers.set("X-MonaKey", monaKey);
  321. log_printf(1, "Appended header \"X-MonaKey: %s\"\n", monaKey.c_str());
  322. } else {
  323. headers.set("X-Write-Token", digestStr);
  324. log_printf(1, "Appended header \"X-Write-Token: %s\"\n", digestStr);
  325. if (monaKey != "00000000-0000-0000-0000-000000000000") {
  326. headers.set("X-Write-Key", monaKey);
  327. log_printf(1, "Appended header \"X-Write-Key: %s\"\n", monaKey.c_str());
  328. }
  329. }
  330. }
  331. static bool convertBodyToUTF8(BBS2chProxyFormData &body)
  332. {
  333. bool shouldConvertToUTF8 = true;
  334. bool shouldCheckWholeBody = true;
  335. const std::string &submit = body.get("submit");
  336. if (body.getEncoded("submit").size() != submit.size()) {
  337. if (isValidAsUTF8(submit.data(), submit.size())) {
  338. shouldConvertToUTF8 = false;
  339. }
  340. shouldCheckWholeBody = false;
  341. }
  342. if (shouldCheckWholeBody) {
  343. shouldConvertToUTF8 = false;
  344. for (BBS2chProxyFormData::iterator it = body.begin(); it != body.end(); ++it) {
  345. if (it->second.empty()) continue;
  346. if (!isValidAsUTF8(it->second.get().data(), it->second.get().size())) {
  347. shouldConvertToUTF8 = true;
  348. break;
  349. }
  350. }
  351. }
  352. if (shouldConvertToUTF8) {
  353. for (BBS2chProxyFormData::iterator it = body.begin(); it != body.end(); ++it) {
  354. if (it->second.empty()) continue;
  355. char *converted = convertShiftJISToUTF8(it->second.get().data(), it->second.get().size());
  356. if (converted) {
  357. it->second = std::string(converted);
  358. free(converted);
  359. }
  360. }
  361. }
  362. return shouldConvertToUTF8;
  363. }
  364. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  365. static void appendCurlCookiesToJar(CURL *curl, BBS2chProxyKeyManager::CookieJar &jar)
  366. {
  367. struct curl_slist *cookies = NULL;
  368. if (!curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies) && cookies) {
  369. struct curl_slist *each = cookies;
  370. jar.lock();
  371. jar.clear();
  372. while (each) {
  373. BBS2chProxyKeyManager::Cookie cookie(each->data);
  374. jar.set(cookie);
  375. each = each->next;
  376. }
  377. jar.unlock();
  378. BBS2chProxyConnection::keyManager.flushCookies();
  379. curl_slist_free_all(cookies);
  380. }
  381. }
  382. #endif
  383. static int appendUnknownFieldFromResponseHtml(const std::vector<char> &responseBody, BBS2chProxyFormData &srcBody, BBS2chProxyFormData &dstBody)
  384. {
  385. int appended = 0;
  386. const char *ptr = &responseBody.front();
  387. ptr = strstr(ptr, "<form method=\"POST\"");
  388. const char *end = ptr ? strstr(ptr, "</form>") : NULL;
  389. while (ptr && end && ptr < end) {
  390. ptr = strstr(ptr, "<input ");
  391. if (!ptr) break;
  392. ptr += 7;
  393. const char *start = ptr;
  394. const char *last = strchr(ptr, '>');
  395. if (!last) break;
  396. while (ptr < last) {
  397. if (!strncmp(ptr, "name=", 5)) break;
  398. ptr++;
  399. }
  400. ptr += 5;
  401. if (ptr >= last) continue;
  402. char endChar = ' ';
  403. if (*ptr == '"' || *ptr == '\'') {
  404. endChar = *ptr;
  405. ptr++;
  406. }
  407. std::string field;
  408. while (*ptr != endChar && *ptr != '>') field += *ptr++;
  409. if (srcBody.has(field)) continue;
  410. ptr = start;
  411. while (ptr < last) {
  412. if (!strncmp(ptr, "value=", 6)) break;
  413. ptr++;
  414. }
  415. ptr += 6;
  416. if (ptr >= last) continue;
  417. endChar = ' ';
  418. if (*ptr == '"' || *ptr == '\'') {
  419. endChar = *ptr;
  420. ptr++;
  421. }
  422. std::string value;
  423. while (*ptr != endChar && *ptr != '>') value += *ptr++;
  424. log_printf(1, "Found token-like field \"%s=%s\" in the confirmation form\n", field.c_str(), value.c_str());
  425. dstBody.append(field, value);
  426. appended++;
  427. }
  428. return appended;
  429. }
  430. #ifdef USE_LUA
  431. extern "C" {
  432. static int lua_hmacSHA256(lua_State *l)
  433. {
  434. static const char *table = "0123456789abcdef";
  435. size_t keyLength, dataLength;
  436. const char *key = luaL_checklstring(l, 1, &keyLength);
  437. const char *data = luaL_checklstring(l, 2, &dataLength);
  438. if (!key || !data) return 0;
  439. unsigned char digest[32];
  440. char digestStr[65];
  441. proxy2ch_HMAC_SHA256(key, keyLength, data, dataLength, digest);
  442. for (int i=0; i<32; i++) {
  443. unsigned char c = digest[i];
  444. unsigned char upper = (c >> 4) & 0xf;
  445. unsigned char lower = c & 0xf;
  446. digestStr[i*2] = table[upper];
  447. digestStr[i*2+1] = table[lower];
  448. }
  449. digestStr[64] = 0;
  450. lua_pushstring(l, digestStr);
  451. return 1;
  452. }
  453. static int lua_decodeURIComponent(lua_State *l)
  454. {
  455. size_t length;
  456. const char *input = luaL_checklstring(l, 1, &length);
  457. if (!input) return 0;
  458. bool decodePlus = true;
  459. if (!lua_isnoneornil(l, 2)) {
  460. decodePlus = (lua_toboolean(l, 2));
  461. }
  462. std::string output = BBS2chProxyFormData::decodeURIComponent(input, length, decodePlus);
  463. lua_pushstring(l, output.c_str());
  464. return 1;
  465. }
  466. static int lua_encodeURIComponent(lua_State *l)
  467. {
  468. size_t length;
  469. const char *input = luaL_checklstring(l, 1, &length);
  470. if (!input) return 0;
  471. bool spaceAsPlus = true;
  472. if (!lua_isnoneornil(l, 2)) {
  473. spaceAsPlus = (lua_toboolean(l, 2));
  474. }
  475. std::string output = BBS2chProxyFormData::encodeURIComponent(input, length, spaceAsPlus);
  476. lua_pushstring(l, output.c_str());
  477. return 1;
  478. }
  479. static int lua_convertShiftJISToUTF8(lua_State *l)
  480. {
  481. size_t length;
  482. const char *input = luaL_checklstring(l, 1, &length);
  483. if (!input) return 0;
  484. if (length > 0) {
  485. char *output = convertShiftJISToUTF8(input, length);
  486. if (!output) lua_pushnil(l);
  487. else {
  488. lua_pushstring(l, output);
  489. free(output);
  490. }
  491. }
  492. else lua_pushstring(l, "");
  493. return 1;
  494. }
  495. static int lua_isExpiredKey(lua_State *l)
  496. {
  497. size_t length;
  498. const char *input = luaL_checklstring(l, 1, &length);
  499. if (!input) return 0;
  500. if (BBS2chProxyConnection::keyManager.isExpired(input)) {
  501. lua_pushboolean(l, 1);
  502. }
  503. else lua_pushboolean(l, 0);
  504. return 1;
  505. }
  506. static int lua_isValidAsUTF8(lua_State *l)
  507. {
  508. size_t length;
  509. const char *input = luaL_checklstring(l, 1, &length);
  510. if (!input) return 0;
  511. lua_pushboolean(l, isValidAsUTF8(input, length));
  512. return 1;
  513. }
  514. static int lua_getMonaKey(lua_State *l)
  515. {
  516. size_t length;
  517. const char *input = luaL_checklstring(l, 1, &length);
  518. if (!input) return 0;
  519. const std::string &key = BBS2chProxyConnection::keyManager.getKey(input);
  520. lua_pushstring(l, key.c_str());
  521. return 1;
  522. }
  523. static int lua_getSID(lua_State *l)
  524. {
  525. const std::string &sid = BBS2chProxyConnection::auth.getSID();
  526. lua_pushstring(l, sid.c_str());
  527. return 1;
  528. }
  529. }
  530. #endif
  531. IBBS2chProxyPoster::IBBS2chProxyPoster(BBS2chProxyURL &url, BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  532. : _requestHeaders(headers), _requestBody(body), _verbose(0), connectionDelegate(delegate), _manageCookies(false)
  533. {
  534. prepareHeadersAndBody(url);
  535. }
  536. BBS2chProxy5chPoster::BBS2chProxy5chPoster(BBS2chProxyURL &url, BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  537. : IBBS2chProxyPoster(url, headers, body, delegate), _isFirstRun(true), _status(0), _isResponseChunked(false), _hasSetCookie(false)
  538. {
  539. _url = url.absoluteString();
  540. _confirmationHandlerMode = url.isKindOfHost("5ch.net") ? bbscgi_confirmation : 0;
  541. }
  542. BBS2chProxyTalkPoster::BBS2chProxyTalkPoster(BBS2chProxyURL &url, BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  543. : IBBS2chProxyPoster(url, headers, body, delegate)
  544. {
  545. _url = "https://api.talk-platform.com/v1/bbs.cgi";
  546. _requestBody.remove("sid");
  547. _requestHeaders.remove("Accept-Encoding");
  548. }
  549. BBS2chProxyTalkTo5chPoster::BBS2chProxyTalkTo5chPoster(BBS2chProxyURL &url, BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, BBS2chProxyConnection *delegate)
  550. : BBS2chProxy5chPoster(url, headers, body, delegate)
  551. {
  552. _confirmationHandlerMode = url.isKindOfHost("5ch.net") ? 2 : 0;
  553. _requestHeaders.remove("Accept-Encoding");
  554. }
  555. void IBBS2chProxyPoster::prepareHeadersAndBody(const BBS2chProxyURL &url)
  556. {
  557. _host = _requestHeaders.get("Host");
  558. _board = _requestBody.get("bbs");
  559. _thread = _requestBody.get("key");
  560. if (!bbscgi_postorder.empty()) {
  561. _requestBody.reorder(bbscgi_postorder);
  562. log_printf(1, "Reordered request body is: %s\n", _requestBody.toString().c_str());
  563. }
  564. _requestHeaders.remove("Host");
  565. if (user_agent) _requestHeaders.set("User-Agent", user_agent);
  566. if (url.isFamilyOf5chNet() && _requestHeaders.has("Referer")) {
  567. BBS2chProxyURL referrer(_requestHeaders.get("Referer").c_str());
  568. if (url.getScheme() != referrer.getScheme()) {
  569. referrer.setScheme(url.getScheme());
  570. _requestHeaders.set("Referer", referrer.absoluteString());
  571. }
  572. }
  573. if (!bbscgi_headers.empty()) {
  574. for (std::map<std::string, PBBS2chProxyHttpHeaderEntry>::iterator it = bbscgi_headers.getMap().begin(); it != bbscgi_headers.getMap().end(); it++) {
  575. /* we create a copy of entry here, because the original entry shouldn't be modified */
  576. PBBS2chProxyHttpHeaderEntry entry(new BBS2chProxyHttpHeaderEntry(*it->second.get()));
  577. if (!_host.empty()) {
  578. entry->replaceValue("%HOST%", _host);
  579. }
  580. if (!_board.empty()) {
  581. entry->replaceValue("%BOARD%", _board);
  582. }
  583. if (!_thread.empty()) {
  584. entry->replaceValue("%THREAD%", _thread);
  585. }
  586. _requestHeaders.set(entry);
  587. log_printf(1, "Appended custom header \"%s\"\n", entry->getFull().c_str());
  588. }
  589. }
  590. if (bbscgi_fix_timestamp && _requestBody.has("time")) {
  591. std::ostringstream ss;
  592. ss << (time(NULL) - 10);
  593. _requestBody.set("time", ss.str());
  594. log_printf(1, "Updated post timestamp to %s\n", ss.str().c_str());
  595. }
  596. }
  597. curl_slist* IBBS2chProxyPoster::prepareCurlHandle(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body, curl_slist* headersForCurl)
  598. {
  599. CURL *curl = connectionDelegate->curl;
  600. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  601. if (_manageCookies) {
  602. log_printf(1, "Cookies are managed by proxy2ch, most of existing \"Cookie: \" headers are ignored.\n");
  603. curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); //enable cookie engine explicitly
  604. curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); //erase all cookies explicitly
  605. bool hasBeCookieInJar = false;
  606. bool hasUpliftSidCookieInJar = false;
  607. BBS2chProxyKeyManager::CookieJar &jar = BBS2chProxyConnection::keyManager.getCookieJar(_userAgentForRequest, true);
  608. jar.lock();
  609. std::vector<BBS2chProxyKeyManager::Cookie> &list = jar.getList();
  610. for (std::vector<BBS2chProxyKeyManager::Cookie>::iterator it = list.begin(); it != list.end(); ++it) {
  611. if (!it->isExpired()) {
  612. curl_easy_setopt(curl, CURLOPT_COOKIELIST, it->valueInNetscapeFormat().c_str());
  613. if (!hasBeCookieInJar && (it->name == "DMDM" || it->name == "MDMD") && it->domain == ".5ch.net") {
  614. if (it->domain.size() <= _host.size() && _host.find(it->domain, _host.size()-it->domain.size()) != std::string::npos) {
  615. hasBeCookieInJar = true;
  616. }
  617. }
  618. if (!hasUpliftSidCookieInJar && it->name == "sid" && (it->domain == ".5ch.net" || it->domain == ".bbspink.com")) {
  619. if (it->domain.size() <= _host.size() && _host.find(it->domain, _host.size()-it->domain.size()) != std::string::npos) {
  620. hasUpliftSidCookieInJar = true;
  621. }
  622. }
  623. }
  624. }
  625. jar.unlock();
  626. if (headers.has("Cookie")) {
  627. const std::string &value = headers.getEntry("Cookie")->getValue();
  628. std::vector<std::string> list;
  629. size_t offset = 0;
  630. while (1) {
  631. size_t pos = value.find("; ", offset);
  632. if (pos == std::string::npos) {
  633. list.push_back(value.substr(offset));
  634. break;
  635. }
  636. list.push_back(value.substr(offset, pos - offset));
  637. offset = pos + 2;
  638. }
  639. std::string newCookie;
  640. for (std::vector<std::string>::iterator it = list.begin(); it != list.end(); ++it) {
  641. size_t pos = it->find('=');
  642. if (pos == std::string::npos) continue;
  643. std::string name = it->substr(0, pos);
  644. if ((!hasUpliftSidCookieInJar && name == "sid") || (!hasBeCookieInJar && (name == "DMDM" || name == "MDMD"))) {
  645. if (newCookie.size()) newCookie += "; ";
  646. newCookie += *it;
  647. }
  648. }
  649. if (newCookie.size()) curl_easy_setopt(curl, CURLOPT_COOKIE, newCookie.c_str());
  650. headers.remove("Cookie");
  651. }
  652. if (hasUpliftSidCookieInJar) {
  653. headers.remove("X-Ronin-Sid");
  654. body.remove("sid");
  655. }
  656. }
  657. #endif
  658. headersForCurl = headers.appendToCurlSlist(headersForCurl);
  659. if (!headers.has("Expect")) headersForCurl = curl_slist_append(headersForCurl, "Expect:");
  660. if (!headers.has("Accept")) headersForCurl = curl_slist_append(headersForCurl, "Accept:");
  661. if (curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  662. curl_easy_setopt(curl, CURLOPT_URL, _url.c_str());
  663. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  664. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  665. curl_easy_setopt(curl, CURLOPT_POST, 1L);
  666. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.toString().c_str());
  667. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  668. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  669. curl_easy_setopt(curl, CURLOPT_VERBOSE, _verbose);
  670. if (force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  671. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  672. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headersForCurl);
  673. if (!_nic.empty()) curl_easy_setopt(curl, CURLOPT_INTERFACE, _nic.c_str());
  674. if (!_forceProxy.empty()) {
  675. curl_easy_setopt(curl, CURLOPT_PROXY, _forceProxy.c_str());
  676. }
  677. else if (proxy_server) {
  678. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  679. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  680. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  681. }
  682. return headersForCurl;
  683. }
  684. void IBBS2chProxyPoster::runLuaScript(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body)
  685. {
  686. #ifdef USE_LUA
  687. lua_State* l = luaL_newstate();
  688. luaL_openlibs(l);
  689. if (luaL_loadfile(l, lua_script) != LUA_OK) {
  690. log_printf(0, "Lua: Failed to open script %s:\n %s\n", lua_script, lua_tostring(l, -1));
  691. goto lua_end;
  692. }
  693. lua_newtable(l);
  694. lua_pushcfunction(l, lua_hmacSHA256);
  695. lua_setfield(l, -2, "hmacSHA256");
  696. lua_pushcfunction(l, lua_decodeURIComponent);
  697. lua_setfield(l, -2, "decodeURIComponent");
  698. lua_pushcfunction(l, lua_encodeURIComponent);
  699. lua_setfield(l, -2, "encodeURIComponent");
  700. lua_pushcfunction(l, lua_convertShiftJISToUTF8);
  701. lua_setfield(l, -2, "convertShiftJISToUTF8");
  702. lua_pushcfunction(l, lua_isExpiredKey);
  703. lua_setfield(l, -2, "isExpiredKey");
  704. lua_pushcfunction(l, lua_isValidAsUTF8);
  705. lua_setfield(l, -2, "isValidAsUTF8");
  706. lua_pushcfunction(l, lua_getMonaKey);
  707. lua_setfield(l, -2, "getMonaKey");
  708. lua_pushcfunction(l, lua_getSID);
  709. lua_setfield(l, -2, "getSID");
  710. lua_pushstring(l, BBS2chProxyConnection::keyManager.getKey().c_str());
  711. lua_setfield(l, -2, "monaKey");
  712. lua_pushinteger(l, connectionDelegate->serverPort);
  713. lua_setfield(l, -2, "port");
  714. lua_setglobal(l, "proxy2ch");
  715. BBS2chProxyHttpHeaders::getClassDefinitionForLua(l);
  716. lua_setglobal(l, "HttpHeaders");
  717. if (lua_pcall(l, 0, 0, 0) != LUA_OK) {
  718. log_printf(0, "Lua: Failed to run script %s:\n %s\n", lua_script, lua_tostring(l, -1));
  719. goto lua_end;
  720. }
  721. lua_getglobal(l, "willSendRequestToBbsCgi");
  722. if (!lua_isfunction(l, -1)) {
  723. log_printf(0, "Lua: willSendRequestToBbsCgi function does not exist in the script\n");
  724. goto lua_end;
  725. }
  726. lua_newtable(l);
  727. headers.getUserdataForLua(l);
  728. lua_setfield(l, -2, "headers");
  729. lua_pushstring(l, body.toString().c_str());
  730. lua_setfield(l, -2, "body");
  731. lua_pushstring(l, _host.c_str());
  732. lua_pushstring(l, _board.c_str());
  733. lua_pushstring(l, _thread.c_str());
  734. if (lua_pcall(l, 4, 1, 0) != LUA_OK) {
  735. log_printf(0, "Lua: Failed to call willSendRequestToBbsCgi function:\n %s\n", lua_tostring(l, -1));
  736. goto lua_end;
  737. }
  738. if (!lua_istable(l, -1)) {
  739. log_printf(0, "Lua: A return type of willSendRequestToBbsCgi function should be a table\n");
  740. goto lua_end;
  741. }
  742. lua_pushstring(l, "body");
  743. lua_rawget(l, -2);
  744. if (lua_isstring(l, -1)) {
  745. size_t length;
  746. const char *newBody = lua_tolstring(l, -1, &length);
  747. body = BBS2chProxyFormData(newBody, length);
  748. log_printf(1, "Lua: Set request body \"%s\"\n", newBody);
  749. }
  750. lua_pop(l, 1);
  751. lua_pushstring(l, "headers");
  752. lua_rawget(l, -2);
  753. if (lua_istable(l, -1)) {
  754. headers = BBS2chProxyHttpHeaders();
  755. lua_pushnil(l);
  756. while (lua_next(l, -2)) {
  757. if (lua_isstring(l, -1) && lua_isstring(l, -2)) {
  758. const char *name = lua_tostring(l, -2);
  759. const char *value = lua_tostring(l, -1);
  760. headers.add(name, value);
  761. log_printf(1, "Lua: Set request header \"%s: %s\"\n", name, value);
  762. }
  763. lua_pop(l, 1);
  764. }
  765. }
  766. else if (lua_isuserdata(l, -1)) {
  767. if (lua_getmetatable(l, -1)) {
  768. #if LUA_VERSION_NUM > 502
  769. if (lua_getfield(l, -1, "_type") == LUA_TSTRING)
  770. #else
  771. if (lua_getfield(l, -1, "_type"), lua_type(l, -1) == LUA_TSTRING)
  772. #endif
  773. {
  774. if (!strcmp(lua_tostring(l, -1), "HttpHeaders")) {
  775. BBS2chProxyHttpHeaders *newHeaders = *((BBS2chProxyHttpHeaders **)lua_touserdata(l, -3));
  776. if (newHeaders != &headers) {
  777. headers = *newHeaders;
  778. }
  779. for (BBS2chProxyHttpHeaders::iterator it = headers.begin(); it != headers.end(); ++it) {
  780. log_printf(1, "Lua: Set request header \"%s\"\n", it->second.c_str());
  781. }
  782. }
  783. }
  784. lua_pop(l, 2);
  785. }
  786. }
  787. lua_pop(l, 1);
  788. lua_pushstring(l, "options");
  789. lua_rawget(l, -2);
  790. if (lua_istable(l, -1)) {
  791. lua_pushstring(l, "interface");
  792. lua_rawget(l, -2);
  793. if (lua_isstring(l, -1)) {
  794. _nic = std::string(lua_tostring(l, -1));
  795. }
  796. lua_pop(l, 1);
  797. lua_pushstring(l, "verbose");
  798. lua_rawget(l, -2);
  799. if (lua_isboolean(l, -1)) {
  800. _verbose = lua_toboolean(l, -1);
  801. }
  802. lua_pop(l, 1);
  803. lua_pushstring(l, "proxy");
  804. lua_rawget(l, -2);
  805. if (lua_isstring(l, -1)) {
  806. _forceProxy = std::string(lua_tostring(l, -1));
  807. }
  808. lua_pop(l, 1);
  809. lua_pushstring(l, "manageCookies");
  810. lua_rawget(l, -2);
  811. if (lua_isboolean(l, -1)) {
  812. if (curl_version_number >= 0x074d00) _manageCookies = lua_toboolean(l, -1);
  813. }
  814. lua_pop(l, 1);
  815. }
  816. lua_end:
  817. lua_close(l);
  818. #endif
  819. }
  820. void BBS2chProxy5chPoster::makeSignature(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body)
  821. {
  822. bool isPink = _host.find("bbspink.com") != std::string::npos;
  823. bool shouldSign = appKey && (((api_mode & 2) && !isPink) || (api_mode & 4));
  824. bool shouldConvertBodyToUTF8 = (bbscgi_utf8 == 1 && shouldSign) || (bbscgi_utf8 == 2);
  825. _userAgentForRequest = headers.get("User-Agent");
  826. if (headers.has("X-MonaKey")) {
  827. _monaKeyForRequest = headers.get("X-MonaKey");
  828. }
  829. if (shouldConvertBodyToUTF8 && !headers.has("X-PostSig")) {
  830. if (convertBodyToUTF8(body)) {
  831. log_printf(1, "Converted request body to UTF-8: %s\n", body.toString().c_str());
  832. }
  833. else {
  834. log_printf(1, "Request body seems already to be UTF-8, will be sent without conversion\n");
  835. }
  836. std::string contentType = headers.get("Content-Type");
  837. std::transform(contentType.begin(), contentType.end(), contentType.begin(), tolower);
  838. if (contentType.find("charset=utf-8") == std::string::npos) {
  839. headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
  840. log_printf(1, "Appended header \"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\"\n");
  841. }
  842. }
  843. if (shouldSign && (!lua_script || !headers.has("X-PostSig"))) {
  844. if (!_userAgentForRequest.empty()) {
  845. _monaKeyForRequest = BBS2chProxyConnection::keyManager.getKey(_userAgentForRequest);
  846. appendPostSignature(body, _userAgentForRequest, _monaKeyForRequest, headers, false);
  847. } else {
  848. log_printf(0, "API: User-Agent muse be set explicitly to post with API.\n");
  849. }
  850. }
  851. if (!_monaKeyForRequest.empty()) {
  852. double wait = BBS2chProxyConnection::keyManager.secondsToWaitBeforePosting(_monaKeyForRequest);
  853. if (wait > 0) {
  854. log_printf(1, "Sleeping for %.1f seconds to avoid posting too fast...\n", wait);
  855. #ifdef _WIN32
  856. Sleep(wait * 1e+3);
  857. #else
  858. usleep(wait * 1e+6);
  859. #endif
  860. }
  861. }
  862. }
  863. long BBS2chProxy5chPoster::post()
  864. {
  865. CURL *curl = connectionDelegate->curl;
  866. long statusCode = 0;
  867. for (int run=0; run<2; run++) {
  868. BBS2chProxyHttpHeaders _headers = _requestHeaders;
  869. BBS2chProxyFormData _body = _requestBody;
  870. curl_slist *headersForCurl = NULL;
  871. std::string explicitAcceptEncoding;
  872. _verbose = 0;
  873. _status = 0;
  874. _monaKeyForRequest.clear();
  875. _nic.clear();
  876. _forceProxy.clear();
  877. _isFirstRun = run == 0;
  878. _responseHeaders.clear();
  879. _responseBody.clear();
  880. _isResponseChunked = false;
  881. _hasSetCookie = false;
  882. _manageCookies = manage_bbscgi_cookies;
  883. _useTransparentContentEncoding = false;
  884. _hasAcceptEncoding = false;
  885. #ifdef USE_LUA
  886. if (lua_script) {
  887. runLuaScript(_headers, _body);
  888. }
  889. #endif
  890. if (_confirmationHandlerMode) {
  891. bool modified = confirmationManager.modifyRequestBodyIfNeeded(*this, _body);
  892. if (modified) log_printf(1, "Looks like a response to a confirmation dialog. The request body has been modified to pass the confirmation.\n");
  893. }
  894. makeSignature(_headers, _body);
  895. if (_confirmationHandlerMode > 0 && _headers.has("Accept-Encoding")) {
  896. const std::string &value = _headers.get("Accept-Encoding");
  897. if (!value.empty()) {
  898. if (BBS2chProxyConnection::acceptEncodingChecker.isSupported(value)) {
  899. explicitAcceptEncoding = value;
  900. _useTransparentContentEncoding = true;
  901. _headers.remove("Accept-Encoding");
  902. }
  903. else if (_confirmationHandlerMode == 2 && run == 0) {
  904. _useTransparentContentEncoding = true;
  905. _headers.remove("Accept-Encoding");
  906. }
  907. }
  908. else _headers.remove("Accept-Encoding");
  909. }
  910. _hasAcceptEncoding = _headers.has("Accept-Encoding");
  911. headersForCurl = prepareCurlHandle(_headers, _body, headersForCurl);
  912. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_bbscgi);
  913. curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
  914. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_proxy);
  915. curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
  916. if (_useTransparentContentEncoding) curl_easy_setopt(curl, CURLOPT_ENCODING, explicitAcceptEncoding.c_str());
  917. CURLcode res = curl_easy_perform(curl);
  918. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  919. if (_manageCookies && _hasSetCookie) {
  920. appendCurlCookiesToJar(curl, BBS2chProxyConnection::keyManager.getCookieJar(_userAgentForRequest));
  921. }
  922. #endif
  923. if (res != CURLE_OK) {
  924. if (res == CURLE_WRITE_ERROR && _status == 1) {
  925. log_printf(1, "MonaKey should be reset. Sending the same request automatically...\n");
  926. curl_easy_reset(curl);
  927. curl_slist_free_all(headersForCurl);
  928. continue;
  929. }
  930. else if (res == CURLE_WRITE_ERROR && _status == 2) {
  931. log_printf(1, "Acorn cookie is rotten. Sending the same request automatically...\n");
  932. curl_easy_reset(curl);
  933. curl_slist_free_all(headersForCurl);
  934. continue;
  935. }
  936. else {
  937. log_printf(0, "curl error: %s (%s)\n", curl_easy_strerror(res), _url.c_str());
  938. if (!_status) connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  939. statusCode = 503;
  940. }
  941. }
  942. else {
  943. if (_responseBody.size() > 0) {
  944. _responseBody.push_back('\0');
  945. PBBS2chProxyFormData body(new BBS2chProxyFormData());
  946. int appended = appendUnknownFieldFromResponseHtml(_responseBody, _requestBody, *body);
  947. if (appended) {
  948. body->append("time", _body.getEncoded("time"), true);
  949. confirmationManager.add(*this, body);
  950. }
  951. if (_confirmationHandlerMode == 2 && run == 0) {
  952. log_printf(1, "Detected a confirmation dialog. Will send the same request automatically after 1 second...\n");
  953. curl_easy_reset(curl);
  954. curl_slist_free_all(headersForCurl);
  955. #ifdef _WIN32
  956. Sleep(1 * 1e+3);
  957. #else
  958. usleep(1 * 1e+6);
  959. #endif
  960. continue;
  961. }
  962. }
  963. if (_isResponseChunked) {
  964. connectionDelegate->socketToClient->writeString("0\r\n\r\n");
  965. }
  966. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  967. }
  968. curl_easy_reset(curl);
  969. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  970. if (_manageCookies) {
  971. /* since curl_easy_reset() doesn't reset cookie engine, reset explicitly.
  972. curl < 7.77.0 doesn't have a way to disable cookie engine,
  973. so once enabled it will be enabled permanently. See https://github.com/curl/curl/issues/6889 */
  974. curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); //force erase all cookies
  975. curl_easy_setopt(curl, CURLOPT_COOKIEFILE, NULL); //force disable cookie engine
  976. }
  977. #endif
  978. curl_slist_free_all(headersForCurl);
  979. break;
  980. }
  981. return statusCode;
  982. }
  983. void BBS2chProxyTalkPoster::makeSignature(BBS2chProxyHttpHeaders &headers, BBS2chProxyFormData &body)
  984. {
  985. bool shouldSign = appKey && (api_mode & 8);
  986. bool shouldConvertBodyToUTF8 = (bbscgi_utf8 == 1 && shouldSign) || (bbscgi_utf8 == 2);
  987. _userAgentForRequest = headers.get("User-Agent");
  988. if (headers.has("X-Write-Key")) {
  989. _monaKeyForRequest = headers.get("X-Write-Key");
  990. }
  991. if (shouldConvertBodyToUTF8 && !headers.has("X-Write-Token")) {
  992. if (convertBodyToUTF8(body)) {
  993. log_printf(1, "Converted request body to UTF-8: %s\n", body.toString().c_str());
  994. }
  995. else {
  996. log_printf(1, "Request body seems already to be UTF-8, will be sent without conversion\n");
  997. }
  998. std::string contentType = headers.get("Content-Type");
  999. std::transform(contentType.begin(), contentType.end(), contentType.begin(), tolower);
  1000. if (contentType.find("charset=utf-8") == std::string::npos) {
  1001. headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
  1002. log_printf(1, "Appended header \"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\"\n");
  1003. }
  1004. }
  1005. if (shouldSign && (!lua_script || !headers.has("X-Write-Token"))) {
  1006. if (!_userAgentForRequest.empty()) {
  1007. body.append("sid", BBS2chProxyConnection::auth.getSID());
  1008. body.append("appkey", appKey);
  1009. body.append("anonymous", body.has("subject") ? "true" : "false");
  1010. _monaKeyForRequest = BBS2chProxyConnection::keyManager.getKey(_userAgentForRequest);
  1011. appendPostSignature(body, _userAgentForRequest, _monaKeyForRequest, headers, true);
  1012. } else {
  1013. log_printf(0, "API: User-Agent muse be set explicitly to post with API.\n");
  1014. }
  1015. }
  1016. if (!_monaKeyForRequest.empty()) {
  1017. double wait = BBS2chProxyConnection::keyManager.secondsToWaitBeforePosting(_monaKeyForRequest);
  1018. if (wait > 0) {
  1019. log_printf(1, "Sleeping for %.1f seconds to avoid posting too fast...\n", wait);
  1020. #ifdef _WIN32
  1021. Sleep(wait * 1e+3);
  1022. #else
  1023. usleep(wait * 1e+6);
  1024. #endif
  1025. }
  1026. }
  1027. }
  1028. long BBS2chProxyTalkPoster::post()
  1029. {
  1030. CURL *curl = connectionDelegate->curl;
  1031. long statusCode = 0;
  1032. for (int run=0; run<2; run++) {
  1033. BBS2chProxyHttpHeaders _headers = _requestHeaders;
  1034. BBS2chProxyFormData _body = _requestBody;
  1035. curl_slist *headersForCurl = NULL;
  1036. _verbose = 0;
  1037. _nic.clear();
  1038. _forceProxy.clear();
  1039. _manageCookies = false;
  1040. #ifdef USE_LUA
  1041. if (lua_script) {
  1042. runLuaScript(_headers, _body);
  1043. _manageCookies = false;
  1044. }
  1045. #endif
  1046. makeSignature(_headers, _body);
  1047. headersForCurl = prepareCurlHandle(_headers, _body, headersForCurl);
  1048. BBS2chProxyHttpHeaders receivedHeaders;
  1049. std::vector<char> receivedBody;
  1050. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_download);
  1051. curl_easy_setopt(curl, CURLOPT_HEADERDATA, &receivedHeaders);
  1052. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  1053. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &receivedBody);
  1054. curl_easy_setopt(curl, CURLOPT_ENCODING, "");
  1055. CURLcode res = curl_easy_perform(curl);
  1056. bool responseSent = false;
  1057. if (res == CURLE_OK) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  1058. if (receivedHeaders.hasNameAndValue("Content-Type", "application/json")) {
  1059. receivedBody.push_back('\0');
  1060. JSON_Value *json = json_parse_string(&receivedBody.front());
  1061. if (json && json_type(json) == JSONObject) {
  1062. JSON_Object *root = json_object(json);
  1063. const char *error = json_object_dotget_string(root, "error.message");
  1064. if (error) {
  1065. log_printf(0, "Posting to bbs.cgi has been cancelled. Reason: %s\n", error);
  1066. 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: ";
  1067. out += error;
  1068. out += "</b></font>\n</body>\n</html>";
  1069. char *outSJIS = convertUTF8ToShiftJIS(out.c_str(), out.size());
  1070. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  1071. connectionDelegate->socketToClient->writeString("Content-Type: text/html; charset=Shift_JIS\r\n\r\n");
  1072. if (outSJIS) {
  1073. connectionDelegate->socketToClient->write(outSJIS, strlen(outSJIS));
  1074. free(outSJIS);
  1075. }
  1076. statusCode = 200;
  1077. responseSent = true;
  1078. } else if (statusCode == 200) {
  1079. int resNum = json_object_get_number(root, "commentNumber");
  1080. 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>";
  1081. char *outSJIS = convertUTF8ToShiftJIS(out.c_str(), out.size());
  1082. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  1083. if (resNum) {
  1084. std::ostringstream ss;
  1085. ss << "X-Resnum: " << resNum << "\r\n";
  1086. connectionDelegate->socketToClient->writeString(ss.str());
  1087. }
  1088. connectionDelegate->socketToClient->writeString("Content-Type: text/html; charset=Shift_JIS\r\n\r\n");
  1089. if (outSJIS) {
  1090. connectionDelegate->socketToClient->write(outSJIS, strlen(outSJIS));
  1091. free(outSJIS);
  1092. }
  1093. responseSent = true;
  1094. }
  1095. }
  1096. if (json) json_value_free(json);
  1097. }
  1098. else if (receivedHeaders.has("X-Write-Key")) {
  1099. BBS2chProxyConnection::keyManager.setKey(receivedHeaders.get("X-Write-Key"), _monaKeyForRequest, _userAgentForRequest, 0);
  1100. const std::string &extendToken = receivedHeaders.get("X-Write-Key-Extend-Token");
  1101. if (run == 0) {
  1102. log_printf(1, "MonaKey has been updated. Sending the same request automatically...\n");
  1103. if (!extendToken.empty()) {
  1104. _requestHeaders.set("X-Write-Key-Extend-Token", extendToken);
  1105. }
  1106. curl_easy_reset(curl);
  1107. curl_slist_free_all(headersForCurl);
  1108. continue;
  1109. } else if (!extendToken.empty()) {
  1110. log_printf(0, "WARNING: you must send header \"X-Write-Key-Extend-Token: %s\" to complete posting.\n", extendToken.c_str());
  1111. }
  1112. 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>";
  1113. char *outSJIS = convertUTF8ToShiftJIS(out.c_str(), out.size());
  1114. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  1115. connectionDelegate->socketToClient->writeString("Content-Type: text/html; charset=Shift_JIS\r\n\r\n");
  1116. if (outSJIS) {
  1117. connectionDelegate->socketToClient->write(outSJIS, strlen(outSJIS));
  1118. free(outSJIS);
  1119. }
  1120. statusCode = 200;
  1121. responseSent = true;
  1122. }
  1123. if (!responseSent) {
  1124. log_printf(5, "bbscgi response: %s\n", receivedHeaders.getStatusLine().c_str());
  1125. for (BBS2chProxyHttpHeaders::iterator it = receivedHeaders.begin(); it != receivedHeaders.end(); ++it) {
  1126. log_printf(5, "bbscgi response: %s\n", it->second.c_str());
  1127. }
  1128. receivedBody.push_back('\0');
  1129. log_printf(5, "bbscgi response: %s\n", &receivedBody.front());
  1130. connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  1131. statusCode = 503;
  1132. }
  1133. curl_easy_reset(curl);
  1134. curl_slist_free_all(headersForCurl);
  1135. break;
  1136. }
  1137. return statusCode;
  1138. }
  1139. long BBS2chProxyTalkTo5chPoster::post()
  1140. {
  1141. CURL *curl = connectionDelegate->curl;
  1142. long statusCode = 0;
  1143. std::string confirmationTime;
  1144. for (int run=0; run<2; run++) {
  1145. BBS2chProxyHttpHeaders _headers = _requestHeaders;
  1146. BBS2chProxyFormData _body = _requestBody;
  1147. curl_slist *headersForCurl = NULL;
  1148. _verbose = 0;
  1149. _status = 0;
  1150. _monaKeyForRequest = "";
  1151. _nic.clear();
  1152. _forceProxy.clear();
  1153. _manageCookies = manage_bbscgi_cookies;
  1154. _responseBody.clear();
  1155. #ifdef USE_LUA
  1156. if (lua_script) {
  1157. runLuaScript(_headers, _body);
  1158. }
  1159. #endif
  1160. if (!confirmationTime.empty()) _body.set("time", confirmationTime);
  1161. makeSignature(_headers, _body);
  1162. headersForCurl = prepareCurlHandle(_headers, _body, headersForCurl);
  1163. BBS2chProxyHttpHeaders receivedHeaders;
  1164. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_download);
  1165. curl_easy_setopt(curl, CURLOPT_HEADERDATA, &receivedHeaders);
  1166. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  1167. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &_responseBody);
  1168. curl_easy_setopt(curl, CURLOPT_ENCODING, "");
  1169. CURLcode res = curl_easy_perform(curl);
  1170. if (res == CURLE_OK) {
  1171. const std::string &errorHeader = receivedHeaders.get("X-Chx-Error");
  1172. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  1173. if (!errorHeader.empty() && (!strncmp(errorHeader.c_str(), "E3000 ", 6) || !strncmp(errorHeader.c_str(), "E3100 ", 6) || !strncmp(errorHeader.c_str(), "E4000 ", 6))) {
  1174. std::string setCookie = "MonaTicket=; expires=Thu, 01-Jan-1970 00:00:00 GMT; path=/; domain=.5ch.net";
  1175. receivedHeaders.add("Set-Cookie", setCookie);
  1176. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  1177. if (_manageCookies) {
  1178. curl_easy_setopt(curl, CURLOPT_COOKIELIST, setCookie.c_str());
  1179. }
  1180. #endif
  1181. }
  1182. if (receivedHeaders.has("Set-Cookie")) {
  1183. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  1184. if (_manageCookies) {
  1185. appendCurlCookiesToJar(curl, BBS2chProxyConnection::keyManager.getCookieJar(_userAgentForRequest));
  1186. }
  1187. #endif
  1188. std::vector<std::string>& values = receivedHeaders.getEntry("Set-Cookie")->getValueList();
  1189. for (std::vector<std::string>::iterator it = values.begin(); it != values.end(); it++) {
  1190. std::string &value = *it;
  1191. size_t start = value.find("domain=");
  1192. if (start == std::string::npos) continue;
  1193. start += 7;
  1194. size_t end = value.find(";", start);
  1195. size_t pos = value.find(".5ch.net", start);
  1196. size_t domainLen = 8;
  1197. if (pos == std::string::npos || (end != std::string::npos && pos >= end)) {
  1198. pos = value.find(".bbspink.com", start);
  1199. domainLen = 12;
  1200. }
  1201. if (pos != std::string::npos && (end == std::string::npos || pos < end)) {
  1202. value.replace(start, pos+domainLen-start, ".talk-platform.com");
  1203. }
  1204. }
  1205. }
  1206. if (statusCode == 200 && errorHeader.empty()) {
  1207. std::ostringstream ss;
  1208. ss << "{\"boardCode\":\"" << "5channel_" << _board << "\",\"threadNumber\":" << _thread << ",\"commentNumber\":" << receivedHeaders.get("X-Resnum") << "}";
  1209. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  1210. if (receivedHeaders.has("Set-Cookie")) connectionDelegate->socketToClient->writeString(receivedHeaders.getFull("Set-Cookie", true));
  1211. connectionDelegate->socketToClient->writeString("Content-Type: application/json\r\n\r\n");
  1212. connectionDelegate->socketToClient->writeString(ss.str());
  1213. } else if (statusCode == 200) {
  1214. if (run == 0 && _manageCookies && atoi(errorHeader.c_str()) == 1932) {
  1215. log_printf(1, "Acorn cookie is rotten. Sending the same request automatically...\n");
  1216. curl_easy_reset(curl);
  1217. curl_slist_free_all(headersForCurl);
  1218. continue;
  1219. } else if (_confirmationHandlerMode && run == 0 && !strncmp(errorHeader.c_str(), "0000 Confirmation", 17)) {
  1220. log_printf(1, "Detected a confirmation dialog. Will send the same request automatically after 1 second...\n");
  1221. _responseBody.push_back('\0');
  1222. appendUnknownFieldFromResponseHtml(_responseBody, _body, _requestBody);
  1223. confirmationTime = _body.get("time");
  1224. curl_easy_reset(curl);
  1225. curl_slist_free_all(headersForCurl);
  1226. #ifdef _WIN32
  1227. Sleep(1 * 1e+3);
  1228. #else
  1229. usleep(1 * 1e+6);
  1230. #endif
  1231. continue;
  1232. }
  1233. std::ostringstream ss;
  1234. ss << "{\"error\":{\"message\":\"" << errorHeader << "\"}}";
  1235. connectionDelegate->socketToClient->sendBasicHeaders(200, "OK");
  1236. if (receivedHeaders.has("Set-Cookie")) connectionDelegate->socketToClient->writeString(receivedHeaders.getFull("Set-Cookie", true));
  1237. connectionDelegate->socketToClient->writeString("Content-Type: application/json\r\n\r\n");
  1238. connectionDelegate->socketToClient->writeString(ss.str());
  1239. } else {
  1240. connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  1241. statusCode = 503;
  1242. }
  1243. } else {
  1244. connectionDelegate->socketToClient->sendResponse(503, "Service Unavailable");
  1245. statusCode = 503;
  1246. }
  1247. curl_easy_reset(curl);
  1248. curl_slist_free_all(headersForCurl);
  1249. #if LIBCURL_VERSION_NUM >= 0x070e01 /* curl 7.14.1 or later */
  1250. if (_manageCookies) {
  1251. /* since curl_easy_reset() doesn't reset cookie engine, reset explicitly.
  1252. curl < 7.77.0 doesn't have a way to disable cookie engine,
  1253. so once enabled it will be enabled permanently. See https://github.com/curl/curl/issues/6889 */
  1254. curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL"); //force erase all cookies
  1255. curl_easy_setopt(curl, CURLOPT_COOKIEFILE, NULL); //force disable cookie engine
  1256. }
  1257. #endif
  1258. break;
  1259. }
  1260. return statusCode;
  1261. }
  1262. std::string IBBS2chProxyPoster::identifier()
  1263. {
  1264. std::string key = _host;
  1265. key += "/";
  1266. key += _board;
  1267. key += "/";
  1268. key += _thread;
  1269. return key;
  1270. }
  1271. void BBS2chProxy5chPoster::ConfirmationManager::add(IBBS2chProxyPoster &poster, PBBS2chProxyFormData body)
  1272. {
  1273. time_t now = time(NULL);
  1274. pthread_mutex_lock(&_mutex);
  1275. _cachedForms[poster.identifier()] = std::make_pair(now, body);
  1276. _lastCached = now;
  1277. pthread_mutex_unlock(&_mutex);
  1278. }
  1279. bool BBS2chProxy5chPoster::ConfirmationManager::modifyRequestBodyIfNeeded(IBBS2chProxyPoster &poster, BBS2chProxyFormData &body)
  1280. {
  1281. bool ret = false;
  1282. const std::string &key = poster.identifier();
  1283. time_t now = time(NULL);
  1284. pthread_mutex_lock(&_mutex);
  1285. if (_lastCached && now - _lastCached > 120) {
  1286. _cachedForms.clear();
  1287. _lastCached = 0;
  1288. pthread_mutex_unlock(&_mutex);
  1289. return false;
  1290. }
  1291. std::map<std::string, std::pair<time_t, PBBS2chProxyFormData> >::iterator it = _cachedForms.find(key);
  1292. if (it != _cachedForms.end()) {
  1293. if (now - it->second.first <= 120) {
  1294. PBBS2chProxyFormData cachedBody = it->second.second;
  1295. for (BBS2chProxyFormData::iterator it = cachedBody->begin(); it != cachedBody->end(); ++it) {
  1296. body.set(it->first, it->second.getEncoded(), true);
  1297. }
  1298. ret = true;
  1299. }
  1300. _cachedForms.erase(it);
  1301. }
  1302. pthread_mutex_unlock(&_mutex);
  1303. return ret;
  1304. }