BBS2chProxyConnection.cpp 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579
  1. #include <string>
  2. #include <vector>
  3. #include <map>
  4. #include <set>
  5. #include <sstream>
  6. #include <stdexcept>
  7. #include <algorithm>
  8. #include <pthread.h>
  9. #include <time.h>
  10. #include <stdlib.h>
  11. #include <string.h>
  12. #include <unistd.h>
  13. #ifdef USE_LUA
  14. #include <lua.hpp>
  15. #endif
  16. #ifdef _WIN32
  17. #include <fcntl.h>
  18. #include <winsock2.h>
  19. #include <ws2tcpip.h>
  20. #include <mswsock.h>
  21. #define CLOSESOCKET(x) closesocket(x)
  22. #define gmtime_r(a, b) gmtime_s(b, a)
  23. #else
  24. #include <sys/socket.h>
  25. #include <netinet/in.h>
  26. #include <netdb.h>
  27. #include <arpa/inet.h>
  28. #include <poll.h>
  29. #define CLOSESOCKET(x) close(x)
  30. #endif
  31. #include "BBS2chProxyConnection.h"
  32. #include "DataStorage.h"
  33. #include "hmac.h"
  34. #include "stringEncodingConverter.h"
  35. #include "BBS2chProxyRawSocket.h"
  36. #ifdef USE_MITM
  37. #include "BBS2chProxySecureSocket.h"
  38. #endif
  39. //#define DEBUG 1
  40. extern char *proxy_server;
  41. extern long proxy_port;
  42. extern long proxy_type;
  43. extern long timeout;
  44. extern char *user_agent;
  45. extern char *api_ua_dat;
  46. extern char *x_2ch_ua_dat;
  47. extern char *appKey;
  48. extern char *hmacKey;
  49. extern int allow_chunked;
  50. extern int curl_features;
  51. extern unsigned int curl_version_number;
  52. extern bool accept_https;
  53. extern int force_5chnet;
  54. extern int force_5chnet_https;
  55. extern int force_ipv4;
  56. extern char *bbsmenu_url;
  57. extern char *api_server;
  58. extern std::map<std::string, std::string> bbscgi_headers;
  59. extern int gikofix;
  60. extern CURLSH *curl_share;
  61. extern char *lua_script;
  62. extern unsigned int api_mode;
  63. extern std::vector<std::string> bbscgi_postorder;
  64. extern unsigned int bbscgi_utf8;
  65. extern int api_override;
  66. extern int direct_dat;
  67. #ifdef USE_MITM
  68. extern unsigned int mitm_mode;
  69. #endif
  70. extern void log_printf(int level, const char *format ...);
  71. #include "utils.h"
  72. BBS2chProxyKeyManager BBS2chProxyConnection::keyManager;
  73. BBS2chProxyAuth BBS2chProxyConnection::auth;
  74. static regex_t regex;
  75. static regex_t regex_kako;
  76. static regex_t regex_offlaw;
  77. static regex_t regex_api;
  78. static regex_t regex_api_auth;
  79. static bool isPinkPrefix(const std::string &prefix)
  80. {
  81. if (prefix == "aoi") return true;
  82. else if (prefix == "babiru") return true;
  83. else if (prefix == "idol") return true;
  84. else if (prefix == "kilauea") return true;
  85. else if (prefix == "mercury") return true;
  86. else if (prefix == "nasu") return true;
  87. else if (prefix == "okazu") return true;
  88. else if (prefix == "peach") return true;
  89. else if (prefix == "pele") return true;
  90. else if (prefix == "phoebe") return true;
  91. else if (prefix == "pie") return true;
  92. else if (prefix == "pink") return true;
  93. else if (prefix == "qiufen") return true;
  94. else if (prefix == "sakura01") return true;
  95. else if (prefix == "sakura02") return true;
  96. else if (prefix == "set") return true;
  97. else if (prefix == "venus") return true;
  98. else if (prefix == "vip") return true;
  99. else if (prefix == "wow") return true;
  100. else if (prefix == "yomi") return true;
  101. return false;
  102. }
  103. #ifdef USE_LUA
  104. extern "C" {
  105. static int lua_hmacSHA256(lua_State *l)
  106. {
  107. static const char *table = "0123456789abcdef";
  108. size_t keyLength, dataLength;
  109. const char *key = luaL_checklstring(l, 1, &keyLength);
  110. const char *data = luaL_checklstring(l, 2, &dataLength);
  111. if (!key || !data) return 0;
  112. unsigned char digest[32];
  113. char digestStr[65];
  114. proxy2ch_HMAC_SHA256(key, keyLength, data, dataLength, digest);
  115. for (int i=0; i<32; i++) {
  116. unsigned char c = digest[i];
  117. unsigned char upper = (c >> 4) & 0xf;
  118. unsigned char lower = c & 0xf;
  119. digestStr[i*2] = table[upper];
  120. digestStr[i*2+1] = table[lower];
  121. }
  122. digestStr[64] = 0;
  123. lua_pushstring(l, digestStr);
  124. return 1;
  125. }
  126. static int lua_decodeURIComponent(lua_State *l)
  127. {
  128. size_t length;
  129. const char *input = luaL_checklstring(l, 1, &length);
  130. if (!input) return 0;
  131. bool decodePlus = true;
  132. if (!lua_isnoneornil(l, 2)) {
  133. decodePlus = (lua_toboolean(l, 2));
  134. }
  135. std::string output = decodeURIComponent(input, length, decodePlus);
  136. lua_pushstring(l, output.c_str());
  137. return 1;
  138. }
  139. static int lua_encodeURIComponent(lua_State *l)
  140. {
  141. size_t length;
  142. const char *input = luaL_checklstring(l, 1, &length);
  143. if (!input) return 0;
  144. bool spaceAsPlus = true;
  145. if (!lua_isnoneornil(l, 2)) {
  146. spaceAsPlus = (lua_toboolean(l, 2));
  147. }
  148. std::string output = encodeURIComponent(input, length, spaceAsPlus);
  149. lua_pushstring(l, output.c_str());
  150. return 1;
  151. }
  152. static int lua_convertShiftJISToUTF8(lua_State *l)
  153. {
  154. size_t length;
  155. const char *input = luaL_checklstring(l, 1, &length);
  156. if (!input) return 0;
  157. if (length > 0) {
  158. char *output = convertShiftJISToUTF8(input, length);
  159. if (!output) lua_pushnil(l);
  160. else {
  161. lua_pushstring(l, output);
  162. free(output);
  163. }
  164. }
  165. else lua_pushstring(l, "");
  166. return 1;
  167. }
  168. static int lua_isExpiredKey(lua_State *l)
  169. {
  170. size_t length;
  171. const char *input = luaL_checklstring(l, 1, &length);
  172. if (!input) return 0;
  173. if (BBS2chProxyConnection::keyManager.isExpired(input)) {
  174. lua_pushboolean(l, 1);
  175. }
  176. else lua_pushboolean(l, 0);
  177. return 1;
  178. }
  179. static int lua_isValidAsUTF8(lua_State *l)
  180. {
  181. size_t length;
  182. const char *input = luaL_checklstring(l, 1, &length);
  183. if (!input) return 0;
  184. lua_pushboolean(l, isValidAsUTF8(input, length));
  185. return 1;
  186. }
  187. static int lua_getMonaKey(lua_State *l)
  188. {
  189. size_t length;
  190. const char *input = luaL_checklstring(l, 1, &length);
  191. if (!input) return 0;
  192. const std::string &key = BBS2chProxyConnection::keyManager.getKey(input);
  193. lua_pushstring(l, key.c_str());
  194. return 1;
  195. }
  196. }
  197. #endif
  198. void BBS2chProxyConnection::run(void * (*func)(void *))
  199. {
  200. pthread_t thread;
  201. pthread_attr_t thread_attr;
  202. pthread_attr_init(&thread_attr);
  203. pthread_attr_setdetachstate(&thread_attr , PTHREAD_CREATE_DETACHED);
  204. if(0 != pthread_create(&thread , &thread_attr , func , this))
  205. perror("pthread_create");
  206. pthread_attr_destroy(&thread_attr);
  207. }
  208. struct TunnelSockets {
  209. int sock_c;
  210. int sock_s;
  211. std::string addr;
  212. int port;
  213. };
  214. static void *tunnelMain(void *param)
  215. {
  216. TunnelSockets *sockets = (TunnelSockets *)param;
  217. char *buf = new char[16384];
  218. #ifdef _WIN32
  219. fd_set fds;
  220. int nfds = sockets->sock_c > sockets->sock_s ? sockets->sock_c + 1 : sockets->sock_s + 1;
  221. #else
  222. struct pollfd fds[2];
  223. memset(fds, 0, sizeof(fds));
  224. fds[0].fd = sockets->sock_c;
  225. fds[0].events = POLLIN;
  226. fds[1].fd = sockets->sock_s;
  227. fds[1].events = POLLIN;
  228. #endif
  229. while (1) {
  230. #ifdef _WIN32
  231. FD_ZERO(&fds);
  232. FD_SET(sockets->sock_c, &fds);
  233. FD_SET(sockets->sock_s, &fds);
  234. if (select(nfds, &fds, NULL, NULL, NULL) < 0) break;
  235. if (FD_ISSET(sockets->sock_c, &fds)) {
  236. int ret = recv(sockets->sock_c, buf, 16384, 0);
  237. if (ret > 0) send(sockets->sock_s, buf, ret, 0);
  238. else if (ret <= 0) break;
  239. }
  240. if (FD_ISSET(sockets->sock_s, &fds)) {
  241. int ret = recv(sockets->sock_s, buf, 16384, 0);
  242. if (ret > 0) send(sockets->sock_c, buf, ret, 0);
  243. else if (ret <= 0) break;
  244. }
  245. #else
  246. if (poll(fds, 2, -1) < 0) break;
  247. if (fds[0].revents & POLLIN) {
  248. int ret = recv(sockets->sock_c, buf, 16384, 0);
  249. if (ret > 0) send(sockets->sock_s, buf, ret, 0);
  250. else if (ret <= 0) break;
  251. }
  252. else if (fds[0].revents != 0) break;
  253. if (fds[1].revents & POLLIN) {
  254. int ret = recv(sockets->sock_s, buf, 16384, 0);
  255. if (ret > 0) send(sockets->sock_c, buf, ret, 0);
  256. else if (ret <= 0) break;
  257. }
  258. else if (fds[1].revents != 0) break;
  259. #endif
  260. }
  261. CLOSESOCKET(sockets->sock_c);
  262. CLOSESOCKET(sockets->sock_s);
  263. log_printf(1, "Finished tunneling to %s:%d\n", sockets->addr.c_str(), sockets->port);
  264. delete sockets;
  265. delete[] buf;
  266. return NULL;
  267. }
  268. int BBS2chProxyConnection::tunnel(const char *addr, int port)
  269. {
  270. struct sockaddr_in server;
  271. memset(&server, 0, sizeof(server));
  272. server.sin_family = AF_INET;
  273. server.sin_addr.s_addr = inet_addr(addr);
  274. server.sin_port = htons(port);
  275. if(server.sin_addr.s_addr == 0xffffffff) {
  276. struct hostent *host;
  277. host = gethostbyname(addr);
  278. if (host == NULL) {
  279. log_printf(0, "Failed to lookup hostname %s\n", addr);
  280. sendResponse(400, "Bad Request", socketToClient);
  281. return 400;
  282. }
  283. server.sin_addr.s_addr = *(unsigned int *)host->h_addr_list[0];
  284. }
  285. log_printf(1,"Tunneling connection to %s:%d\n",addr,port);
  286. int sock_s = socket(AF_INET, SOCK_STREAM, 0);
  287. if(-1 == ::connect(sock_s, (struct sockaddr *)&server, sizeof(server))) {
  288. perror("connect");
  289. sendResponse(400, "Bad Request", socketToClient);
  290. return 400;
  291. }
  292. send(sock_c, "HTTP/1.1 200 Connection established\r\n\r\n", 39, 0);
  293. TunnelSockets *sockets = new TunnelSockets();
  294. sockets->sock_c = sock_c;
  295. sockets->sock_s = sock_s;
  296. sockets->addr = addr;
  297. sockets->port = port;
  298. pthread_t thread;
  299. if(0 != pthread_create(&thread, NULL, tunnelMain, sockets))
  300. perror("pthread_create");
  301. pthread_detach(thread);
  302. return 0;
  303. }
  304. void BBS2chProxyConnection::connect(void)
  305. {
  306. char method[32], url[1024], protocol[32];
  307. int i;
  308. char *buf, *ptr;
  309. unsigned int datProxyMode = 0; // 0: no dat, 1: read.cgi or API, 2: force API, 3: kakolog
  310. regmatch_t match[7];
  311. long statusCode = 0;
  312. BBS2chProxyURL baseURL;
  313. BBS2chProxyHttpHeaders requestHeaders;
  314. BBS2chThreadIdentifier threadIdentifier;
  315. socketToClient = new BBS2chProxyRawSocket(sock_c);
  316. buf = (char *)malloc(16384);
  317. if(!buf) goto end;
  318. beginHandleRequest:
  319. ptr = buf;
  320. if(!socketToClient->readLine(buf, 1024)) {
  321. sendResponse(400, "Bad Request", socketToClient);
  322. statusCode = 400;
  323. goto end;
  324. }
  325. i=0;
  326. while(*ptr != ' ' && *ptr != 0 && i < 32) method[i++] = *ptr++;
  327. if(*ptr == 0 || i == 32) {
  328. sendResponse(400, "Bad Request", socketToClient);
  329. statusCode = 400;
  330. goto end;
  331. }
  332. method[i] = 0;
  333. ptr++;
  334. i=0;
  335. while(*ptr != ' ' && *ptr != 0 && i < 1024) url[i++] = *ptr++;
  336. if(*ptr == 0 || i == 1024) {
  337. sendResponse(400, "Bad Request", socketToClient);
  338. statusCode = 400;
  339. goto end;
  340. }
  341. url[i] = 0;
  342. ptr++;
  343. i=0;
  344. while(*ptr != '\r' && *ptr != '\n' && *ptr != 0 && i < 32) protocol[i++] = *ptr++;
  345. if(*ptr == 0 || i == 32) {
  346. sendResponse(400, "Bad Request", socketToClient);
  347. statusCode = 400;
  348. goto end;
  349. }
  350. protocol[i] = 0;
  351. if(!strncasecmp(protocol,"HTTP/1.0",8)) {
  352. isClientHttp1_0 = true;
  353. }
  354. else isClientHttp1_0 = false;
  355. log_printf(1, "Received %s %s %s\n",method,url,protocol);
  356. if(strcasecmp(method,"GET") && strcasecmp(method,"POST") && strcasecmp(method,"HEAD") && strcasecmp(method,"CONNECT") && strcasecmp(method,"PUT") && strcasecmp(method, "OPTIONS")) {
  357. sendResponse(400, "Bad Request", socketToClient);
  358. statusCode = 400;
  359. goto end;
  360. }
  361. if(!url[0]) {
  362. sendResponse(400, "Bad Request", socketToClient);
  363. statusCode = 400;
  364. goto end;
  365. }
  366. if(strncasecmp(protocol,"HTTP",4)) {
  367. sendResponse(400, "Bad Request", socketToClient);
  368. statusCode = 400;
  369. goto end;
  370. }
  371. if(!strcasecmp(method,"CONNECT")) {
  372. if(!accept_https || baseURL.isValid()) {
  373. sendResponse(400, "Bad Request", socketToClient);
  374. statusCode = 400;
  375. goto end;
  376. }
  377. while(socketToClient->readLine(buf, 16384)) {
  378. if(!strcmp("\r\n",buf)) break;
  379. }
  380. int port = 443;
  381. char *ptr = strchr(url, ':');
  382. if(ptr) {
  383. *ptr = 0;
  384. port = atoi(ptr+1);
  385. }
  386. #ifdef USE_MITM
  387. bool useMITM = false;
  388. if (mitm_mode) {
  389. baseURL = BBS2chProxyURL("https", url);
  390. if (mitm_mode == 2) useMITM = true;
  391. else if (mitm_mode == 1 && baseURL.isFamilyOf5chNet()) useMITM = true;
  392. }
  393. if (useMITM) {
  394. socketToClient->writeString("HTTP/1.1 200 Connection established\r\n\r\n");
  395. if (port == 80) {
  396. baseURL.setScheme("http");
  397. goto beginHandleRequest;
  398. }
  399. else {
  400. try {
  401. BBS2chProxySecureSocket *secureSocket = new BBS2chProxySecureSocket(sock_c, url);
  402. delete socketToClient;
  403. socketToClient = secureSocket;
  404. if (port != 443) baseURL.setPort(port);
  405. isHttps = true;
  406. goto beginHandleRequest;
  407. } catch (const std::runtime_error& e) {
  408. log_printf(0, "%s\n", e.what());
  409. sendResponse(400, "Bad Request", socketToClient);
  410. statusCode = 400;
  411. goto end;
  412. }
  413. }
  414. }
  415. else
  416. #endif
  417. {
  418. statusCode = tunnel(url, port);
  419. /* if a return value is non-zero, tunnel function failed to establish a tunnelling connection */
  420. if (statusCode == 0) {
  421. delete socketToClient;
  422. socketToClient = NULL;
  423. }
  424. goto end;
  425. }
  426. }
  427. #if USE_MITM
  428. if (baseURL.isValid()) {
  429. requestURL = BBS2chProxyURL(baseURL, url);
  430. log_printf(1, "Running as MITM proxy for %s\n", requestURL.absoluteString().c_str());
  431. } else
  432. #endif
  433. requestURL = BBS2chProxyURL(url);
  434. if (!requestURL.isHttp()) {
  435. sendResponse(400, "Bad Request", socketToClient);
  436. statusCode = 400;
  437. goto end;
  438. }
  439. if (force_5chnet) {
  440. if (requestURL.getHost() != "menu.2ch.net" && requestURL.replaceHost("2ch.net", "5ch.net")) {
  441. force5ch = true;
  442. log_printf(1, "Detected *.2ch.net URL, changed target URL to %s\n", requestURL.absoluteString().c_str());
  443. }
  444. }
  445. /* parse request headers */
  446. while (socketToClient->readLine(buf, 16384)) {
  447. if (!strcmp("\r\n",buf)) break;
  448. requestHeaders.add(buf);
  449. }
  450. if (requestHeaders.hasNameAndValue("Transfer-Encoding", "chunked")) {
  451. isClientChunked = true;
  452. }
  453. else if (requestHeaders.has("Content-Length")) {
  454. content_length = atoi(requestHeaders.get("Content-Length").c_str());
  455. }
  456. if (requestHeaders.has("Expect")) {
  457. if (!strcasecmp(requestHeaders.get("Expect").c_str(), "100-continue") && !isClientHttp1_0) {
  458. log_printf(1, "Received Expect: 100-continue header, sending 100 Continue response to the client\n");
  459. socketToClient->writeString("HTTP/1.1 100 Continue\r\n\r\n");
  460. }
  461. }
  462. if (regexec(&regex, requestURL.absoluteString().c_str(), 6, match, 0) != REG_NOMATCH) {
  463. const std::string &url = requestURL.absoluteString();
  464. threadIdentifier.hostPrefix = url.substr(match[1].rm_so, match[1].rm_eo - match[1].rm_so);
  465. threadIdentifier.host = requestURL.getHost();
  466. threadIdentifier.board = url.substr(match[4].rm_so, match[4].rm_eo - match[4].rm_so);
  467. threadIdentifier.key = url.substr(match[5].rm_so, match[5].rm_eo - match[5].rm_so);
  468. if (direct_dat) {
  469. long datStatus;
  470. bool foundAsKakolog;
  471. BBS2chProxyURL newURL = getRawDatURLAndStatus(threadIdentifier, requestHeaders, false, &datStatus, &foundAsKakolog);
  472. if (datStatus == 200) {
  473. directDatDownloading = 1;
  474. if (foundAsKakolog) {
  475. requestURL = newURL;
  476. }
  477. } else {
  478. if (datStatus == 404) {
  479. if (!requestHeaders.has("Range") && !requestURL.hostStartsWith("headline.")) datProxyMode = 1;
  480. else {
  481. statusCode = 302;
  482. sendBasicHeaders(302, "Found", socketToClient);
  483. if (0 >= socketToClient->writeString("Location: http://www2.2ch.net/live.html\r\n")) goto end;
  484. if (0 >= socketToClient->writeString("\r\n")) goto end;
  485. goto end;
  486. }
  487. } else {
  488. statusCode = 503;
  489. sendResponse(503, "Service Unavailable", socketToClient);
  490. goto end;
  491. }
  492. }
  493. }
  494. else if ((appKey && (api_mode & 1)) || !requestURL.hostStartsWith("headline.")) datProxyMode = 1;
  495. }
  496. else if (regexec(&regex_kako, requestURL.absoluteString().c_str(), 7, match, 0) != REG_NOMATCH) {
  497. const std::string &url = requestURL.absoluteString();
  498. threadIdentifier.hostPrefix = url.substr(match[1].rm_so, match[1].rm_eo - match[1].rm_so);
  499. threadIdentifier.host = requestURL.getHost();
  500. threadIdentifier.board = url.substr(match[4].rm_so, match[4].rm_eo - match[4].rm_so);
  501. threadIdentifier.key = url.substr(match[6].rm_so, match[6].rm_eo - match[6].rm_so);
  502. if (direct_dat) {
  503. long datStatus;
  504. bool foundAsKakolog;
  505. BBS2chProxyURL newURL = getRawDatURLAndStatus(threadIdentifier, requestHeaders, true, &datStatus, &foundAsKakolog);
  506. if (datStatus == 200) {
  507. directDatDownloading = 1;
  508. requestURL = newURL;
  509. } else {
  510. if (!requestHeaders.has("Range")) datProxyMode = 3;
  511. else {
  512. statusCode = 503;
  513. sendResponse(503, "Service Unavailable", socketToClient);
  514. goto end;
  515. }
  516. }
  517. }
  518. else datProxyMode = 3;
  519. }
  520. else if (regexec(&regex_offlaw, requestURL.absoluteString().c_str(), 5, match, 0) != REG_NOMATCH) {
  521. const char *tmp = requestURL.absoluteString().c_str();
  522. const char *thread = strstr(tmp, "key=");
  523. if (thread) {
  524. match[6].rm_so = thread+4-tmp;
  525. match[6].rm_eo = thread+4-tmp;
  526. const char *ptr = thread+4;
  527. while (*ptr != '&' && *ptr != 0) {
  528. ptr++;
  529. match[6].rm_eo++;
  530. }
  531. if (match[6].rm_so != match[6].rm_eo) {
  532. const std::string &url = requestURL.absoluteString();
  533. threadIdentifier.hostPrefix = url.substr(match[1].rm_so, match[1].rm_eo - match[1].rm_so);
  534. threadIdentifier.host = requestURL.getHost();
  535. threadIdentifier.board = url.substr(match[4].rm_so, match[4].rm_eo - match[4].rm_so);
  536. threadIdentifier.key = url.substr(match[6].rm_so, match[6].rm_eo - match[6].rm_so);
  537. if (direct_dat) {
  538. long datStatus;
  539. bool foundAsKakolog;
  540. BBS2chProxyURL newURL = getRawDatURLAndStatus(threadIdentifier, requestHeaders, true, &datStatus, &foundAsKakolog);
  541. if (datStatus == 200) {
  542. directDatDownloading = 1;
  543. requestURL = newURL;
  544. } else {
  545. if (!requestHeaders.has("Range")) datProxyMode = 3;
  546. else {
  547. statusCode = 503;
  548. sendResponse(503, "Service Unavailable", socketToClient);
  549. goto end;
  550. }
  551. }
  552. }
  553. else datProxyMode = 3;
  554. }
  555. }
  556. }
  557. else if (api_override) {
  558. if (regexec(&regex_api, requestURL.absoluteString().c_str(), 5, match, 0) != REG_NOMATCH) {
  559. const std::string &url = requestURL.absoluteString();
  560. threadIdentifier.hostPrefix = url.substr(match[2].rm_so, match[2].rm_eo - match[2].rm_so);
  561. threadIdentifier.host = threadIdentifier.hostPrefix;
  562. threadIdentifier.host += isPinkPrefix(threadIdentifier.hostPrefix) ? ".bbspink.com" : ".5ch.net";
  563. threadIdentifier.board = url.substr(match[3].rm_so, match[3].rm_eo - match[3].rm_so);
  564. threadIdentifier.key = url.substr(match[4].rm_so, match[4].rm_eo - match[4].rm_so);
  565. if (direct_dat) {
  566. long datStatus;
  567. bool foundAsKakolog;
  568. BBS2chProxyURL newURL = getRawDatURLAndStatus(threadIdentifier, requestHeaders, false, &datStatus, &foundAsKakolog);
  569. if (datStatus == 200) {
  570. directDatDownloading = 2;
  571. strcpy(method, "GET");
  572. requestURL = newURL;
  573. requestHeaders.set("Host", newURL.getHost());
  574. requestHeaders.remove("Content-Length");
  575. requestHeaders.remove("Content-Type");
  576. } else {
  577. if (datStatus == 404) {
  578. if (!requestHeaders.has("Range") && !requestURL.hostStartsWith("headline.")) datProxyMode = 3;
  579. else {
  580. statusCode = 200;
  581. sendBasicHeaders(200, "OK", socketToClient);
  582. if (0 >= socketToClient->writeString("Thread-Status: 8\r\n")) goto end;
  583. if (0 >= socketToClient->writeString("User-Status: 3\r\n\r\n")) goto end;
  584. goto end;
  585. }
  586. } else {
  587. statusCode = 503;
  588. sendResponse(503, "Service Unavailable", socketToClient);
  589. goto end;
  590. }
  591. }
  592. } else if (appKey && !(api_mode & 1)) {
  593. datProxyMode = 2;
  594. } else {
  595. datProxyMode = 3;
  596. }
  597. if (datProxyMode == 3) {
  598. requestHeaders.set("X-Proxy2ch-API-Bypass", "1");
  599. }
  600. }
  601. else if (regexec(&regex_api_auth, requestURL.absoluteString().c_str(), 2, match, 0) != REG_NOMATCH) {
  602. /* return dummy response immediately */
  603. log_printf(1, "Returning dummy response because API overriding is enabled\n");
  604. statusCode = 200;
  605. sendBasicHeaders(200, "OK", socketToClient);
  606. if (0 >= socketToClient->writeString("Content-Type: text/plain\r\n")) goto end;
  607. if (0 >= socketToClient->writeString("Content-Length: 203\r\n\r\n")) goto end;
  608. if (0 >= socketToClient->writeString("SESSION-ID=Monazilla/1.00:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) goto end;
  609. goto end;
  610. }
  611. }
  612. if (datProxyMode) {
  613. if (!appKey || (datProxyMode == 1 && !(api_mode & 1)) || datProxyMode == 3) {
  614. log_printf(1, "Retrieving thread via read.cgi...\n");
  615. threadKey = threadIdentifier.host;
  616. threadKey += '/';
  617. threadKey += threadIdentifier.board;
  618. threadKey += '/';
  619. threadKey += threadIdentifier.key;
  620. std::string targetURL = (force_5chnet_https || isHttps) ? "https://" : "http://";
  621. targetURL += threadIdentifier.host;
  622. targetURL += "/test/read.cgi/";
  623. targetURL += threadIdentifier.board;
  624. targetURL += '/';
  625. targetURL += threadIdentifier.key;
  626. targetURL += '/';
  627. if (force_5chnet_https) isHttps = true;
  628. statusCode = datProxy(targetURL.c_str(), method, requestHeaders);
  629. }
  630. else {
  631. log_printf(1, "Retrieving thread via API...\n");
  632. std::string targetURL = "https://";
  633. targetURL += api_server;
  634. targetURL += "/v1/";
  635. targetURL += threadIdentifier.hostPrefix;
  636. targetURL += '/';
  637. targetURL += threadIdentifier.board;
  638. targetURL += '/';
  639. targetURL += threadIdentifier.key;
  640. isHttps = true;
  641. statusCode = datProxyAPI(targetURL.c_str(), method, requestHeaders);
  642. }
  643. }
  644. else {
  645. if (force_5chnet_https && !isHttps && requestURL.isFamilyOf5chNet()) {
  646. requestURL.setScheme("https");
  647. isHttps = true;
  648. log_printf(1, "The host %s is 5ch.net family, connecting with HTTPS\n", requestURL.getHost().c_str());
  649. }
  650. if (bbsmenu_url && requestURL.equals(BBS2chProxyURL(bbsmenu_url), true)) {
  651. log_printf(1, "Running as a BBS menu proxy...\n");
  652. statusCode = bbsmenuProxy(requestURL.absoluteString().c_str(), method, requestHeaders);
  653. }
  654. else {
  655. bool isPostRequest = !strcasecmp(method, "POST");
  656. bool isPutRequest = !strcasecmp(method, "PUT");
  657. if (isPostRequest && requestURL.isFamilyOf5chNet() && requestURL.pathStartsWith("/test/bbs.cgi")) bbscgi = true;
  658. if (bbscgi) log_printf(1, "Looks like a request to bbs.cgi, will be modified before sending...\n");
  659. else if (directDatDownloading) log_printf(1, "Downloading .dat directly from %s...\n", requestURL.absoluteString().c_str());
  660. else log_printf(1, "Not a notable request, will be forwarded to server...\n");
  661. if (force_5chnet) {
  662. if (requestHeaders.has("Host")) {
  663. std::string host = requestHeaders.get("Host");
  664. size_t pos = host.find("2ch.net");
  665. if (pos != std::string::npos && pos+7 == host.length()) {
  666. if (pos == 0 || host[pos-1] == '.') {
  667. host.replace(pos, 1, "5");
  668. requestHeaders.set("Host", host);
  669. }
  670. }
  671. }
  672. if (bbscgi && requestHeaders.has("Referer")) {
  673. std::string referrer = requestHeaders.get("Referer");
  674. size_t pos = referrer.find("2ch.net");
  675. if (pos != std::string::npos) {
  676. if (pos == 0 || referrer[pos-1] == '.') {
  677. referrer.replace(pos, 1, "5");
  678. requestHeaders.set("Referer", referrer);
  679. }
  680. }
  681. }
  682. }
  683. requestHeaders.remove("Connection");
  684. if (user_agent) requestHeaders.remove("User-Agent");
  685. if (bbscgi && (content_length > 0 || isClientChunked)) {
  686. bool isNotFormURLEncoded = false;
  687. if (requestHeaders.has("Content-Type") && requestHeaders.get("Content-Type").find("application/x-www-form-urlencoded") == std::string::npos) {
  688. isNotFormURLEncoded = true;
  689. }
  690. if (!isNotFormURLEncoded) {
  691. requestHeaders.remove("Content-Length");
  692. if (!bbscgi_headers.empty()) {
  693. for (std::map<std::string, std::string>::iterator it = bbscgi_headers.begin(); it != bbscgi_headers.end(); it++) {
  694. if (requestHeaders.has(it->first)) {
  695. log_printf(1, "Ignoring header \"%s\" because custom header will be appended\n", it->first.c_str());
  696. requestHeaders.remove(it->first);
  697. }
  698. }
  699. }
  700. char *postdata = NULL;
  701. if (isClientChunked) {
  702. content_length = readChunkedBodyIntoBuffer(&postdata, socketToClient);
  703. requestHeaders.remove("Transfer-Encoding");
  704. }
  705. else {
  706. postdata = (char *)calloc(content_length+1, 1);
  707. content_length = socketToClient->read(postdata, content_length);
  708. }
  709. if (gikofix) {
  710. char *ptr = postdata+content_length-1;
  711. while (ptr >= postdata && (*ptr == '\r' || *ptr == '\n')) {
  712. *ptr-- = 0;
  713. }
  714. }
  715. curl_slist *headersForCurl = NULL;
  716. headersForCurl = requestHeaders.appendToCurlSlist(headersForCurl);
  717. statusCode = bbsCgiProxy(requestURL.absoluteString().c_str(), requestHeaders, postdata);
  718. free(postdata);
  719. curl_slist_free_all(headersForCurl);
  720. goto end;
  721. }
  722. }
  723. curl_slist *headersForCurl = NULL;
  724. headersForCurl = requestHeaders.appendToCurlSlist(headersForCurl);
  725. if (!requestHeaders.has("Expect")) {
  726. headersForCurl = curl_slist_append(headersForCurl, "Expect:");
  727. }
  728. if(curl) {
  729. CURLcode res;
  730. if(curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  731. curl_easy_setopt(curl, CURLOPT_URL, requestURL.absoluteString().c_str());
  732. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  733. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  734. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_proxy);
  735. curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
  736. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_proxy);
  737. curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
  738. if(content_length) {
  739. /* set Content-Length explicitly via API to work properly with curl >= 7.66.0 */
  740. if(isPostRequest)
  741. curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, content_length);
  742. else if(isPutRequest)
  743. curl_easy_setopt(curl, CURLOPT_INFILESIZE, content_length);
  744. }
  745. curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback_proxy);
  746. curl_easy_setopt(curl, CURLOPT_READDATA, this);
  747. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  748. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  749. //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
  750. if(force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  751. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  752. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headersForCurl);
  753. if(user_agent) {
  754. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  755. }
  756. if(isPostRequest) {
  757. curl_easy_setopt(curl, CURLOPT_POST, 1L);
  758. }
  759. else if(isPutRequest) {
  760. curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
  761. }
  762. else if(!strcasecmp(method, "HEAD")) {
  763. curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
  764. }
  765. else if(!strcasecmp(method, "OPTIONS")) {
  766. curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
  767. }
  768. if(proxy_server) {
  769. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  770. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  771. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  772. }
  773. res = curl_easy_perform(curl);
  774. if(res != CURLE_OK) {
  775. log_printf(0, "curl error: %s (%s)\n", curl_easy_strerror(res), requestURL.absoluteString().c_str());
  776. if(!status) sendResponse(503, "Service Unavailable", socketToClient);
  777. statusCode = 503;
  778. }
  779. else {
  780. if(isResponseChunked) {
  781. socketToClient->writeString("0\r\n\r\n");
  782. }
  783. curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &statusCode);
  784. }
  785. curl_easy_reset(curl);
  786. }
  787. curl_slist_free_all(headersForCurl);
  788. }
  789. }
  790. end:
  791. if(statusCode) log_printf(1, "Returned status code %d to client\n",statusCode);
  792. if(buf) free(buf);
  793. if(socketToClient) socketToClient->close();
  794. }
  795. int BBS2chProxyConnection::datProxy(const char *url, const char *method, BBS2chProxyHttpHeaders &requestHeaders)
  796. {
  797. DataStorage *html = NULL;
  798. long statusCode = 0;
  799. long rangeStart = 0, rangeEnd = 0;
  800. time_t lastModified = 0;
  801. time_t ifModifiedSince = 0;
  802. char *buf = (char *)malloc(16384);
  803. if(!buf) goto last;
  804. if(requestHeaders.has("Range")) {
  805. std::string value = requestHeaders.get("Range");
  806. if(value.find("bytes=") == 0 && value.find(",") == std::string::npos) {
  807. char *ptr = (char *)value.c_str() + 6;
  808. if(*ptr == '-') {
  809. rangeStart = atoi(ptr);
  810. }
  811. else {
  812. rangeStart = strtol(ptr, &ptr, 10);
  813. if(*ptr == '-') ptr++;
  814. if(*ptr && *ptr != '\r') {
  815. rangeEnd = strtol(ptr, NULL, 10);
  816. if(rangeEnd && rangeStart > rangeEnd) {
  817. sendResponse(416, "Requested range not satisfiable", socketToClient);
  818. statusCode = 416;
  819. goto last;
  820. }
  821. }
  822. }
  823. //fprintf(stderr, "range=%ld-%ld\n",rangeStart,rangeEnd);
  824. }
  825. else {
  826. sendResponse(416, "Requested range not satisfiable", socketToClient);
  827. statusCode = 416;
  828. goto last;
  829. }
  830. }
  831. if(requestHeaders.has("If-Modified-Since")) {
  832. struct tm time_ = {};
  833. strptime(requestHeaders.get("If-Modified-Since").c_str(), httpTimestampFmt, &time_);
  834. ifModifiedSince = mktime(&time_);
  835. }
  836. if(rangeStart > 0) {
  837. PBBS2chProxyThreadInfo info;
  838. pthread_mutex_lock(mutex);
  839. BBS2chProxyThreadCache::iterator it = threadCache->find(threadKey);
  840. if(it != threadCache->end()) {
  841. info = it->second;
  842. }
  843. pthread_mutex_unlock(mutex);
  844. log_printf(5,"range request from %ld bytes\n",rangeStart);
  845. if(info) {
  846. int from = info->lastResNum;
  847. int alreadyRead = info->cachedSize;
  848. int lastResLength = info->cachedData->length;
  849. log_printf(5,"hit %s: cached %d bytes, last res size %d\n",threadKey.c_str(),alreadyRead,lastResLength);
  850. if(rangeStart <= alreadyRead && rangeStart >= alreadyRead - lastResLength) {
  851. if(curl) {
  852. CURLcode res;
  853. DataStorage *dat = new DataStorage();
  854. log_printf(5,"partial access from res num %d\n",from);
  855. snprintf(buf,16384,"%s%d-n",url,from);
  856. if(curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  857. curl_easy_setopt(curl, CURLOPT_URL, buf);
  858. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  859. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  860. curl_easy_setopt(curl, CURLOPT_ENCODING, "");
  861. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  862. curl_easy_setopt(curl, CURLOPT_WRITEDATA, dat);
  863. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  864. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  865. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  866. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  867. if(force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  868. if(proxy_server) {
  869. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  870. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  871. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  872. }
  873. if(user_agent) {
  874. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  875. }
  876. else if(requestHeaders.has("User-Agent")) {
  877. curl_easy_setopt(curl, CURLOPT_USERAGENT, requestHeaders.get("User-Agent").c_str());
  878. }
  879. res = curl_easy_perform(curl);
  880. if(res == CURLE_OK) {
  881. curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &statusCode);
  882. curl_easy_reset(curl);
  883. if(statusCode == 200 && dat->length) {
  884. DataStorage *updated = html2dat(dat, from, &lastModified, true);
  885. if(ifModifiedSince && lastModified && updated && updated->length == lastResLength) {
  886. struct tm time_ = {};
  887. gmtime_r(&lastModified,&time_);
  888. time_t tmp = mktime(&time_);
  889. if(ifModifiedSince >= tmp) {
  890. sendResponse(304, "Not Modified", socketToClient);
  891. log_printf(5,"not modified!\n");
  892. delete updated;
  893. delete dat;
  894. statusCode = 304;
  895. goto last;
  896. }
  897. }
  898. if(updated && updated->length && updated->length >= lastResLength) {
  899. html = new DataStorage(alreadyRead - lastResLength);
  900. html->appendBytes(updated->bytes, updated->length);
  901. if(!rangeEnd) rangeEnd = html->length - 1;
  902. if(rangeStart > rangeEnd) {
  903. sendResponse(416, "Requested range not satisfiable", socketToClient);
  904. delete updated;
  905. delete dat;
  906. statusCode = 416;
  907. goto last;
  908. }
  909. statusCode = 206;
  910. log_printf(5,"cache hit; reconstructed data length:%ld\n",(long)html->length);
  911. }
  912. else {
  913. log_printf(5,"cache misshit?\n");
  914. sendResponse(416, "Requested range not satisfiable", socketToClient);
  915. delete updated;
  916. delete dat;
  917. statusCode = 416;
  918. goto last;
  919. }
  920. delete updated;
  921. }
  922. }
  923. else {
  924. log_printf(0,"curl error: %s (%s)\n",curl_easy_strerror(res),buf);
  925. curl_easy_reset(curl);
  926. }
  927. delete dat;
  928. if(html) goto resp;
  929. }
  930. }
  931. else {
  932. log_printf(5,"invalid cache contents\n");
  933. pthread_mutex_lock(mutex);
  934. BBS2chProxyThreadCache::iterator it = threadCache->find(threadKey);
  935. if(it != threadCache->end()) {
  936. threadCache->erase(it);
  937. }
  938. pthread_mutex_unlock(mutex);
  939. }
  940. }
  941. }
  942. {
  943. if(curl) {
  944. CURLcode res;
  945. DataStorage *dat = new DataStorage();
  946. if(curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  947. #if 1
  948. /* This is an ad-hoc fix against malformed read.cgi behaviors (e.g. krsw server) */
  949. snprintf(buf, 16384, "%s1-", url);
  950. curl_easy_setopt(curl, CURLOPT_URL, buf);
  951. #else
  952. curl_easy_setopt(curl, CURLOPT_URL, url);
  953. #endif
  954. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  955. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  956. curl_easy_setopt(curl, CURLOPT_ENCODING, "");
  957. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  958. curl_easy_setopt(curl, CURLOPT_WRITEDATA, dat);
  959. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  960. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  961. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  962. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  963. if(force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  964. if(proxy_server) {
  965. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  966. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  967. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  968. }
  969. if(user_agent) {
  970. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  971. }
  972. else if(requestHeaders.has("User-Agent")) {
  973. curl_easy_setopt(curl, CURLOPT_USERAGENT, requestHeaders.get("User-Agent").c_str());
  974. }
  975. res = curl_easy_perform(curl);
  976. if(res != CURLE_OK) {
  977. log_printf(0,"curl error: %s (%s)\n",curl_easy_strerror(res),url);
  978. sendResponse(503, "Service Unavailable", socketToClient);
  979. curl_easy_reset(curl);
  980. delete dat;
  981. statusCode = 503;
  982. goto last;
  983. }
  984. curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &statusCode);
  985. curl_easy_reset(curl);
  986. if(statusCode == 200) {
  987. html = html2dat(dat, 1, &lastModified, false);
  988. }
  989. delete dat;
  990. }
  991. if(!html || !html->length) {
  992. sendResponse(503, "Service Unavailable", socketToClient);
  993. statusCode = 503;
  994. goto last;
  995. }
  996. if((rangeStart || rangeEnd) && html && html->length) {
  997. if(!rangeEnd) rangeEnd = html->length - 1;
  998. if(rangeStart < 0) rangeStart = html->length + rangeStart;
  999. if(rangeStart < html->length && rangeEnd < html->length && rangeStart <= rangeEnd) {
  1000. statusCode = 206;
  1001. }
  1002. else {
  1003. if(ifModifiedSince && lastModified && rangeStart == html->length) {
  1004. struct tm time_ = {};
  1005. gmtime_r(&lastModified,&time_);
  1006. time_t tmp = mktime(&time_);
  1007. if(ifModifiedSince >= tmp) {
  1008. sendResponse(304, "Not Modified", socketToClient);
  1009. log_printf(5,"not modified!\n");
  1010. statusCode = 304;
  1011. goto last;
  1012. }
  1013. }
  1014. sendResponse(416, "Requested range not satisfiable", socketToClient);
  1015. statusCode = 416;
  1016. goto last;
  1017. }
  1018. }
  1019. }
  1020. resp:
  1021. if(statusCode == 206) sendBasicHeaders(statusCode,"Partial Content",socketToClient);
  1022. else sendBasicHeaders(statusCode,"OK",socketToClient);
  1023. if(0 >= socketToClient->writeString("Content-Type: text/plain\r\n")) goto last;
  1024. if(0 >= socketToClient->writeString("Accept-Ranges: bytes\r\n")) goto last;
  1025. if (requestHeaders.has("X-Proxy2ch-API-Bypass")) {
  1026. if (0 >= socketToClient->writeString("Thread-Status: 1\r\n")) goto last;
  1027. if (0 >= socketToClient->writeString("User-Status: 3\r\n")) goto last;
  1028. }
  1029. if(statusCode == 206) {
  1030. std::ostringstream ss;
  1031. ss << "Content-Range: bytes " << rangeStart << "-" << rangeEnd << "/" << html->length << "\r\n";
  1032. if (0 >= socketToClient->writeString(ss.str())) goto last;
  1033. //fprintf(stderr,"Content-Length: %ld\r\n",rangeEnd - rangeStart + 1);
  1034. //fprintf(stderr,"Content-Range: bytes %ld-%ld/%ld\r\n",rangeStart,rangeEnd,(long)html->length);
  1035. DataStorage *newHtml = new DataStorage();
  1036. newHtml->appendBytes(html->bytes+rangeStart, rangeEnd - rangeStart + 1);
  1037. delete html;
  1038. html = newHtml;
  1039. }
  1040. {
  1041. std::ostringstream ss;
  1042. ss << "Content-Length: " << html->length << "\r\n";
  1043. if(0 >= socketToClient->writeString(ss.str())) goto last;
  1044. }
  1045. if(lastModified) {
  1046. struct tm time_ = {};
  1047. char date[256];
  1048. gmtime_r(&lastModified,&time_);
  1049. strftime(date,256,httpTimestampFmt,&time_);
  1050. std::string header = "Last-Modified: ";
  1051. header += date;
  1052. header += "\r\n";
  1053. if(0 >= socketToClient->writeString(header)) goto last;
  1054. //fprintf(stderr,"Last-Modified: %s\r\n",date);
  1055. }
  1056. if(0 > socketToClient->writeString("\r\n")) goto last;
  1057. if(html && statusCode >= 200 && statusCode < 300 && strcasecmp(method, "HEAD")) {
  1058. if(html->length > socketToClient->write(html->bytes, html->length)) goto last;
  1059. }
  1060. last:
  1061. if(buf) free(buf);
  1062. if(html) delete html;
  1063. return statusCode;
  1064. }
  1065. DataStorage *BBS2chProxyConnection::html2dat_old(DataStorage *html, int startResNum, time_t *lastModified, bool useCache)
  1066. {
  1067. char *ptr = html->bytes;
  1068. char *end = html->bytes + html->length - 1;
  1069. DataStorage *txt = new DataStorage();
  1070. int res = startResNum, i=0;
  1071. char signature[32];
  1072. char title[1024];
  1073. int cachedSize = 0;
  1074. bool bbspink = strstr(threadKey.c_str(),"bbspink.com") ? true : false;
  1075. ptr = (char *)memmem_priv(ptr, end-ptr+1, "<title>", 7);
  1076. if(!ptr) {
  1077. delete txt;
  1078. return NULL;
  1079. }
  1080. ptr += 7;
  1081. while(1) {
  1082. if(*ptr == '<') {
  1083. if(!strncasecmp(ptr,"</title>",8)) {
  1084. ptr += 8;
  1085. break;
  1086. }
  1087. else title[i++] = *ptr++;
  1088. }
  1089. else title[i++] = *ptr++;
  1090. }
  1091. title[i] = 0;
  1092. snprintf(signature,32,"<dt>%d ",res);
  1093. ptr = (char *)memmem_priv(ptr, end-ptr+1, signature, strlen(signature));
  1094. if(!ptr) {
  1095. delete txt;
  1096. return NULL;
  1097. }
  1098. unsigned char *buffer = (unsigned char *)malloc(65536+1024+1024+1024+2048);
  1099. if(!buffer) {
  1100. delete txt;
  1101. return NULL;
  1102. }
  1103. unsigned char *body = buffer;
  1104. char *mail = (char *)body + 65536;
  1105. char *name = mail + 1024;
  1106. char *date = name + 1024;
  1107. char *encrypted = date + 1024;
  1108. while(ptr < end) {
  1109. //fprintf(stderr,"%s\n",signature);
  1110. DataStorage *resData = new DataStorage();
  1111. i=0;
  1112. mail[0] = 0;
  1113. ptr = strstr(ptr,signature);
  1114. ptr += strlen(signature);
  1115. while(*ptr != '<') ptr++;
  1116. ptr++;
  1117. const char *endStr;
  1118. if(*ptr == 'a' || *ptr == 'A') {
  1119. replay:
  1120. // has mail
  1121. while(*ptr != '"') ptr++;
  1122. ptr++;
  1123. if(!strncmp(ptr,"/cdn-cgi/l/email-protection#",28)) {
  1124. ptr += 28;
  1125. while(*ptr != '"' && *ptr != 'X') encrypted[i++] = *ptr++;
  1126. encrypted[i] = 0;
  1127. i = decryptMail((unsigned char *)mail,encrypted);
  1128. int reconstruct_len = *ptr == 'X' ? i + 15 : i + 16;
  1129. ptr -= reconstruct_len;
  1130. char *start = ptr;
  1131. memcpy(ptr, "<a href=\"mailto:", 16);
  1132. ptr += 16;
  1133. memcpy(ptr, mail, i);
  1134. ptr = start;
  1135. i=0;
  1136. goto replay;
  1137. }
  1138. else {
  1139. if(!strncmp(ptr,"mailto:",7)) ptr += 7;
  1140. while(*ptr != '"') mail[i++] = *ptr++;
  1141. mail[i] = 0;
  1142. }
  1143. endStr = "</a>";
  1144. }
  1145. else if(*ptr == 'b') {
  1146. endStr = NULL;
  1147. }
  1148. else {
  1149. endStr = "</font>";
  1150. }
  1151. if(endStr) {
  1152. ptr = strstr(ptr,"<b>");
  1153. ptr += 3;
  1154. }
  1155. else {
  1156. ptr = strchr(ptr,'>');
  1157. ptr++;
  1158. }
  1159. i=0;
  1160. while(1) {
  1161. if(*ptr == '<') {
  1162. if(!strncasecmp(ptr,"</b>",4) && (!endStr || !strncasecmp(ptr+4,endStr,strlen(endStr)))) {
  1163. ptr += 4;
  1164. if(endStr) ptr += strlen(endStr);
  1165. break;
  1166. }
  1167. else if(!strncmp(ptr,"<span class=\"__cf_email__\"",26)) {
  1168. int j=0;
  1169. ptr = strstr(ptr,"data-cfemail=\"");
  1170. ptr += 14;
  1171. while(*ptr != '"') encrypted[j++] = *ptr++;
  1172. encrypted[j] = 0;
  1173. j = decryptMail((unsigned char *)name+i,encrypted);
  1174. i += j;
  1175. ptr = strstr(ptr,"</script>");
  1176. ptr += 9;
  1177. }
  1178. else name[i++] = *ptr++;
  1179. }
  1180. else name[i++] = *ptr++;
  1181. }
  1182. resData->appendBytes(name, i);
  1183. resData->appendBytes("<>", 2);
  1184. if(mail[0]) resData->appendBytes(mail ,strlen(mail));
  1185. resData->appendBytes("<>", 2);
  1186. ptr += 2;
  1187. i=0;
  1188. while(1) {
  1189. if(*ptr == '<') {
  1190. if(!strncasecmp(ptr,"<dd>",4)) {
  1191. ptr += 4;
  1192. break;
  1193. }
  1194. else if(!strncmp(ptr,"<a href=\"javascript:be(",23)) {
  1195. memcpy(date+i,"BE:",3);
  1196. ptr += 23;
  1197. i += 3;
  1198. while(*ptr != ')') date[i++] = *ptr++;
  1199. date[i++] = '-';
  1200. ptr = strchr(ptr,'?');
  1201. ptr++;
  1202. char *tmp = strstr(ptr,"</a>");
  1203. memcpy(date+i,ptr,tmp-ptr);
  1204. i += tmp-ptr;
  1205. ptr = tmp + 4;
  1206. }
  1207. else date[i++] = *ptr++;
  1208. }
  1209. else date[i++] = *ptr++;
  1210. }
  1211. resData->appendBytes(date ,i);
  1212. resData->appendBytes("<>", 2);
  1213. i=0;
  1214. while(1) {
  1215. if(*ptr == '<') {
  1216. if(!strncasecmp(ptr,"<br><br>\n",9)) {
  1217. ptr += 9;
  1218. break;
  1219. }
  1220. else if(!strncasecmp(ptr,"<dt>",4) || !strncasecmp(ptr,"</dl>",5)) {
  1221. while(i>0 &&body[i-1] == '\n') i--;
  1222. break;
  1223. }
  1224. else if(!strncmp(ptr,"<span class=\"__cf_email__\"",26) || !strncmp(ptr,"<a class=\"__cf_email__\"",23)) {
  1225. int j=0;
  1226. ptr = strstr(ptr,"data-cfemail=\"");
  1227. ptr += 14;
  1228. while(*ptr != '"') encrypted[j++] = *ptr++;
  1229. encrypted[j] = 0;
  1230. j = decryptMail(body+i,encrypted);
  1231. i += j;
  1232. ptr = strstr(ptr,"</script>");
  1233. ptr += 9;
  1234. }
  1235. else if(!strncmp(ptr,"<a href=\"http",13)) {
  1236. ptr = strchr(ptr,'>');
  1237. ptr++;
  1238. char *link = ptr;
  1239. ptr = strstr(link,"</a>");
  1240. memcpy(body+i,link,ptr-link);
  1241. i += ptr-link;
  1242. ptr += 4;
  1243. }
  1244. else if(!strncmp(ptr,"<img src=\"",10)) {
  1245. ptr += 10;
  1246. char *img = ptr;
  1247. ptr = strstr(img,"\">");
  1248. memcpy(body+i,img,ptr-img);
  1249. if(memmem_priv(img,ptr-img,"/img.2ch.net",12) || memmem_priv(img,ptr-img,"/img.5ch.net",12) || memmem_priv(img,ptr-img,"/o.8ch.net",10) || memmem_priv(img,ptr-img,"/o.5ch.net",10)) {
  1250. int length = ptr-img;
  1251. while(*img != '/') {
  1252. img++;
  1253. length--;
  1254. }
  1255. memcpy(body+i,"sssp:",5);
  1256. memcpy(body+i+5,img,length);
  1257. i += length + 5;
  1258. }
  1259. else i += ptr-img;
  1260. ptr += 2;
  1261. }
  1262. else if(!bbspink && !strncmp(ptr,"<br>",4)) {
  1263. if(i>5 && !strncmp((char *)body+i-5,"<br> ",5)) {
  1264. memcpy(body+i," <br>",5);
  1265. i += 5;
  1266. }
  1267. else {
  1268. memcpy(body+i,"<br>",4);
  1269. i += 4;
  1270. }
  1271. ptr += 4;
  1272. }
  1273. else body[i++] = *ptr++;
  1274. }
  1275. else if(!bbspink && *ptr == ' ') {
  1276. if(*(ptr+1) == ' ') ptr++;
  1277. else body[i++] = *ptr++;
  1278. }
  1279. else body[i++] = *ptr++;
  1280. }
  1281. resData->appendBytes(body ,i);
  1282. resData->appendBytes("<>", 2);
  1283. if(res == 1) resData->appendBytes(title ,strlen(title));
  1284. resData->appendBytes("\n" ,1);
  1285. if(useCache && res == startResNum) {
  1286. PBBS2chProxyThreadInfo info;
  1287. bool hit = false;
  1288. pthread_mutex_lock(mutex);
  1289. BBS2chProxyThreadCache::iterator it = threadCache->find(threadKey);
  1290. if(it != threadCache->end()) {
  1291. info = it->second;
  1292. threadCache->erase(it);
  1293. }
  1294. pthread_mutex_unlock(mutex);
  1295. if(info) {
  1296. log_printf(5,"cache hit");
  1297. if(info->cachedData->length == resData->length) {
  1298. log_printf(5,"... size match");
  1299. if(!memcmp(info->cachedData->bytes,resData->bytes,resData->length)) {
  1300. log_printf(5,"... content match");
  1301. hit = true;
  1302. cachedSize = info->cachedSize - resData->length;
  1303. }
  1304. }
  1305. log_printf(5,"\n");
  1306. }
  1307. if(!hit) {
  1308. delete resData;
  1309. free(buffer);
  1310. return NULL;
  1311. }
  1312. }
  1313. txt->appendBytes(resData->bytes, resData->length);
  1314. res++;
  1315. while(*ptr == '\n' || *ptr == '\r') ptr++;
  1316. snprintf(signature,32,"<dt>%d ",res);
  1317. if(!memmem_priv(ptr, end-ptr+1, signature, strlen(signature))) {
  1318. PBBS2chProxyThreadInfo info(new BBS2chProxyThreadInfo());
  1319. info->lastResNum = res-1;
  1320. info->cachedSize = txt->length+cachedSize;
  1321. info->cachedData = resData;
  1322. pthread_mutex_lock(mutex);
  1323. threadCache->insert(std::make_pair(threadKey,info));
  1324. pthread_mutex_unlock(mutex);
  1325. log_printf(5,"cached thread %s (%ld bytes)\n",threadKey.c_str(),(long)resData->length);
  1326. if(lastModified) {
  1327. *lastModified = 0;
  1328. char formattedDate[256];
  1329. char *ptr;
  1330. ptr = date;
  1331. int year = strtol(ptr,&ptr,10);
  1332. if(*ptr != '/') break;
  1333. ptr++;
  1334. int month = strtol(ptr,&ptr,10);
  1335. if(*ptr != '/') break;
  1336. ptr++;
  1337. int day = strtol(ptr,&ptr,10);
  1338. if(!*ptr) break;
  1339. while(*ptr != ' ' && *ptr != 0) ptr++;
  1340. if(!*ptr) break;
  1341. ptr++;
  1342. int hour = strtol(ptr,&ptr,10);
  1343. if(*ptr != ':') break;
  1344. ptr++;
  1345. int minutes = strtol(ptr,&ptr,10);
  1346. if(*ptr != ':') break;
  1347. ptr++;
  1348. int seconds = strtol(ptr,&ptr,10);
  1349. if(!(month>0 && month<13) || !(day>0 && day<32)) break;
  1350. if(year < 100) year += 2000;
  1351. snprintf(formattedDate,256,"%d/%d/%d %02d:%02d:%02d JST",year,month,day,hour,minutes,seconds);
  1352. //fprintf(stderr,"%s\n",formattedDate);
  1353. struct tm time = {};
  1354. strptime(formattedDate,threadTimestampFmt,&time);
  1355. *lastModified = mktime(&time);
  1356. //gmtime_r(lastModified,&time);
  1357. //strftime(formattedDate,256,httpTimestampFmt,&time);
  1358. //fprintf(stderr,"%s\n",formattedDate);
  1359. }
  1360. //fprintf(stderr,"not found,%ld\n",end-ptr+1);
  1361. break;
  1362. }
  1363. delete resData;
  1364. }
  1365. free(buffer);
  1366. return txt;
  1367. }
  1368. DataStorage *BBS2chProxyConnection::html2dat(DataStorage *html, int startResNum, time_t *lastModified, bool useCache)
  1369. {
  1370. char *ptr = html->bytes;
  1371. char *end = html->bytes + html->length - 1;
  1372. DataStorage *txt = new DataStorage();
  1373. int res = startResNum, i=0;
  1374. char signature[64];
  1375. char title[1024];
  1376. int cachedSize = 0;
  1377. char signatureTag[32];
  1378. char closeTag[32];
  1379. int closeTagLen;
  1380. bool isNewHTML = false;
  1381. ptr = (char *)memmem_priv(ptr, end-ptr+1, "<div id=\"threadtitle\">", 22);
  1382. if (ptr) {
  1383. isNewHTML = true;
  1384. char *ptr2 = (char *)memmem_priv(ptr, end-ptr+1, "<article id=\"", 13);
  1385. if (!ptr2) {
  1386. delete txt;
  1387. return NULL;
  1388. }
  1389. ptr += 22;
  1390. while (1) {
  1391. if (*ptr == '<') {
  1392. if (!strncasecmp(ptr, "</div>", 6)) {
  1393. ptr += 6;
  1394. break;
  1395. }
  1396. else title[i++] = *ptr++;
  1397. }
  1398. else if(*ptr == '\n') break;
  1399. else title[i++] = *ptr++;
  1400. }
  1401. title[i] = 0;
  1402. snprintf(signature, 32, "<article id=\"%d\"", res);
  1403. }
  1404. else {
  1405. ptr = html->bytes;
  1406. ptr = (char *)memmem_priv(ptr, end-ptr+1, "<h1 class=\"title\">", 18);
  1407. if(!ptr) {
  1408. delete txt;
  1409. return html2dat_old(html, startResNum, lastModified, useCache);
  1410. }
  1411. else {
  1412. char *ptr2 = (char *)memmem_priv(ptr, end-ptr+1, " class=\"post\"", 13);
  1413. if(ptr2) {
  1414. char *tmp = ptr2;
  1415. *ptr2 = 0;
  1416. while(*ptr2 != '<') ptr2--;
  1417. strcpy(signatureTag, ptr2);
  1418. *tmp = ' ';
  1419. }
  1420. else {
  1421. delete txt;
  1422. return NULL;
  1423. }
  1424. /*char *ptr2 = (char *)memmem_priv(ptr, end-ptr+1, "<dl class=\"post\"", 16);
  1425. if(ptr2) {
  1426. delete txt;
  1427. return html2dat_pink(html, startResNum, lastModified, useCache);
  1428. }*/
  1429. }
  1430. ptr += 18;
  1431. while(1) {
  1432. if(*ptr == '<') {
  1433. if(!strncasecmp(ptr,"</h1>",5)) {
  1434. ptr += 5;
  1435. break;
  1436. }
  1437. else title[i++] = *ptr++;
  1438. }
  1439. else if(*ptr == '\n') break;
  1440. else title[i++] = *ptr++;
  1441. }
  1442. title[i] = 0;
  1443. snprintf(signature,32,"%s class=\"post\" id=\"%d\"",signatureTag,res);
  1444. }
  1445. ptr = (char *)memmem_priv(ptr, end-ptr+1, signature, strlen(signature));
  1446. if(!ptr) {
  1447. delete txt;
  1448. return NULL;
  1449. }
  1450. unsigned char *buffer = (unsigned char *)malloc(65536+1024+1024+1024+2048);
  1451. if(!buffer) {
  1452. delete txt;
  1453. return NULL;
  1454. }
  1455. unsigned char *body = buffer;
  1456. char *mail = (char *)body + 65536;
  1457. char *name = mail + 1024;
  1458. char *date = name + 1024;
  1459. char *encrypted = date + 1024;
  1460. while(ptr < end) {
  1461. //fprintf(stderr,"%s\n",signature);
  1462. DataStorage *resData = new DataStorage();
  1463. i=0;
  1464. mail[0] = 0;
  1465. if (isNewHTML) ptr = strstr(ptr," class=\"postusername\"><b>");
  1466. else ptr = strstr(ptr," class=\"name\"><b>");
  1467. if(ptr) {
  1468. char *tmp = ptr;
  1469. *ptr = 0;
  1470. while(*ptr != '<') ptr--;
  1471. snprintf(closeTag,32,"</%s>",ptr+1);
  1472. closeTagLen = strlen(closeTag);
  1473. if (isNewHTML) ptr = tmp + 25;
  1474. else ptr = tmp + 17;
  1475. }
  1476. else {
  1477. delete resData;
  1478. break;
  1479. }
  1480. char endStr[64];
  1481. if(!strncmp(ptr,"<a href=\"mailto:",16)) {
  1482. replay:
  1483. // has mail
  1484. while(*ptr != '"') ptr++;
  1485. ptr++;
  1486. if(!strncmp(ptr,"/cdn-cgi/l/email-protection#",28)) {
  1487. ptr += 28;
  1488. while(*ptr != '"' && *ptr != 'X') encrypted[i++] = *ptr++;
  1489. encrypted[i] = 0;
  1490. i = decryptMail((unsigned char *)mail,encrypted);
  1491. int reconstruct_len = *ptr == 'X' ? i + 15 : i + 16;
  1492. ptr -= reconstruct_len;
  1493. char *start = ptr;
  1494. memcpy(ptr, "<a href=\"mailto:", 16);
  1495. ptr += 16;
  1496. memcpy(ptr, mail, i);
  1497. ptr = start;
  1498. i=0;
  1499. goto replay;
  1500. }
  1501. else {
  1502. if(!strncmp(ptr,"mailto:",7)) ptr += 7;
  1503. while(1) {
  1504. if(*ptr == '<' && !strncmp(ptr,"<a href=\"",9)) {
  1505. ptr = strchr(ptr,'>');
  1506. ptr++;
  1507. char *link = ptr;
  1508. ptr = strstr(link,"</a>");
  1509. memcpy(mail+i,link,ptr-link);
  1510. i += ptr-link;
  1511. ptr += 4;
  1512. }
  1513. else if(*ptr == '"') break;
  1514. else mail[i++] = *ptr++;
  1515. }
  1516. //while(*ptr != '"') mail[i++] = *ptr++;
  1517. mail[i] = 0;
  1518. }
  1519. snprintf(endStr,64,"</a></b>%s",closeTag);
  1520. while(*ptr != '>') ptr++;
  1521. ptr++;
  1522. }
  1523. /* we do not have to handle this special case because read.cgi on bbspink doesn't
  1524. emit font tags anymore and it conflicts with text decorations using "melon point" */
  1525. /*else if(!strncmp(ptr,"<font",5)) {
  1526. snprintf(endStr,64,"</font></b>%s",closeTag);
  1527. while(*ptr != '>') ptr++;
  1528. ptr++;
  1529. }*/
  1530. else {
  1531. snprintf(endStr,64,"</b>%s",closeTag);
  1532. }
  1533. i=0;
  1534. while(1) {
  1535. if(*ptr == '<') {
  1536. if(!strncmp(ptr,endStr,strlen(endStr))) {
  1537. ptr += strlen(endStr);
  1538. break;
  1539. }
  1540. else if(!strncmp(ptr,"<span class=\"__cf_email__\"",26)) {
  1541. int j=0;
  1542. ptr = strstr(ptr,"data-cfemail=\"");
  1543. ptr += 14;
  1544. while(*ptr != '"') encrypted[j++] = *ptr++;
  1545. encrypted[j] = 0;
  1546. j = decryptMail((unsigned char *)name+i,encrypted);
  1547. i += j;
  1548. ptr = strstr(ptr,"</script>");
  1549. ptr += 9;
  1550. }
  1551. else if(!strncmp(ptr,"<a href=\"",9)) {
  1552. ptr = strchr(ptr,'>');
  1553. ptr++;
  1554. char *link = ptr;
  1555. ptr = strstr(link,"</a>");
  1556. memcpy(name+i,link,ptr-link);
  1557. i += ptr-link;
  1558. ptr += 4;
  1559. }
  1560. else name[i++] = *ptr++;
  1561. }
  1562. else name[i++] = *ptr++;
  1563. }
  1564. resData->appendBytes(name, i);
  1565. resData->appendBytes("<>", 2);
  1566. if(mail[0]) resData->appendBytes(mail ,strlen(mail));
  1567. resData->appendBytes("<>", 2);
  1568. ptr = strstr(ptr," class=\"date\">");
  1569. if(ptr) {
  1570. char *tmp = ptr;
  1571. *ptr = 0;
  1572. while(*ptr != '<') ptr--;
  1573. snprintf(closeTag,32,"</%s>",ptr+1);
  1574. closeTagLen = strlen(closeTag);
  1575. ptr = tmp + 14;
  1576. }
  1577. else {
  1578. delete resData;
  1579. break;
  1580. }
  1581. i=0;
  1582. while(1) {
  1583. if(*ptr == '<') {
  1584. if(!strncasecmp(ptr,closeTag,closeTagLen)) {
  1585. ptr += closeTagLen;
  1586. break;
  1587. }
  1588. else date[i++] = *ptr++;
  1589. }
  1590. else date[i++] = *ptr++;
  1591. }
  1592. if(!strncmp(ptr,"<div class=\"uid",15) || !strncmp(ptr,"<span class=\"uid",16)) {
  1593. char *tmp = ptr+1;
  1594. while(*ptr != ' ') ptr++;
  1595. *ptr = 0;
  1596. snprintf(closeTag,32,"</%s>",tmp);
  1597. closeTagLen = strlen(closeTag);
  1598. ptr += 11;
  1599. while(*ptr != '>') ptr++;
  1600. ptr++;
  1601. date[i++] = ' ';
  1602. while(1) {
  1603. if(*ptr == '<') {
  1604. if(!strncasecmp(ptr,closeTag,closeTagLen)) {
  1605. ptr += closeTagLen;
  1606. break;
  1607. }
  1608. else date[i++] = *ptr++;
  1609. }
  1610. else date[i++] = *ptr++;
  1611. }
  1612. }
  1613. if(!strncmp(ptr,"<div class=\"be",14) || !strncmp(ptr,"<span class=\"be",15)) {
  1614. ptr += 14;
  1615. while(*ptr != '>') ptr++;
  1616. ptr++;
  1617. if(!strncmp(ptr,"<a href=\"",9)) {
  1618. ptr += 9;
  1619. while(*ptr != '/' && *ptr != '"') ptr++;
  1620. if(*ptr == '/' && (!strncmp(ptr,"//be.2ch.net/user/",18) || !strncmp(ptr,"//be.5ch.net/user/",18))) {
  1621. memcpy(date+i," BE:",4);
  1622. i += 4;
  1623. ptr += 18;
  1624. while(*ptr != '"') date[i++] = *ptr++;
  1625. date[i++] = '-';
  1626. ptr = strchr(ptr,'?');
  1627. ptr++;
  1628. char *tmp = strstr(ptr,"</a>");
  1629. memcpy(date+i,ptr,tmp-ptr);
  1630. i += tmp-ptr;
  1631. ptr = tmp + 4;
  1632. }
  1633. }
  1634. }
  1635. resData->appendBytes(date ,i);
  1636. resData->appendBytes("<>", 2);
  1637. if (isNewHTML) {
  1638. ptr = strstr(ptr,"<section class=\"post-content\">");
  1639. if (!ptr) {
  1640. delete resData;
  1641. break;
  1642. }
  1643. else {
  1644. ptr += 30;
  1645. if (!strncasecmp(ptr, "<span class=\"AA\">", 17)) {
  1646. strcpy(closeTag, "</span></section>");
  1647. closeTagLen = 17;
  1648. ptr += 17;
  1649. }
  1650. else {
  1651. strcpy(closeTag, "</section>");
  1652. closeTagLen = 10;
  1653. }
  1654. }
  1655. }
  1656. else if(!strcmp(signatureTag,"<div")) {
  1657. ptr = strstr(ptr,"<div class=\"message\">");
  1658. if(!ptr) {
  1659. delete resData;
  1660. break;
  1661. }
  1662. else {
  1663. ptr += 21;
  1664. if(!strncasecmp(ptr,"<span class=\"escaped\">",22)) {
  1665. if(!strncasecmp(ptr+22,"<span class=\"AA\">",17)) {
  1666. strcpy(closeTag,"</span></span></div>");
  1667. closeTagLen = 20;
  1668. ptr += 22+17;
  1669. }
  1670. else {
  1671. strcpy(closeTag,"</span></div>");
  1672. closeTagLen = 13;
  1673. ptr += 22;
  1674. }
  1675. }
  1676. else {
  1677. strcpy(closeTag,"</div>");
  1678. closeTagLen = 6;
  1679. }
  1680. }
  1681. }
  1682. else {
  1683. ptr = strstr(ptr,"<dd class=\"thread_in\">");
  1684. if(!ptr) {
  1685. delete resData;
  1686. break;
  1687. }
  1688. strcpy(closeTag,"</dd>");
  1689. closeTagLen = 5;
  1690. ptr += 22;
  1691. }
  1692. i=0;
  1693. while(1) {
  1694. if(*ptr == '<') {
  1695. if(!strncasecmp(ptr,closeTag,closeTagLen)) {
  1696. ptr += closeTagLen;
  1697. break;
  1698. }
  1699. else if(!strncmp(ptr,"<span class=\"__cf_email__\"",26) || !strncmp(ptr,"<a class=\"__cf_email__\"",23)) {
  1700. int j=0;
  1701. ptr = strstr(ptr,"data-cfemail=\"");
  1702. ptr += 14;
  1703. while(*ptr != '"') encrypted[j++] = *ptr++;
  1704. encrypted[j] = 0;
  1705. j = decryptMail(body+i,encrypted);
  1706. i += j;
  1707. ptr = strstr(ptr,"</script>");
  1708. ptr += 9;
  1709. }
  1710. else if(!strncmp(ptr,"<a ",3)) {
  1711. char *tmp = strchr(ptr,'>');
  1712. char *href = (char *)memmem_priv(ptr,tmp-ptr,"href=\"",6);
  1713. char *link = tmp+1;
  1714. if(href && !strncmp(link,"&gt;&gt;",8) && memmem_priv(href,link-href,"test/read.cgi/",14)) {
  1715. while(ptr < link) {
  1716. if(!strncmp(ptr," class=\"",8)) {
  1717. ptr += 8;
  1718. while(*ptr != '"' && *ptr != '>') ptr++;
  1719. if(*ptr == '"') ptr++;
  1720. }
  1721. else body[i++] = *ptr++;
  1722. }
  1723. }
  1724. else {
  1725. ptr = strstr(link,"</a>");
  1726. memcpy(body+i,link,ptr-link);
  1727. i += ptr-link;
  1728. ptr += 4;
  1729. }
  1730. }
  1731. else if(!strncmp(ptr,"<img src=\"",10)) {
  1732. ptr += 10;
  1733. char *img = ptr;
  1734. ptr = strstr(img,"\">");
  1735. memcpy(body+i,img,ptr-img);
  1736. if(memmem_priv(img,ptr-img,"/img.2ch.net",12) || memmem_priv(img,ptr-img,"/img.5ch.net",12) || memmem_priv(img,ptr-img,"/o.8ch.net",10) || memmem_priv(img,ptr-img,"/o.5ch.net",10)) {
  1737. int length = ptr-img;
  1738. while(*img != '/') {
  1739. img++;
  1740. length--;
  1741. }
  1742. memcpy(body+i,"sssp:",5);
  1743. memcpy(body+i+5,img,length);
  1744. i += length + 5;
  1745. }
  1746. else i += ptr-img;
  1747. ptr += 2;
  1748. }
  1749. else if(!strncmp(ptr,"<br>",4)) {
  1750. if(i>5 && !strncmp((char *)body+i-5,"<br> ",5)) {
  1751. memcpy(body+i," <br>",5);
  1752. i += 5;
  1753. }
  1754. else {
  1755. memcpy(body+i,"<br>",4);
  1756. i += 4;
  1757. }
  1758. ptr += 4;
  1759. }
  1760. else body[i++] = *ptr++;
  1761. }
  1762. else body[i++] = *ptr++;
  1763. }
  1764. resData->appendBytes(body ,i);
  1765. resData->appendBytes("<>", 2);
  1766. if(res == 1) resData->appendBytes(title ,strlen(title));
  1767. resData->appendBytes("\n" ,1);
  1768. if(useCache && res == startResNum) {
  1769. PBBS2chProxyThreadInfo info;
  1770. bool hit = false;
  1771. pthread_mutex_lock(mutex);
  1772. BBS2chProxyThreadCache::iterator it = threadCache->find(threadKey);
  1773. if(it != threadCache->end()) {
  1774. info = it->second;
  1775. threadCache->erase(it);
  1776. }
  1777. pthread_mutex_unlock(mutex);
  1778. if(info) {
  1779. log_printf(5,"cache hit");
  1780. if(info->cachedData->length == resData->length) {
  1781. log_printf(5,"... size match");
  1782. if(!memcmp(info->cachedData->bytes,resData->bytes,resData->length)) {
  1783. log_printf(5,"... content match");
  1784. hit = true;
  1785. cachedSize = info->cachedSize - resData->length;
  1786. }
  1787. }
  1788. log_printf(5,"\n");
  1789. }
  1790. if(!hit) {
  1791. delete resData;
  1792. free(buffer);
  1793. return NULL;
  1794. }
  1795. }
  1796. txt->appendBytes(resData->bytes, resData->length);
  1797. res++;
  1798. while(*ptr == '\n' || *ptr == '\r') ptr++;
  1799. if (isNewHTML) strcpy(signature, "<article id=\"");
  1800. else snprintf(signature,64,"%s class=\"post\" id=\"",signatureTag);
  1801. ptr = (char *)memmem_priv(ptr, end-ptr+1, signature, strlen(signature));
  1802. if(ptr) {
  1803. int next = atoi(ptr+strlen(signature));
  1804. if(next >= res) {
  1805. while(next > res) {
  1806. txt->appendBytes("broken<><>broken<> broken <>\n", 29);
  1807. res++;
  1808. }
  1809. }
  1810. else ptr = NULL;
  1811. }
  1812. if(!ptr) {
  1813. PBBS2chProxyThreadInfo info(new BBS2chProxyThreadInfo());
  1814. info->lastResNum = res-1;
  1815. info->cachedSize = txt->length+cachedSize;
  1816. info->cachedData = resData;
  1817. pthread_mutex_lock(mutex);
  1818. threadCache->insert(std::make_pair(threadKey,info));
  1819. pthread_mutex_unlock(mutex);
  1820. log_printf(5,"cached thread %s (%ld bytes)\n",threadKey.c_str(),(long)resData->length);
  1821. if(lastModified) {
  1822. *lastModified = 0;
  1823. char formattedDate[256];
  1824. char *ptr;
  1825. ptr = date;
  1826. int year = strtol(ptr,&ptr,10);
  1827. if(*ptr != '/') break;
  1828. ptr++;
  1829. int month = strtol(ptr,&ptr,10);
  1830. if(*ptr != '/') break;
  1831. ptr++;
  1832. int day = strtol(ptr,&ptr,10);
  1833. if(!*ptr) break;
  1834. while(*ptr != ' ' && *ptr != 0) ptr++;
  1835. if(!*ptr) break;
  1836. ptr++;
  1837. int hour = strtol(ptr,&ptr,10);
  1838. if(*ptr != ':') break;
  1839. ptr++;
  1840. int minutes = strtol(ptr,&ptr,10);
  1841. if(*ptr != ':') break;
  1842. ptr++;
  1843. int seconds = strtol(ptr,&ptr,10);
  1844. if(!(month>0 && month<13) || !(day>0 && day<32)) break;
  1845. if(year < 100) year += 2000;
  1846. snprintf(formattedDate,256,"%d/%d/%d %02d:%02d:%02d JST",year,month,day,hour,minutes,seconds);
  1847. //fprintf(stderr,"%s\n",formattedDate);
  1848. struct tm time = {};
  1849. strptime(formattedDate,threadTimestampFmt,&time);
  1850. *lastModified = mktime(&time);
  1851. //gmtime_r(lastModified,&time);
  1852. //strftime(formattedDate,256,httpTimestampFmt,&time);
  1853. //fprintf(stderr,"%s\n",formattedDate);
  1854. }
  1855. //fprintf(stderr,"not found,%ld\n",end-ptr+1);
  1856. break;
  1857. }
  1858. delete resData;
  1859. }
  1860. free(buffer);
  1861. return txt;
  1862. }
  1863. int BBS2chProxyConnection::datProxyAPI(const char *url, const char *method, BBS2chProxyHttpHeaders &requestHeaders)
  1864. {
  1865. long statusCode = 0;
  1866. const std::string &postBody = auth.requestBodyForURL(url, curl);
  1867. bool directMode = false;
  1868. if (postBody.empty()) {
  1869. sendResponse(401, "Unauthorized", socketToClient);
  1870. return 401;
  1871. }
  1872. /* just read and strip off post body */
  1873. if (!strcasecmp(method, "POST")) {
  1874. char *postdata = NULL;
  1875. if (isClientChunked) {
  1876. readChunkedBodyIntoBuffer(&postdata, socketToClient);
  1877. }
  1878. else if (content_length) {
  1879. postdata = (char *)calloc(content_length+1, 1);
  1880. socketToClient->read(postdata, content_length);
  1881. }
  1882. if (postdata && strstr(postdata, "sid=")) directMode = true;
  1883. if (postdata) free(postdata);
  1884. }
  1885. if (curl) {
  1886. CURLcode res;
  1887. struct curl_slist *headersForCurl = NULL;
  1888. DataStorage receivedHeader;
  1889. DataStorage receivedBody;
  1890. headersForCurl = requestHeaders.appendToCurlSlist(headersForCurl, "Range");
  1891. headersForCurl = requestHeaders.appendToCurlSlist(headersForCurl, "If-Modified-Since");
  1892. headersForCurl = requestHeaders.appendToCurlSlist(headersForCurl, "Accept-Encoding");
  1893. if (x_2ch_ua_dat) headersForCurl = curl_slist_append(headersForCurl, x_2ch_ua_dat);
  1894. if (curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  1895. curl_easy_setopt(curl, CURLOPT_URL, url);
  1896. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headersForCurl);
  1897. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  1898. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  1899. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  1900. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &receivedBody);
  1901. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_download);
  1902. curl_easy_setopt(curl, CURLOPT_HEADERDATA, &receivedHeader);
  1903. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  1904. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  1905. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  1906. if (force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  1907. if (proxy_server) {
  1908. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  1909. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  1910. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  1911. }
  1912. if (api_ua_dat) {
  1913. curl_easy_setopt(curl, CURLOPT_USERAGENT, api_ua_dat);
  1914. }
  1915. else {
  1916. if (user_agent && !strncmp(user_agent, "Monazilla/", strlen("Monazilla/")))
  1917. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  1918. else
  1919. curl_easy_setopt(curl, CURLOPT_USERAGENT, "");
  1920. }
  1921. curl_easy_setopt(curl, CURLOPT_POST, 1L);
  1922. #if LIBCURL_VERSION_NUM >= 0x071101
  1923. curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, postBody.c_str());
  1924. #else
  1925. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postBody.c_str());
  1926. #endif
  1927. //return;
  1928. res = curl_easy_perform(curl);
  1929. if (res == CURLE_OK) {
  1930. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  1931. receivedHeader.appendBytes("", 1);
  1932. const char *ptr = receivedHeader.bytes;
  1933. /* this is necessary because the raw header may contain chunk trailers after real headers */
  1934. const char *end = strstr(receivedHeader.bytes, "\r\n\r\n");
  1935. int threadStatus = 0;
  1936. if (end && !directMode) {
  1937. BBS2chProxyHttpHeaders headers;
  1938. while (ptr < end) {
  1939. const char *lineEnd = strchr(ptr, '\n');
  1940. if (!lineEnd) break;
  1941. headers.add(ptr, lineEnd-ptr);
  1942. ptr = lineEnd + 1;
  1943. }
  1944. if (headers.has("Thread-Status")) {
  1945. threadStatus = atoi(headers.get("Thread-Status").c_str());
  1946. }
  1947. }
  1948. if (threadStatus == 1 || (directMode && end)) {
  1949. if (end+4-receivedHeader.bytes > socketToClient->write(receivedHeader.bytes, end+4-receivedHeader.bytes)) goto last;
  1950. if (receivedBody.length > socketToClient->write(receivedBody.bytes, receivedBody.length)) goto last;
  1951. goto last;
  1952. }
  1953. else if (threadStatus == 8) {
  1954. sendBasicHeaders(302, "Found", socketToClient);
  1955. if (0 >= socketToClient->writeString("Location: http://www2.2ch.net/live.html\r\n")) goto last;
  1956. if (0 >= socketToClient->writeString("\r\n")) goto last;
  1957. statusCode = 302;
  1958. goto last;
  1959. }
  1960. else {
  1961. if (statusCode < 400) {
  1962. sendResponse(401, "Unauthorized", socketToClient);
  1963. statusCode = 401;
  1964. }
  1965. else {
  1966. sendResponse(503, "Service Unavailable", socketToClient);
  1967. statusCode = 503;
  1968. }
  1969. receivedBody.appendBytes("",1);
  1970. if (!strncasecmp(receivedBody.bytes,"ng (",4)) {
  1971. log_printf(0, "API gateway returned error: %s\n", receivedBody.bytes);
  1972. }
  1973. }
  1974. //fprintf(stderr,"%ld\n",statusCode);
  1975. }
  1976. else {
  1977. log_printf(0, "curl error: %s\n", curl_easy_strerror(res));
  1978. sendResponse(503, "Service Unavailable", socketToClient);
  1979. statusCode = 503;
  1980. }
  1981. last:
  1982. curl_easy_reset(curl);
  1983. curl_slist_free_all(headersForCurl);
  1984. }
  1985. return statusCode;
  1986. }
  1987. int BBS2chProxyConnection::bbsmenuProxy(const char *url, const char *method, BBS2chProxyHttpHeaders &requestHeaders)
  1988. {
  1989. long statusCode = 0;
  1990. DataStorage *dat = new DataStorage();
  1991. DataStorage *outHTML = new DataStorage();
  1992. if(curl) {
  1993. CURLcode res;
  1994. if(curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  1995. curl_easy_setopt(curl, CURLOPT_URL, url);
  1996. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  1997. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  1998. curl_easy_setopt(curl, CURLOPT_ENCODING, "");
  1999. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  2000. curl_easy_setopt(curl, CURLOPT_WRITEDATA, dat);
  2001. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  2002. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  2003. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  2004. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  2005. if(force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  2006. if(proxy_server) {
  2007. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  2008. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  2009. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  2010. }
  2011. if(user_agent) {
  2012. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  2013. }
  2014. else if(requestHeaders.has("User-Agent")) {
  2015. curl_easy_setopt(curl, CURLOPT_USERAGENT, requestHeaders.get("User-Agent").c_str());
  2016. }
  2017. res = curl_easy_perform(curl);
  2018. if(res == CURLE_OK) {
  2019. curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &statusCode);
  2020. if(statusCode == 200 && dat->length) {
  2021. dat->appendBytes("",1);
  2022. dat->length--;
  2023. char *ptr = dat->bytes;
  2024. while(*ptr) {
  2025. if(!strncasecmp(ptr,"<a href=",8)) {
  2026. char *start = ptr+8;
  2027. char *end = strchr(start,'>');
  2028. if (end) {
  2029. char *urlEnd = end;
  2030. if (*start == '"') {
  2031. start++;
  2032. char *tmp = strchr(start, '"');
  2033. if (tmp && tmp < end) urlEnd = tmp;
  2034. }
  2035. BBS2chProxyURL url(std::string(start, urlEnd-start).c_str());
  2036. if (url.isKindOfHost("5ch.net")) {
  2037. url.replaceHost("5ch.net", "2ch.net");
  2038. url.setScheme("http");
  2039. const std::string &replacedURL = url.absoluteString();
  2040. outHTML->appendBytes("<A HREF=", 8);
  2041. outHTML->appendBytes(replacedURL.c_str(), replacedURL.length());
  2042. outHTML->appendBytes(">", 1);
  2043. ptr = end+1;
  2044. continue;
  2045. } else if (url.isKindOfHost("bbspink.com")) {
  2046. url.setScheme("http");
  2047. const std::string &replacedURL = url.absoluteString();
  2048. outHTML->appendBytes("<A HREF=", 8);
  2049. outHTML->appendBytes(replacedURL.c_str(), replacedURL.length());
  2050. outHTML->appendBytes(">", 1);
  2051. ptr = end+1;
  2052. continue;
  2053. }
  2054. }
  2055. }
  2056. outHTML->appendBytes(ptr++, 1);
  2057. }
  2058. }
  2059. }
  2060. else {
  2061. log_printf(0,"curl error: %s (%s)\n", curl_easy_strerror(res), url);
  2062. statusCode = 503;
  2063. }
  2064. }
  2065. if(statusCode == 200) {
  2066. std::ostringstream ss;
  2067. ss << "Content-Length: " << outHTML->length << "\r\n";
  2068. sendBasicHeaders(statusCode,"OK",socketToClient);
  2069. if(0 >= socketToClient->writeString("Content-Type: text/html\r\n")) goto last;
  2070. if(0 >= socketToClient->writeString(ss.str())) goto last;
  2071. if(0 >= socketToClient->writeString("\r\n")) goto last;
  2072. if(strcasecmp(method, "HEAD")) {
  2073. if(outHTML->length > socketToClient->write(outHTML->bytes, outHTML->length)) goto last;
  2074. }
  2075. }
  2076. else {
  2077. sendResponse(503, "Service Unavailable", socketToClient);
  2078. statusCode = 503;
  2079. }
  2080. last:
  2081. if(curl) curl_easy_reset(curl);
  2082. if(dat) delete dat;
  2083. if(outHTML) delete outHTML;
  2084. return statusCode;
  2085. }
  2086. int BBS2chProxyConnection::bbsCgiProxy(const char *url, BBS2chProxyHttpHeaders &requestHeaders, const char *requestBody)
  2087. {
  2088. long statusCode = 0;
  2089. std::string hostStr = requestHeaders.get("Host");
  2090. std::string boardStr;
  2091. std::string threadStr;
  2092. requestHeaders.remove("Host");
  2093. if (user_agent) requestHeaders.set("User-Agent", user_agent);
  2094. if (requestBody && (lua_script || !bbscgi_headers.empty() || !bbscgi_postorder.empty())) {
  2095. std::map<std::string, std::string> fields;
  2096. const char *ptr = requestBody;
  2097. size_t bodyLength = 0;
  2098. while (1) {
  2099. const char *tmp = ptr;
  2100. while (*tmp != '=' && *tmp != 0) tmp++;
  2101. if (*tmp == 0) {
  2102. bodyLength = tmp - requestBody;
  2103. break;
  2104. }
  2105. std::string key(ptr, tmp-ptr);
  2106. tmp++;
  2107. ptr = tmp;
  2108. while (*tmp != '&' && *tmp != 0) tmp++;
  2109. std::string value(ptr, tmp-ptr);
  2110. fields.insert(std::make_pair(key, value));
  2111. if (*tmp == 0) {
  2112. bodyLength = tmp - requestBody;
  2113. break;
  2114. }
  2115. ptr = tmp + 1;
  2116. }
  2117. std::map<std::string, std::string>::iterator it;
  2118. if (it = fields.find("bbs"), it != fields.end()) boardStr = it->second;
  2119. if (it = fields.find("key"), it != fields.end()) threadStr = it->second;
  2120. if (!bbscgi_postorder.empty()) {
  2121. std::string newBody;
  2122. for (std::vector<std::string>::iterator it2 = bbscgi_postorder.begin(); it2 != bbscgi_postorder.end(); it2++) {
  2123. const std::string &name = *it2;
  2124. if (it = fields.find(name), it != fields.end()) {
  2125. if (!newBody.empty()) newBody.append("&");
  2126. newBody.append(name);
  2127. newBody.append("=");
  2128. newBody.append(it->second);
  2129. fields.erase(name);
  2130. }
  2131. }
  2132. for (it = fields.begin(); it != fields.end(); it++) {
  2133. if (!newBody.empty()) newBody.append("&");
  2134. newBody.append(it->first);
  2135. newBody.append("=");
  2136. newBody.append(it->second);
  2137. }
  2138. if (bodyLength == newBody.length()) {
  2139. strcpy((char *)requestBody, newBody.c_str());
  2140. log_printf(1, "Reordered request body is: %s\n", requestBody);
  2141. }
  2142. else {
  2143. log_printf(0, "Error occured while reordering the request body - skipping\n");
  2144. }
  2145. }
  2146. }
  2147. if (!bbscgi_headers.empty()) {
  2148. for (std::map<std::string, std::string>::iterator it = bbscgi_headers.begin(); it!=bbscgi_headers.end(); it++) {
  2149. /* we cannot use a reference here, because the original string shouldn't be replaced */
  2150. std::string value = it->second;
  2151. if (!hostStr.empty()) {
  2152. std::string::size_type pos = value.find("%HOST%");
  2153. while (pos != std::string::npos) {
  2154. value.replace(pos, 6, hostStr);
  2155. pos = value.find("%HOST%", pos+hostStr.length());
  2156. }
  2157. }
  2158. if (!boardStr.empty()) {
  2159. std::string::size_type pos = value.find("%BOARD%");
  2160. while (pos != std::string::npos) {
  2161. value.replace(pos, 7, boardStr);
  2162. pos = value.find("%BOARD%", pos+boardStr.length());
  2163. }
  2164. }
  2165. if (!threadStr.empty()) {
  2166. std::string::size_type pos = value.find("%THREAD%");
  2167. while (pos != std::string::npos) {
  2168. value.replace(pos, 8, threadStr);
  2169. pos = value.find("%THREAD%", pos+threadStr.length());
  2170. }
  2171. }
  2172. requestHeaders.set(it->first, value);
  2173. log_printf(1, "Appended custom header \"%s: %s\"\n", it->first.c_str(), value.c_str());
  2174. }
  2175. }
  2176. for (int run=0; run<2; run++) {
  2177. BBS2chProxyHttpHeaders *_headers = new BBS2chProxyHttpHeaders(requestHeaders);
  2178. curl_slist *headersForCurl = NULL;
  2179. char *_body = (char *)requestBody;
  2180. std::string nic, forceProxy;
  2181. long verbose = 0;
  2182. status = 0;
  2183. monaKeyForRequest = "";
  2184. #ifdef USE_LUA
  2185. if (lua_script) {
  2186. lua_State* l = luaL_newstate();
  2187. luaL_openlibs(l);
  2188. if (luaL_loadfile(l, lua_script) != LUA_OK) {
  2189. log_printf(0, "Lua: Failed to open script %s:\n %s\n", lua_script, lua_tostring(l, -1));
  2190. goto lua_end;
  2191. }
  2192. lua_newtable(l);
  2193. lua_pushcfunction(l, lua_hmacSHA256);
  2194. lua_setfield(l, -2, "hmacSHA256");
  2195. lua_pushcfunction(l, lua_decodeURIComponent);
  2196. lua_setfield(l, -2, "decodeURIComponent");
  2197. lua_pushcfunction(l, lua_encodeURIComponent);
  2198. lua_setfield(l, -2, "encodeURIComponent");
  2199. lua_pushcfunction(l, lua_convertShiftJISToUTF8);
  2200. lua_setfield(l, -2, "convertShiftJISToUTF8");
  2201. lua_pushcfunction(l, lua_isExpiredKey);
  2202. lua_setfield(l, -2, "isExpiredKey");
  2203. lua_pushcfunction(l, lua_isValidAsUTF8);
  2204. lua_setfield(l, -2, "isValidAsUTF8");
  2205. lua_pushcfunction(l, lua_getMonaKey);
  2206. lua_setfield(l, -2, "getMonaKey");
  2207. lua_pushstring(l, keyManager.getKey().c_str());
  2208. lua_setfield(l, -2, "monaKey");
  2209. lua_pushinteger(l, serverPort);
  2210. lua_setfield(l, -2, "port");
  2211. lua_setglobal(l, "proxy2ch");
  2212. BBS2chProxyHttpHeaders::getClassDefinitionForLua(l);
  2213. lua_setglobal(l, "HttpHeaders");
  2214. if (lua_pcall(l, 0, 0, 0) != LUA_OK) {
  2215. log_printf(0, "Lua: Failed to run script %s:\n %s\n", lua_script, lua_tostring(l, -1));
  2216. goto lua_end;
  2217. }
  2218. lua_getglobal(l, "willSendRequestToBbsCgi");
  2219. if (!lua_isfunction(l, -1)) {
  2220. log_printf(0, "Lua: willSendRequestToBbsCgi function does not exist in the script\n");
  2221. goto lua_end;
  2222. }
  2223. lua_newtable(l);
  2224. _headers->getUserdataForLua(l);
  2225. lua_setfield(l, -2, "headers");
  2226. lua_pushstring(l, _body);
  2227. lua_setfield(l, -2, "body");
  2228. lua_pushstring(l, hostStr.c_str());
  2229. lua_pushstring(l, boardStr.c_str());
  2230. lua_pushstring(l, threadStr.c_str());
  2231. if (lua_pcall(l, 4, 1, 0) != LUA_OK) {
  2232. log_printf(0, "Lua: Failed to call willSendRequestToBbsCgi function:\n %s\n", lua_tostring(l, -1));
  2233. goto lua_end;
  2234. }
  2235. if (!lua_istable(l, -1)) {
  2236. log_printf(0, "Lua: A return type of willSendRequestToBbsCgi function should be a table\n");
  2237. goto lua_end;
  2238. }
  2239. lua_pushstring(l, "body");
  2240. lua_rawget(l, -2);
  2241. if (lua_isstring(l, -1)) {
  2242. const char *newBody = lua_tostring(l, -1);
  2243. _body = strdup(newBody);
  2244. log_printf(1, "Lua: Set request body \"%s\"\n", newBody);
  2245. }
  2246. lua_pop(l, 1);
  2247. lua_pushstring(l, "headers");
  2248. lua_rawget(l, -2);
  2249. if (lua_istable(l, -1)) {
  2250. delete _headers;
  2251. _headers = new BBS2chProxyHttpHeaders();
  2252. lua_pushnil(l);
  2253. while (lua_next(l, -2)) {
  2254. if (lua_isstring(l, -1) && lua_isstring(l, -2)) {
  2255. const char *name = lua_tostring(l, -2);
  2256. const char *value = lua_tostring(l, -1);
  2257. _headers->add(name, value);
  2258. log_printf(1, "Lua: Set request header \"%s: %s\"\n", name, value);
  2259. }
  2260. lua_pop(l, 1);
  2261. }
  2262. }
  2263. else if (lua_isuserdata(l, -1)) {
  2264. if (lua_getmetatable(l, -1)) {
  2265. if (lua_getfield(l, -1, "_type") == LUA_TSTRING) {
  2266. if (!strcmp(lua_tostring(l, -1), "HttpHeaders")) {
  2267. BBS2chProxyHttpHeaders *newHeaders = *((BBS2chProxyHttpHeaders **)lua_touserdata(l, -3));
  2268. if (newHeaders != _headers) {
  2269. /* remove metatable to prevent the object from garbage collected by lua */
  2270. lua_newtable(l);
  2271. lua_setmetatable(l, -4);
  2272. delete _headers;
  2273. _headers = newHeaders;
  2274. }
  2275. for (std::map<std::string, PBBS2chProxyHttpHeaderEntry>::iterator it = _headers->getMap().begin(); it != _headers->getMap().end(); it++) {
  2276. log_printf(1, "Lua: Set request header \"%s\"\n", it->second->getFull().c_str());
  2277. }
  2278. }
  2279. }
  2280. lua_pop(l, 2);
  2281. }
  2282. }
  2283. lua_pop(l, 1);
  2284. lua_pushstring(l, "options");
  2285. lua_rawget(l, -2);
  2286. if (lua_istable(l, -1)) {
  2287. lua_pushstring(l, "interface");
  2288. lua_rawget(l, -2);
  2289. if (lua_isstring(l, -1)) {
  2290. nic = std::string(lua_tostring(l, -1));
  2291. }
  2292. lua_pop(l, 1);
  2293. lua_pushstring(l, "verbose");
  2294. lua_rawget(l, -2);
  2295. if (lua_isboolean(l, -1)) {
  2296. verbose = lua_toboolean(l, -1);
  2297. }
  2298. lua_pop(l, 1);
  2299. lua_pushstring(l, "proxy");
  2300. lua_rawget(l, -2);
  2301. if (lua_isstring(l, -1)) {
  2302. forceProxy = std::string(lua_tostring(l, -1));
  2303. }
  2304. }
  2305. lua_end:
  2306. lua_close(l);
  2307. }
  2308. #endif
  2309. do {
  2310. bool isPink = hostStr.find("bbspink.com") != std::string::npos;
  2311. bool shouldSign = appKey && (((api_mode & 2) && !isPink) || (api_mode & 4));
  2312. bool shouldConvertBodyToUTF8 = (bbscgi_utf8 == 1 && shouldSign) || (bbscgi_utf8 == 2);
  2313. userAgentForRequest = _headers->get("User-Agent");
  2314. if (userAgentForRequest.empty() && user_agent) userAgentForRequest = user_agent;
  2315. if (_headers->has("X-MonaKey")) {
  2316. monaKeyForRequest = _headers->get("X-MonaKey");
  2317. }
  2318. if (shouldConvertBodyToUTF8 && !_headers->has("X-PostSig")) {
  2319. std::string newBody = convertBodyToUTF8(_body);
  2320. if (!newBody.empty()) {
  2321. if (_body != requestBody) {
  2322. free(_body);
  2323. }
  2324. _body = strdup(newBody.c_str());
  2325. log_printf(1, "Converted request body to UTF-8: %s\n", _body);
  2326. }
  2327. else {
  2328. log_printf(1, "Request body seems already to be UTF-8, will be sent without conversion\n");
  2329. }
  2330. std::string contentType = _headers->get("Content-Type");
  2331. std::transform(contentType.begin(), contentType.end(), contentType.begin(), tolower);
  2332. if (contentType.find("charset=utf-8") == std::string::npos) {
  2333. _headers->set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
  2334. log_printf(1, "Appended header \"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\"\n");
  2335. }
  2336. }
  2337. if (shouldSign && (!lua_script || !_headers->has("X-PostSig"))) {
  2338. if (!userAgentForRequest.empty()) {
  2339. monaKeyForRequest = keyManager.getKey(userAgentForRequest);
  2340. appendPostSignature(_body, userAgentForRequest, monaKeyForRequest, _headers);
  2341. } else {
  2342. log_printf(0, "API: User-Agent muse be set explicitly to post with API.\n");
  2343. }
  2344. }
  2345. if (!monaKeyForRequest.empty()) {
  2346. double wait = keyManager.secondsToWaitBeforePosting(monaKeyForRequest);
  2347. if (wait > 0) {
  2348. log_printf(1, "Sleeping for %.1f seconds to avoid posting too fast...\n", wait);
  2349. #ifdef _WIN32
  2350. Sleep(wait * 1e+3);
  2351. #else
  2352. usleep(wait * 1e+6);
  2353. #endif
  2354. }
  2355. }
  2356. headersForCurl = _headers->appendToCurlSlist(headersForCurl);
  2357. if (!_headers->has("Expect")) headersForCurl = curl_slist_append(headersForCurl, "Expect:");
  2358. if (!_headers->has("Accept")) headersForCurl = curl_slist_append(headersForCurl, "Accept:");
  2359. } while (0);
  2360. if (curl) {
  2361. CURLcode res;
  2362. if (curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  2363. curl_easy_setopt(curl, CURLOPT_URL, url);
  2364. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  2365. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  2366. if (run == 0)
  2367. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_bbscgi);
  2368. else
  2369. curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_proxy);
  2370. curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
  2371. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_proxy);
  2372. curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
  2373. curl_easy_setopt(curl, CURLOPT_POST, 1L);
  2374. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, _body);
  2375. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  2376. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  2377. curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose);
  2378. if (force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  2379. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  2380. curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headersForCurl);
  2381. if (!nic.empty()) curl_easy_setopt(curl, CURLOPT_INTERFACE, nic.c_str());
  2382. if (user_agent) {
  2383. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  2384. }
  2385. if (!forceProxy.empty()) {
  2386. curl_easy_setopt(curl, CURLOPT_PROXY, forceProxy.c_str());
  2387. }
  2388. else if (proxy_server) {
  2389. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  2390. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  2391. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  2392. }
  2393. res = curl_easy_perform(curl);
  2394. if (res != CURLE_OK) {
  2395. if (res == CURLE_WRITE_ERROR && status == 2) {
  2396. log_printf(1, "MonaKey should be reset. Sending the same request automatically...\n");
  2397. curl_easy_reset(curl);
  2398. curl_slist_free_all(headersForCurl);
  2399. delete _headers;
  2400. if (_body != requestBody) free(_body);
  2401. continue;
  2402. }
  2403. else {
  2404. log_printf(0,"curl error: %s (%s)\n",curl_easy_strerror(res),url);
  2405. if (!status) sendResponse(503, "Service Unavailable", socketToClient);
  2406. statusCode = 503;
  2407. }
  2408. }
  2409. else {
  2410. if (isResponseChunked) {
  2411. socketToClient->writeString("0\r\n\r\n");
  2412. }
  2413. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  2414. }
  2415. curl_easy_reset(curl);
  2416. }
  2417. curl_slist_free_all(headersForCurl);
  2418. delete _headers;
  2419. if (_body != requestBody) free(_body);
  2420. break;
  2421. }
  2422. return statusCode;
  2423. }
  2424. BBS2chProxyURL BBS2chProxyConnection::getRawDatURLAndStatus(const BBS2chThreadIdentifier &threadIdentifier, BBS2chProxyHttpHeaders &requestHeaders, bool findKakologOnly, long *status, bool *foundAsKakolog)
  2425. {
  2426. long statusCode = 0;
  2427. std::string datURL;
  2428. for (int i=findKakologOnly ? 1 : 0; i<2; i++) {
  2429. datURL = force_5chnet_https ? "https://" : "http://";
  2430. datURL += threadIdentifier.host;
  2431. datURL += '/';
  2432. datURL += threadIdentifier.board;
  2433. if (i) {
  2434. datURL += "/oyster/";
  2435. datURL += threadIdentifier.key.substr(0, std::max(threadIdentifier.key.size() - 6, (size_t)0));
  2436. datURL += "/";
  2437. datURL += threadIdentifier.key;
  2438. datURL += ".dat";
  2439. } else {
  2440. datURL += "/dat/";
  2441. datURL += threadIdentifier.key;
  2442. datURL += ".dat";
  2443. }
  2444. statusCode = 0;
  2445. if (curl) {
  2446. CURLcode res;
  2447. if (curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  2448. curl_easy_setopt(curl, CURLOPT_URL, datURL.c_str());
  2449. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  2450. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  2451. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  2452. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  2453. //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
  2454. if (force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  2455. curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  2456. curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
  2457. if (requestHeaders.has("User-Agent")) {
  2458. curl_easy_setopt(curl, CURLOPT_USERAGENT, requestHeaders.get("User-Agent").c_str());
  2459. }
  2460. else if (user_agent) {
  2461. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  2462. }
  2463. if (proxy_server) {
  2464. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  2465. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  2466. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  2467. }
  2468. res = curl_easy_perform(curl);
  2469. if (res == CURLE_OK) {
  2470. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  2471. }
  2472. curl_easy_reset(curl);
  2473. if (statusCode == 200) {
  2474. if (foundAsKakolog) *foundAsKakolog = !!(i);
  2475. break;
  2476. }
  2477. }
  2478. }
  2479. if (status) *status = statusCode;
  2480. return BBS2chProxyURL(datURL.c_str());
  2481. }
  2482. void BBS2chProxyConnection::compileRegex(void)
  2483. {
  2484. static int compiled;
  2485. if (compiled) return;
  2486. regcomp(&regex, "^https?://([^:/.]+)\\.(2ch\\.net|5ch\\.net|bbspink\\.com)(:[0-9]+)?/([^/]+)/dat/([0-9]+)\\.dat", REG_EXTENDED|REG_ICASE);
  2487. regcomp(&regex_kako, "^https?://([^:/.]+)\\.(2ch\\.net|5ch\\.net|bbspink\\.com)(:[0-9]+)?/([^/]+)/kako/[0-9]+/([0-9]+/)?([0-9]+)\\.dat", REG_EXTENDED|REG_ICASE);
  2488. regcomp(&regex_offlaw, "^https?://([^:/.]+)\\.(2ch\\.net|5ch\\.net|bbspink\\.com)(:[0-9]+)?/test/offlaw2.so\\?.*bbs=([^&]+)", REG_EXTENDED|REG_ICASE);
  2489. regcomp(&regex_api, "^https?://api\\.[25]ch\\.net(:[0-9]+)?/v1/([^/]+)/([^/]+)/([0-9]+)", REG_EXTENDED|REG_ICASE);
  2490. regcomp(&regex_api_auth, "^https?://api\\.[25]ch\\.net(:[0-9]+)?/v1/auth/?$", REG_EXTENDED|REG_ICASE);
  2491. compiled = 1;
  2492. }