431 {
433
434
435 std::string subdomain = std::regex_replace(subdomainBase, std::regex("\\."), "");
436 for (auto& c : subdomain)
437 c = (char)std::tolower((unsigned char)c);
438
439 {
440 std::lock_guard<std::mutex> lk(
urlMutex);
442 }
443
444
445 SECURITY_ATTRIBUTES sa{};
446 sa.nLength = sizeof(sa);
447 sa.bInheritHandle = TRUE;
449 {
450 OutputDebugStringA("[LT] CreatePipe stdout failed\n");
451 return {};
452 }
453 SetHandleInformation(
ltStdoutRd, HANDLE_FLAG_INHERIT, 0);
454
455
457 {
458 OutputDebugStringA("[LT] CreatePipe stdin failed\n");
461 return {};
462 }
463 SetHandleInformation(
ltStdinWr, HANDLE_FLAG_INHERIT, 0);
464
465
468
469 std::ostringstream cmd;
470 cmd << "\"" << nodePath << "\" \"" << ctlPath << "\""
471 << " --port " << port
472 << " --subdomain " << subdomain;
473
474 STARTUPINFOA si{};
475 PROCESS_INFORMATION pi{};
476 si.cb = sizeof(si);
477 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
481 si.wShowWindow = SW_HIDE;
482
483 DWORD flags = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP;
484
485 std::string cmdLine = cmd.str();
486 std::vector<char> mutableCmd(cmdLine.begin(), cmdLine.end());
487 mutableCmd.push_back('\0');
488
489
491
492 BOOL ok = CreateProcessA(
493 nullptr,
494 mutableCmd.data(),
495 nullptr, nullptr,
496 TRUE,
497 flags,
498 nullptr,
499 workingDir.empty() ? nullptr : workingDir.c_str(),
500 &si, &pi);
501
502
505
506 if (!ok)
507 {
508 DWORD err = GetLastError();
510 OutputDebugStringA(em.c_str());
513 return {};
514 }
515
518 running.store(
true, std::memory_order_relaxed);
519
522 {
523 std::string buf; buf.reserve(4096);
524 constexpr DWORD CHUNK = 1024;
525 char tmp[CHUNK + 1];
526
527 auto trimCR = [](std::string& s) {
528 if (!s.empty() && s.back() == '\r') s.pop_back();
529 };
530
531 auto handleJsonLine = [](const std::string& line) {
532 auto findField = [&](const char* key)->std::string {
533 auto kpos = line.find(std::string("\"")+key+"\"");
534 if (kpos == std::string::npos) return {};
535 auto colon = line.find(':', kpos);
536 if (colon == std::string::npos) return {};
537 auto q1 = line.find('"', colon + 1);
538 if (q1 == std::string::npos) return {};
539 auto q2 = line.find('"', q1 + 1);
540 if (q2 == std::string::npos) return {};
541 return line.substr(q1 + 1, q2 - (q1 + 1));
542 };
543
544 const auto ev = findField("event");
545 const auto url = findField("url");
546 const auto msg = findField(
"message");
547
548 if (ev.empty()) {
550 return;
551 }
552
553 if (ev == "boot") {
555 } else if (ev == "require_ok") {
557 } else if (ev == "require_err") {
559 } else if (ev == "ready") {
561 {
565 }
567 } else if (ev == "error") {
569 } else if (ev == "closed") {
571 } else {
573 }
574 };
575
577 DWORD got = 0;
578 BOOL ok = ReadFile(rd, tmp, CHUNK, &got, nullptr);
579 if (!ok || got == 0) break;
580 tmp[got] = '\0';
581 buf.append(tmp, got);
582
583 size_t pos = 0;
584 while (true) {
585 size_t nl = buf.find('\n', pos);
586 if (nl == std::string::npos) break;
587 std::string line = buf.substr(pos, nl - pos);
588 trimCR(line);
589 pos = nl + 1;
590
591 if (!line.empty()) {
592 handleJsonLine(line);
593 }
594 }
595 if (pos > 0) buf.erase(0, pos);
596 }
598 .detach();
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649 std::unique_lock<std::mutex> lk(
urlMutex);
650 bool have =
urlCv.wait_for(lk, std::chrono::seconds(15), []
652
654 }
static void stopLocalTunnel()
static std::condition_variable urlCv
static std::atomic< bool > running
static void closeHandleIf(HANDLE &h)
static void assignToJob(HANDLE process)
static std::string lastErrorToString(DWORD err)
static fs::path getLocalTunnelControllerPath()
static fs::path getNodeExePath()