diff --git a/Lvc/src/Lvc.h b/Lvc/src/Lvc.h --- a/Lvc/src/Lvc.h +++ b/Lvc/src/Lvc.h @@ -1,235 +1,236 @@ #ifndef _Lvc_h_ #define _Lvc_h_ #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/Box.h" #include "lgi/common/Tree.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/TextView3.h" #include "lgi/common/TextLog.h" #include "lgi/common/TabView.h" #include "lgi/common/Edit.h" #include "lgi/common/Menu.h" #include "lgi/common/Ssh.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/StructuredLog.h" #define OPT_Folders "Folders" #define OPT_Folder "Folder" #define METHOD_GetContext "GetContext" #define APP_VERSION "0.8" extern const char *AppName; extern void OpenPatchViewer(LViewI *Parent, LOptionsFile *Opts); enum LvcIcon { IcoFolder, IcoCleanFolder, IcoDirtyFolder, IcoFile, IcoCleanFile, IcoDirtyFile, }; enum LvcNotify { LvcBase = LNotifyUserApp, LvcCommandStart, LvcCommandEnd }; enum FileColumns { COL_CHECKBOX, COL_STATE, COL_FILENAME, }; enum AppIds { IDC_TOOLS_BOX = 100, IDC_FOLDERS_BOX, IDC_COMMITS_BOX, IDC_FILES_BOX, IDC_TXT, IDC_LOG, IDC_MSG_BOX, IDC_TAB_VIEW, IDC_FOLDER_TBL, // Contains the following controls: IDC_TREE, IDC_FILTER_FOLDERS, IDC_CLEAR_FILTER_FOLDERS, IDC_COMMITS_TBL, // Contains the following controls: IDC_LIST, IDC_FILTER_COMMITS, IDC_CLEAR_FILTER_COMMITS, IDC_FILES_TBL, // Contains the following controls: IDC_FILES, IDC_FILTER_FILES, IDC_CLEAR_FILTER_FILES, IDM_ADD_LOCAL = 200, IDM_ADD_REMOTE, + IDM_ADD_DIFF_FILE, IDM_UPDATE, IDM_COPY_REV, IDM_COPY_INDEX, IDM_RENAME_BRANCH, IDM_REMOVE, IDM_CLEAN, IDM_REVERT, IDM_REVERT_TO_REV, IDM_BLAME, IDM_SAVE_AS, IDM_BROWSE, IDM_LOG, IDM_EOL_LF, IDM_EOL_CRLF, IDM_EOL_AUTO, IDM_ADD_FILE, IDM_ADD_BINARY_FILE, IDM_BROWSE_FOLDER, IDM_TERMINAL, IDM_RESOLVE_MARK, IDM_RESOLVE_UNMARK, IDM_RESOLVE_LOCAL, IDM_RESOLVE_INCOMING, IDM_RESOLVE_TOOL, IDM_MERGE, IDM_LOG_FILE, IDM_COPY_PATH, IDM_COPY_LEAF, IDM_EDIT, IDM_REMOTE_URL, }; enum AppMessages { M_DETECT_VCS = M_USER + 100, M_RUN_CMD, M_RESPONSE, M_HANDLE_CALLBACK, }; enum VersionCtrl { VcNone, VcCvs, VcSvn, VcGit, VcHg, VcPending, VcError, VcMax, }; class VcFolder; struct ParseParams { LString Str; LString AltInitPath; class VcLeaf *Leaf = NULL; bool IsWorking = false;; bool Debug = false; std::function Callback; ParseParams(const char *str = NULL) { Str = str; } }; typedef bool (VcFolder::*ParseFn)(int, LString, ParseParams*); class SshConnection; struct AppPriv { VcFolder *CurFolder = NULL; LTree *Tree = NULL; LList *Commits = NULL; LList *Files = NULL; LEdit *Msg = NULL; LTextLog *Diff = NULL; LTextLog *Log = NULL; LTabView *Tabs = NULL; VersionCtrl PrevType = VcNone; LOptionsFile Opts; LStructuredLog sLog; int Resort = -1; // Filtering LString FolderFilter, CommitFilter, FileFilter; LHashTbl,SshConnection*> Connections; AppPriv() : Opts(LOptionsFile::DesktopMode, AppName), sLog("Lvc.slog") { } ~AppPriv(); SshConnection *GetConnection(const char *Uri, bool Create = true); auto Wnd() { return Commits ? Commits->GetWindow() : LAppInst->AppWnd; } void ClearFiles() { if (Files) Files->Empty(); if (Diff) Diff->Name(NULL); } LArray GetRevs(LString::Array &Revs); LString::Array GetCommitRange(); bool IsMenuChecked(int Item) { LMenu *m = Tree->GetWindow()->GetMenu(); LMenuItem *i = m ? m->FindItem(Item) : NULL; return i ? i->Checked() : false; } VersionCtrl DetectVcs(VcFolder *Fld); class VcFile *FindFile(const char *Path); }; #include "SshConnection.h" class BlameUi : public LWindow { struct BlameUiPriv *d; public: BlameUi(AppPriv *priv, VersionCtrl Vc, LString Output); ~BlameUi(); }; class DropDownBtn : public LDropDown, public ResObject { struct DropDownBtnPriv *d; class DropLst *Pu; public: DropDownBtn(); ~DropDownBtn(); LString::Array GetList(); bool SetList(int EditCtrl, LString::Array a); bool OnLayout(LViewLayoutInfo &Inf); }; extern bool ConvertEol(const char *Path, bool Cr); extern int GetEol(const char *Path); extern LString::Array GetProgramsInPath(const char *Program); extern LColour GetPaletteColour(int i); #include "VcFile.h" #include "VcCommit.h" #include "VcFolder.h" #endif diff --git a/Lvc/src/Main.cpp b/Lvc/src/Main.cpp --- a/Lvc/src/Main.cpp +++ b/Lvc/src/Main.cpp @@ -1,1718 +1,1733 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLog.h" #include "lgi/common/Button.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Tree.h" #include "lgi/common/FileSelect.h" #include "lgi/common/StructuredLog.h" #include "Lvc.h" #include "resdefs.h" #ifdef WINDOWS #include "resource.h" #endif ////////////////////////////////////////////////////////////////// const char *AppName = "Lvc"; #define DEFAULT_BUILD_FIX_MSG "Build fix." #define OPT_Hosts "Hosts" #define OPT_Host "Host" AppPriv::~AppPriv() { if (CurFolder) CurFolder->Empty(); } SshConnection *AppPriv::GetConnection(const char *Uri, bool Create) { LUri u(Uri); u.sPath.Empty(); auto s = u.ToString(); auto Conn = Connections.Find(s); if (!Conn && Create) Connections.Add(s, Conn = new SshConnection(Log, s, "matthew@*$ ")); return Conn; } VersionCtrl AppPriv::DetectVcs(VcFolder *Fld) { char p[MAX_PATH_LEN]; LUri u = Fld->GetUri(); if (!u.IsFile() || !u.sPath) { auto c = GetConnection(u.ToString()); if (!c) return VcNone; auto type = c->Types.Find(u.sPath); if (type) return type; c->DetectVcs(Fld); Fld->GetCss(true)->Color(LColour::Blue); Fld->Update(); return VcPending; } auto Path = u.sPath.Get(); #ifdef WINDOWS if (*Path == '/') Path++; #endif if (LMakePath(p, sizeof(p), Path, ".git") && LDirExists(p)) return VcGit; if (LMakePath(p, sizeof(p), Path, ".svn") && LDirExists(p)) return VcSvn; if (LMakePath(p, sizeof(p), Path, ".hg") && LDirExists(p)) return VcHg; if (LMakePath(p, sizeof(p), Path, "CVS") && LDirExists(p)) return VcCvs; return VcNone; } class DiffView : public LTextLog { public: DiffView(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { for (auto ln : LTextView3::Line) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; if (*t == '+') { ln->c = LColour::Green; ln->Back.Rgb(245, 255, 245); } else if (*t == '-') { ln->c = LColour::Red; ln->Back.Rgb(255, 245, 245); } else if (*t == '@') { ln->c.Rgb(128, 128, 128); ln->Back.Rgb(235, 235, 235); } else ln->c = LColour(L_TEXT); } } } }; class ToolBar : public LLayout, public LResourceLoad { public: ToolBar() { LAutoString Name; LRect Pos; if (LoadFromResource(IDD_TOOLBAR, this, &Pos, &Name)) { OnPosChange(); } else LAssert(!"Missing toolbar resource"); } void OnCreate() { AttachChildren(); } void OnPosChange() { LRect Cli = GetClient(); LTableLayout *v; if (GetViewById(IDC_TABLE, v)) { v->SetPos(Cli); v->OnPosChange(); auto r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); // printf("Used = %s\n", r.GetStr()); LCss::Len NewSz(LCss::LenPx, (float)r.Y()+3); auto OldSz = GetCss(true)->Height(); if (OldSz != NewSz) { GetCss(true)->Height(NewSz); SendNotify(LNotifyTableLayoutRefresh); } } else LAssert(!"Missing table ctrl"); } void OnPaint(LSurface *pDC) { pDC->Colour(LColour(L_MED)); pDC->Rectangle(); } }; class CommitCtrls : public LLayout, public LResourceLoad { public: CommitCtrls() { LAutoString Name; LRect Pos; if (LoadFromResource(IDD_COMMIT, this, &Pos, &Name)) { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) { v->GetCss(true)->PaddingRight("8px"); LRect r = v->GetPos(); r.Offset(-r.x1, -r.y1); r.x2++; v->SetPos(r); v->OnPosChange(); r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); GetCss(true)->Height(LCss::Len(LCss::LenPx, (float)r.Y())); } else LAssert(!"Missing table ctrl"); } else LAssert(!"Missing toolbar resource"); } void OnPosChange() { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) v->SetPos(GetClient()); } void OnCreate() { AttachChildren(); } }; LString::Array GetProgramsInPath(const char *Program) { LString::Array Bin; LString Prog = Program; #ifdef WINDOWS Prog += LGI_EXECUTABLE_EXT; #endif LString::Array a = LGetPath(); for (auto p : a) { LFile::Path c(p, Prog); if (c.Exists()) Bin.New() = c.GetFull(); } return Bin; } class OptionsDlg : public LDialog, public LXmlTreeUi { LOptionsFile &Opts; public: OptionsDlg(LViewI *Parent, LOptionsFile &opts) : Opts(opts) { SetParent(Parent); Map("svn-path", IDC_SVN, GV_STRING); Map("svn-limit", IDC_SVN_LIMIT); Map("git-path", IDC_GIT, GV_STRING); Map("git-limit", IDC_GIT_LIMIT); Map("hg-path", IDC_HG, GV_STRING); Map("hg-limit", IDC_HG_LIMIT); Map("cvs-path", IDC_CVS, GV_STRING); Map("cvs-limit", IDC_CVS_LIMIT); if (LoadFromResource(IDD_OPTIONS)) { MoveSameScreen(Parent); Convert(&Opts, this, true); } } void Browse(int EditId) { auto s = new LFileSelect; s->Parent(this); s->Open([this, EditId](auto s, auto status) { if (status) SetCtrlName(EditId, s->Name()); delete s; }); } void BrowseFiles(LViewI *Ctrl, const char *Bin, int EditId) { LRect Pos = Ctrl->GetPos(); LPoint Pt(Pos.x1, Pos.y2 + 1); PointToScreen(Pt); LSubMenu s; LString::Array Bins = GetProgramsInPath(Bin); for (unsigned i=0; i= 1000) { LString Bin = Bins[Cmd - 1000]; if (Bin) SetCtrlName(EditId, Bin); } break; } } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_SVN_BROWSE: BrowseFiles(Ctrl, "svn", IDC_SVN); break; case IDC_GIT_BROWSE: BrowseFiles(Ctrl, "git", IDC_GIT); break; case IDC_HG_BROWSE: BrowseFiles(Ctrl, "hg", IDC_HG); break; case IDC_CVS_BROWSE: BrowseFiles(Ctrl, "cvs", IDC_CVS); break; case IDOK: Convert(&Opts, this, false); // fall case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return LDialog::OnNotify(Ctrl, n); } }; int CommitDataCmp(VcCommit **_a, VcCommit **_b) { auto a = *_a; auto b = *_b; return a->GetTs().Compare(&b->GetTs()); } LString::Array AppPriv::GetCommitRange() { LString::Array r; if (Commits) { LArray Sel; Commits->GetSelection(Sel); if (Sel.Length() > 1) { Sel.Sort(CommitDataCmp); r.Add(Sel[0]->GetRev()); r.Add(Sel.Last()->GetRev()); } else { r.Add(Sel[0]->GetRev()); } } else LAssert(!"No commit list ptr"); return r; } LArray AppPriv::GetRevs(LString::Array &Revs) { LArray a; for (auto i = Commits->begin(); i != Commits->end(); i++) { VcCommit *c = dynamic_cast(*i); if (c) { for (auto r: Revs) { if (r.Equals(c->GetRev())) { a.Add(c); break; } } } } return a; } class CommitList : public LList { public: CommitList(int id) : LList(id, 0, 0, 200, 200) { } void SelectRevisions(LString::Array &Revs, const char *BranchHint = NULL) { VcCommit *Scroll = NULL; LArray Matches; for (auto i: *this) { VcCommit *item = dynamic_cast(i); if (!item) continue; bool IsMatch = false; for (auto r: Revs) { if (item->IsRev(r)) { IsMatch = true; break; } } if (IsMatch) Matches.Add(item); else if (i->Select()) i->Select(false); } for (auto item: Matches) { auto b = item->GetBranch(); if (BranchHint) { if (!b || Stricmp(b, BranchHint)) continue; } else if (b) { continue; } if (!Scroll) Scroll = item; item->Select(true); } if (!Scroll && Matches.Length() > 0) { Scroll = Matches[0]; Scroll->Select(true); } if (Scroll) Scroll->ScrollTo(); } bool OnKey(LKey &k) { switch (k.c16) { case 'p': case 'P': { if (k.Down()) { LArray Sel; GetSelection(Sel); if (Sel.Length()) { auto first = Sel[0]; auto branch = first->GetBranch(); auto p = first->GetParents(); if (p->Length() == 0) break; for (auto c:Sel) c->Select(false); SelectRevisions(*p, branch); } } return true; } case 'c': case 'C': { if (k.Down()) { LArray Sel; GetSelection(Sel); if (Sel.Length()) { LHashTbl,VcCommit*> Map; for (auto s:Sel) Map.Add(s->GetRev(), s); LString::Array n; for (auto it = begin(); it != end(); it++) { VcCommit *c = dynamic_cast(*it); if (c) { for (auto r:*c->GetParents()) { if (Map.Find(r)) { n.Add(c->GetRev()); break; } } } } for (auto c:Sel) c->Select(false); SelectRevisions(n, Sel[0]->GetBranch()); } } return true; } } return LList::OnKey(k); } }; int LstCmp(LListItem *a, LListItem *b, int Col) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if (A == NULL || B == NULL) { return (A ? 1 : -1) - (B ? 1 : -1); } auto f = A->GetFolder(); auto flds = f->GetFields(); if (!flds.Length()) { LgiTrace("%s:%i - No fields?\n", _FL); return 0; } auto fld = flds[Col]; switch (fld) { case LGraph: case LIndex: case LParents: case LRevision: default: return (int) (B->GetIndex() - A->GetIndex()); case LBranch: case LAuthor: case LMessageTxt: return Stricmp(A->GetFieldText(fld), B->GetFieldText(fld)); case LTimeStamp: return B->GetTs().Compare(&A->GetTs()); } return 0; } struct TestThread : public LThread { public: TestThread() : LThread("test") { Run(); } int Main() { auto Path = LGetPath(); LSubProcess p("python", "/Users/matthew/CodeLib/test.py"); auto t = LString(LGI_PATH_SEPARATOR).Join(Path); for (auto s: Path) printf("s: %s\n", s.Get()); p.SetEnvironment("PATH", t); if (p.Start()) { LStringPipe s; p.Communicate(&s); printf("Test: %s\n", s.NewLStr().Get()); } return 0; } }; class RemoteFolderDlg : public LDialog { class App *app; LTree *tree; struct SshHost *root, *newhost; LXmlTreeUi Ui; public: LString Uri; RemoteFolderDlg(App *application); ~RemoteFolderDlg(); int OnNotify(LViewI *Ctrl, LNotification n); }; class VcDiffFile : public LTreeItem { AppPriv *d; LString File; public: VcDiffFile(AppPriv *priv, LString file) : d(priv), File(file) { } const char *GetText(int i = 0) override { return i ? NULL : File.Get(); } LString StripFirst(LString s) { return s.Replace("\\","/").SplitDelimit("/", 1).Last(); } - void Select(bool s) override + void Select(bool selected) override { - LTreeItem::Select(s); - if (s) - { - d->Files->Empty(); - d->Diff->Name(NULL); + LTreeItem::Select(selected); + if (!selected) + return; + + d->Files->Empty(); + d->Diff->Name(NULL); - LFile in(File, O_READ); - LString s = in.Read(); - if (!s) - return; + LFile in(File, O_READ); + LString s = in.Read(); + if (!s) + return; - LString NewLine("\n"); - LString::Array a = s.Replace("\r").Split("\n"); - LArray index; - LString oldName, newName; - LString::Array Diff; - VcFile *f = NULL; - bool InPreamble = false; - bool InDiff = false; - for (unsigned i=0; i index; + LString oldName, newName; + LString::Array Diff; + VcFile *f = NULL; + bool InPreamble = false; + bool InDiff = false; + for (unsigned i=0; iSetDiff(NewLine.Join(Diff)); + f->Select(false); + } + + Diff.Empty(); + oldName.Empty(); + newName.Empty(); + InDiff = false; + InPreamble = true; + } + else if (!Strnicmp(Ln, "Index", 5)) + { + if (InPreamble) + index = a[i].SplitDelimit(": ", 1).Slice(1); + } + else if (!strncmp(Ln, "--- ", 4)) { - const char *Ln = a[i]; - if (!Strnicmp(Ln, "diff ", 5)) + auto p = a[i].SplitDelimit(" \t", 1); + if (p.Length() > 1) + oldName = p[1]; + } + else if (!strncmp(Ln, "+++ ", 4)) + { + auto p = a[i].SplitDelimit(" \t", 1); + if (p.Length() > 1) + newName = p[1]; + + if (oldName && newName) { - if (f) + InDiff = true; + InPreamble = false; + + f = d->FindFile(newName); + if (!f) + f = new VcFile(d, NULL, LString(), false); + + const char *nullFn = "dev/null"; + auto Path = StripFirst(oldName); + f->SetUri(LString("file:///") + Path); + if (newName.Find(nullFn) >= 0) { - f->SetDiff(NewLine.Join(Diff)); - f->Select(false); + // Delete + f->SetText(Path, COL_FILENAME); + f->SetText("D", COL_STATE); + } + else + { + f->SetText(Path, COL_FILENAME); + if (oldName.Find(nullFn) >= 0) + // Add + f->SetText("A", COL_STATE); + else + // Modify + f->SetText("M", COL_STATE); } - Diff.Empty(); - oldName.Empty(); - newName.Empty(); - InDiff = false; - InPreamble = true; - } - else if (!Strnicmp(Ln, "Index", 5)) - { - if (InPreamble) - index = a[i].SplitDelimit(": ", 1).Slice(1); - } - else if (!strncmp(Ln, "--- ", 4)) - { - auto p = a[i].SplitDelimit(" \t", 1); - if (p.Length() > 1) - oldName = p[1]; - } - else if (!strncmp(Ln, "+++ ", 4)) - { - auto p = a[i].SplitDelimit(" \t", 1); - if (p.Length() > 1) - newName = p[1]; - - if (oldName && newName) - { - InDiff = true; - InPreamble = false; - - f = d->FindFile(newName); - if (!f) - f = new VcFile(d, NULL, LString(), false); - - const char *nullFn = "dev/null"; - if (newName.Find(nullFn) >= 0) - { - // Delete - f->SetText(StripFirst(oldName), COL_FILENAME); - f->SetText("D", COL_STATE); - } - else - { - f->SetText(StripFirst(newName), COL_FILENAME); - if (oldName.Find(nullFn) >= 0) - // Add - f->SetText("A", COL_STATE); - else - // Modify - f->SetText("M", COL_STATE); - } - - f->GetStatus(); - d->Files->Insert(f); - } - } - else if (!_strnicmp(Ln, "------", 6)) - { - InPreamble = !InPreamble; - } - else if (!_strnicmp(Ln, "======", 6)) - { - InPreamble = false; - InDiff = true; - } - else if (InDiff) - { - Diff.Add(a[i]); + f->GetStatus(); + d->Files->Insert(f); } } - if (f && Diff.Length()) + else if (!_strnicmp(Ln, "------", 6)) + { + InPreamble = !InPreamble; + } + else if (!_strnicmp(Ln, "======", 6)) { - f->SetDiff(NewLine.Join(Diff)); - Diff.Empty(); + InPreamble = false; + InDiff = true; } + else if (InDiff) + { + Diff.Add(a[i]); + } + } + if (f && Diff.Length()) + { + f->SetDiff(NewLine.Join(Diff)); + Diff.Empty(); } } }; class App : public LWindow, public AppPriv { LAutoPtr ImgLst; LBox *FoldersBox = NULL; bool CallMethod(const char *MethodName, LScriptArguments &Args) { if (!Stricmp(MethodName, METHOD_GetContext)) { *Args.GetReturn() = (AppPriv*)this; return true; } return false; } public: App() { LString AppRev; AppRev.Printf("%s v%s", AppName, APP_VERSION); Name(AppRev); LRect r(0, 0, 1400, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); Opts.SerializeFile(false); SerializeState(&Opts, "WndPos", true); #ifdef WINDOWS SetIcon(MAKEINTRESOURCEA(IDI_ICON1)); #else SetIcon("icon32.png"); #endif ImgLst.Reset(LLoadImageList("image-list.png", 16, 16)); if (Attach(0)) { if ((Menu = new LMenu)) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } LBox *ToolsBox = new LBox(IDC_TOOLS_BOX, true, "ToolsBox"); FoldersBox = new LBox(IDC_FOLDERS_BOX, false, "FoldersBox"); LBox *CommitsBox = new LBox(IDC_COMMITS_BOX, true, "CommitsBox"); ToolBar *Tools = new ToolBar; ToolsBox->Attach(this); Tools->Attach(ToolsBox); FoldersBox->Attach(ToolsBox); auto FolderLayout = new LTableLayout(IDC_FOLDER_TBL); auto c = FolderLayout->GetCell(0, 0, true, 2); Tree = new LTree(IDC_TREE, 0, 0, 320, 200); Tree->SetImageList(ImgLst, false); Tree->ShowColumnHeader(true); Tree->AddColumn("Folder", 250); Tree->AddColumn("Counts", 50); c->Add(Tree); c = FolderLayout->GetCell(0, 1); c->Add(new LEdit(IDC_FILTER_FOLDERS, 0, 0, -1, -1)); c = FolderLayout->GetCell(1, 1); c->Add(new LButton(IDC_CLEAR_FILTER_FOLDERS, 0, 0, -1, -1, "x")); FolderLayout->Attach(FoldersBox); CommitsBox->Attach(FoldersBox); auto CommitsLayout = new LTableLayout(IDC_COMMITS_TBL); c = CommitsLayout->GetCell(0, 0, true, 2); Commits = new CommitList(IDC_LIST); c->Add(Commits); c = CommitsLayout->GetCell(0, 1); c->Add(new LEdit(IDC_FILTER_COMMITS, 0, 0, -1, -1)); c = CommitsLayout->GetCell(1, 1); c->Add(new LButton(IDC_CLEAR_FILTER_COMMITS, 0, 0, -1, -1, "x")); CommitsLayout->Attach(CommitsBox); CommitsLayout->GetCss(true)->Height("40%"); LBox *FilesBox = new LBox(IDC_FILES_BOX, false); FilesBox->Attach(CommitsBox); auto FilesLayout = new LTableLayout(IDC_FILES_TBL); c = FilesLayout->GetCell(0, 0, true, 2); Files = new LList(IDC_FILES, 0, 0, 200, 200); Files->AddColumn("[ ]", 30); Files->AddColumn("State", 100); Files->AddColumn("Name", 400); c->Add(Files); c = FilesLayout->GetCell(0, 1); c->Add(new LEdit(IDC_FILTER_FILES, 0, 0, -1, -1)); c = FilesLayout->GetCell(1, 1); c->Add(new LButton(IDC_CLEAR_FILTER_FILES, 0, 0, -1, -1, "x")); FilesLayout->GetCss(true)->Width("35%"); FilesLayout->Attach(FilesBox); LBox *MsgBox = new LBox(IDC_MSG_BOX, true); MsgBox->Attach(FilesBox); CommitCtrls *Commit = new CommitCtrls; Commit->Attach(MsgBox); Commit->GetCss(true)->Height("25%"); if (Commit->GetViewById(IDC_MSG, Msg)) { LTextView3 *Tv = dynamic_cast(Msg); if (Tv) { Tv->Sunken(true); Tv->SetWrapType(TEXTED_WRAP_NONE); } } else LAssert(!"No ctrl?"); Tabs = new LTabView(IDC_TAB_VIEW); Tabs->Attach(MsgBox); const char *Style = "Padding: 0px 8px 8px 0px"; Tabs->GetCss(true)->Parse(Style); LTabPage *p = Tabs->Append("Diff"); p->Append(Diff = new DiffView(IDC_TXT)); // Diff->Sunken(true); Diff->SetWrapType(TEXTED_WRAP_NONE); p = Tabs->Append("Log"); p->Append(Log = new LTextLog(IDC_LOG)); // Log->Sunken(true); Log->SetWrapType(TEXTED_WRAP_NONE); SetCtrlValue(IDC_UPDATE, true); AttachChildren(); Visible(true); } LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) { Opts.CreateTag(OPT_Folders); f = Opts.LockTag(OPT_Folders, _FL); } if (f) { bool Req[VcMax] = {0}; for (auto c: f->Children) { if (c->IsTag(OPT_Folder)) { auto f = new VcFolder(this, c); Tree->Insert(f); if (!Req[f->GetType()]) { Req[f->GetType()] = true; f->GetVersion(); } } } Opts.Unlock(); LRect Large(0, 0, 2000, 200); Tree->SetPos(Large); Tree->ResizeColumnsToContent(); LItemColumn *c; int i = 0, px = 0; while ((c = Tree->ColumnAt(i++))) { px += c->Width(); } FoldersBox->Value(MAX(320, px + 20)); // new TestThread(); } SetPulse(200); DropTarget(true); } ~App() { SerializeState(&Opts, "WndPos", false); SaveFolders(); } void SaveFolders() { LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) return; f->EmptyChildren(); for (auto i: *Tree) { VcFolder *vcf = dynamic_cast(i); if (vcf) f->InsertTag(vcf->Save()); } Opts.Unlock(); Opts.SerializeFile(true); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_RESPONSE: { SshConnection::HandleMsg(Msg); break; } case M_HANDLE_CALLBACK: { LAutoPtr Pc((ProcessCallback*)Msg->A()); if (Pc) Pc->OnComplete(); break; } } return LWindow::OnEvent(Msg); } void OnReceiveFiles(LArray &Files) { for (auto f : Files) { if (LDirExists(f)) OpenLocalFolder(f); } } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_PATCH_VIEWER: { OpenPatchViewer(this, &Opts); break; } case IDM_OPEN_LOCAL: { OpenLocalFolder(); break; } case IDM_OPEN_REMOTE: { OpenRemoteFolder(); break; } case IDM_OPEN_DIFF: { auto s = new LFileSelect; s->Parent(this); s->Open([this](auto dlg, auto status) { if (status) OpenDiff(dlg->Name()); delete dlg; }); break; } case IDM_OPTIONS: { auto Dlg = new OptionsDlg(this, Opts); Dlg->DoModal([](auto dlg, auto ctrlId) { delete dlg; }); break; } case IDM_FIND: { auto i = new LInput(this, "", "Search string:"); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId == IDOK) { LString::Array Revs; Revs.Add(i->GetStr()); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } delete dlg; }); break; } case IDM_UNTRACKED: { auto mi = GetMenu()->FindItem(IDM_UNTRACKED); if (!mi) break; mi->Checked(!mi->Checked()); LArray Flds; Tree->GetSelection(Flds); for (auto f : Flds) { f->Refresh(); } break; } case IDM_REFRESH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Refresh(); break; } case IDM_PULL: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Pull(); break; } case IDM_PUSH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Push(); break; } case IDM_STATUS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->FolderStatus(); break; } case IDM_UPDATE_SUBS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->UpdateSubs(); break; break; } case IDM_EXIT: { LCloseApp(); break; } } return 0; } void OnPulse() { for (auto i:*Tree) { VcFolder *vcf = dynamic_cast(i); if (vcf) vcf->OnPulse(); } } void OpenLocalFolder(const char *Fld = NULL) { auto Load = [this](const char *Fld) { // Check the folder isn't already loaded... bool Has = false; LArray Folders; Tree->GetAll(Folders); for (auto f: Folders) { if (f->GetUri().IsFile() && !Stricmp(f->LocalPath(), Fld)) { Has = true; break; } } if (!Has) { LUri u; u.SetFile(Fld); auto f = new VcFolder(this, u.ToString()); if (f) { Tree->Insert(f); SaveFolders(); } } }; if (!Fld) { auto s = new LFileSelect; s->Parent(this); s->OpenFolder([this, Load](auto s, auto status) { if (status) Load(s->Name()); delete s; }); } else Load(Fld); } void OpenRemoteFolder() { auto Dlg = new RemoteFolderDlg(this); Dlg->DoModal([this, Dlg](auto dlg, auto status) { if (status) { Tree->Insert(new VcFolder(this, Dlg->Uri)); SaveFolders(); } delete dlg; }); } void OpenDiff(const char *File) { Tree->Insert(new VcDiffFile(this, File)); } void OnFilterFolders() { if (!Tree) return; for (auto i = Tree->GetChild(); i; i = i->GetNext()) { auto n = i->GetText(); bool vis = !FolderFilter || Stristr(n, FolderFilter.Get()) != NULL; i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); } Tree->UpdateAllItems(); Tree->Invalidate(); } void OnFilterCommits() { if (!Commits) return; LArray a; if (!Commits->GetAll(a)) return; auto cols = Commits->GetColumns(); for (auto i: a) { bool vis = !CommitFilter; for (int c=1; !vis && cGetText(c); if (Stristr(txt, CommitFilter.Get())) vis = true; } i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); } Commits->UpdateAllItems(); Commits->Invalidate(); } void OnFilterFiles() { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->FilterCurrentFiles(); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_CLEAR_FILTER_FOLDERS: { SetCtrlName(IDC_FILTER_FOLDERS, NULL); // Fall through } case IDC_FILTER_FOLDERS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FOLDERS, NULL); LString n = GetCtrlName(IDC_FILTER_FOLDERS); if (n != FolderFilter) { FolderFilter = n; OnFilterFolders(); } break; } case IDC_CLEAR_FILTER_COMMITS: { SetCtrlName(IDC_FILTER_COMMITS, NULL); // Fall through } case IDC_FILTER_COMMITS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_COMMITS, NULL); LString n = GetCtrlName(IDC_FILTER_COMMITS); if (n != CommitFilter) { CommitFilter = n; OnFilterCommits(); } break; } case IDC_CLEAR_FILTER_FILES: { SetCtrlName(IDC_FILTER_FILES, NULL); // Fall through } case IDC_FILTER_FILES: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FILES, NULL); LString n = GetCtrlName(IDC_FILTER_FILES); if (n != FileFilter) { FileFilter = n; OnFilterFiles(); } break; } case IDC_FILES: { switch (n.Type) { case LNotifyItemColumnClicked: { int Col = -1; LMouse m; if (Files->GetColumnClickInfo(Col, m)) { if (Col == 0) { // Select / deselect all check boxes.. List n; if (Files->GetAll(n)) { bool Checked = false; for (auto f: n) Checked |= f->Checked() > 0; for (auto f: n) f->Checked(Checked ? 0 : 1); } } } break; } default: break; } break; } case IDC_OPEN: { OpenLocalFolder(); break; } case IDC_TREE: { switch (n.Type) { case LNotifyContainerClick: { LMouse m; c->GetMouse(m); if (m.Right()) { LSubMenu s; s.AppendItem("Add Local", IDM_ADD_LOCAL); s.AppendItem("Add Remote", IDM_ADD_REMOTE); + s.AppendItem("Add Diff File", IDM_ADD_DIFF_FILE); int Cmd = s.Float(c->GetGView(), m); switch (Cmd) { case IDM_ADD_LOCAL: { OpenLocalFolder(); break; } case IDM_ADD_REMOTE: { OpenRemoteFolder(); break; } + case IDM_ADD_DIFF_FILE: + { + auto s = new LFileSelect; + s->Parent(this); + s->Open([this](auto dlg, auto status) + { + if (status) + OpenDiff(dlg->Name()); + delete dlg; + }); + break; + } } } break; } case (LNotifyType)LvcCommandStart: { SetCtrlEnabled(IDC_PUSH, false); SetCtrlEnabled(IDC_PULL, false); SetCtrlEnabled(IDC_PULL_ALL, false); break; } case (LNotifyType)LvcCommandEnd: { SetCtrlEnabled(IDC_PUSH, true); SetCtrlEnabled(IDC_PULL, true); SetCtrlEnabled(IDC_PULL_ALL, true); break; } default: break; } break; } case IDC_COMMIT_AND_PUSH: case IDC_COMMIT: { auto BuildFix = GetCtrlValue(IDC_BUILD_FIX); const char *Msg = GetCtrlName(IDC_MSG); if (BuildFix || ValidStr(Msg)) { auto Sel = Tree->Selection(); if (Sel) { VcFolder *f = dynamic_cast(Sel); if (!f) { for (auto p = Sel->GetParent(); p; p = p->GetParent()) { f = dynamic_cast(p); if (f) break; } } if (f) { auto Branch = GetCtrlName(IDC_BRANCH); bool AndPush = c->GetId() == IDC_COMMIT_AND_PUSH; f->Commit(BuildFix ? DEFAULT_BUILD_FIX_MSG : Msg, ValidStr(Branch) ? Branch : NULL, AndPush); } } } else LgiMsg(this, "No message for commit.", AppName); break; } case IDC_PUSH: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Push(); break; } case IDC_PULL: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Pull(); break; } case IDC_PULL_ALL: { LArray Folders; Tree->GetAll(Folders); bool AndUpdate = GetCtrlValue(IDC_UPDATE) != 0; for (auto f : Folders) { f->Pull(AndUpdate, LogSilo); } break; } case IDC_STATUS: { LArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->FolderStatus(); } break; } case IDC_HEADS: { if (n.Type == LNotifyValueChanged) { auto Revs = LString(c->Name()).SplitDelimit(); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } break; } case IDC_LIST: { switch (n.Type) { case LNotifyItemColumnClicked: { int Col = -1; LMouse Ms; Commits->GetColumnClickInfo(Col, Ms); Commits->Sort(LstCmp, Col); break; } case LNotifyItemDoubleClick: { VcFolder *f = dynamic_cast(Tree->Selection()); if (!f) break; LArray s; if (Commits->GetSelection(s) && s.Length() == 1) f->OnUpdate(s[0]->GetRev()); break; } default: break; } break; } } return 0; } }; struct SshHost : public LTreeItem { LXmlTag *t; LString Host, User, Pass; SshHost(LXmlTag *tag = NULL) { t = tag; if (t) { Serialize(false); SetText(Host); } } void Serialize(bool WriteToTag) { if (WriteToTag) { LUri u; u.sProtocol = "ssh"; u.sHost = Host; u.sUser = User; u.sPass = Pass; t->SetContent(u.ToString()); } else { LUri u(t->GetContent()); if (!Stricmp(u.sProtocol.Get(), "ssh")) { Host = u.sHost; User = u.sUser; Pass = u.sPass; } } } }; RemoteFolderDlg::RemoteFolderDlg(App *application) : app(application), root(NULL), newhost(NULL), tree(NULL) { SetParent(app); LoadFromResource(IDD_REMOTE_FOLDER); if (GetViewById(IDC_HOSTS, tree)) { printf("tree=%p\n", tree); tree->Insert(root = new SshHost()); root->SetText("Ssh Hosts"); } else return; LViewI *v; if (GetViewById(IDC_HOSTNAME, v)) v->Focus(true); Ui.Map("Host", IDC_HOSTNAME); Ui.Map("User", IDC_USER); Ui.Map("Password", IDC_PASS); LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (hosts) { SshHost *h; for (auto c: hosts->Children) if (c->IsTag(OPT_Host) && (h = new SshHost(c))) root->Insert(h); app->Opts.Unlock(); } root->Insert(newhost = new SshHost()); newhost->SetText("New Host"); root->Expanded(true); newhost->Select(true); } RemoteFolderDlg::~RemoteFolderDlg() { } int RemoteFolderDlg::OnNotify(LViewI *Ctrl, LNotification n) { SshHost *cur = tree ? dynamic_cast(tree->Selection()) : NULL; #define CHECK_SPECIAL() \ if (cur == newhost) \ { \ root->Insert(cur = new SshHost()); \ cur->Select(true); \ } \ if (cur == root) \ break; switch (Ctrl->GetId()) { case IDC_HOSTS: { switch (n.Type) { case LNotifyItemSelect: { bool isRoot = cur == root; SetCtrlEnabled(IDC_HOSTNAME, !isRoot); SetCtrlEnabled(IDC_USER, !isRoot); SetCtrlEnabled(IDC_PASS, !isRoot); SetCtrlEnabled(IDC_DELETE, !isRoot && !(cur == newhost)); SetCtrlName(IDC_HOSTNAME, cur ? cur->Host.Get() : NULL); SetCtrlName(IDC_USER, cur ? cur->User.Get() : NULL); SetCtrlName(IDC_PASS, cur ? cur->Pass.Get() : NULL); break; } default: break; } break; } case IDC_HOSTNAME: { CHECK_SPECIAL() if (cur) { cur->Host = Ctrl->Name(); cur->SetText(cur->Host ? cur->Host : ""); } break; } case IDC_DELETE: { auto sel = tree ? dynamic_cast(tree->Selection()) : NULL; if (!sel) break; LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (!hosts) { LAssert(!"Couldn't lock tag."); break; } if (hosts->Children.HasItem(sel->t)) { sel->t->RemoveTag(); DeleteObj(sel->t); delete sel; } app->Opts.Unlock(); break; } case IDC_USER: { CHECK_SPECIAL() if (cur) cur->User = Ctrl->Name(); break; } case IDC_PASS: { CHECK_SPECIAL() if (cur) cur->Pass = Ctrl->Name(); break; } case IDOK: { LXmlTag *hosts; if (!(hosts = app->Opts.LockTag(OPT_Hosts, _FL))) { if (!(app->Opts.CreateTag(OPT_Hosts) && (hosts = app->Opts.LockTag(OPT_Hosts, _FL)))) break; } LAssert(hosts != NULL); for (auto i = root->GetChild(); i; i = i->GetNext()) { SshHost *h = dynamic_cast(i); if (!h || h == newhost) continue; if (h->t) ; else if ((h->t = new LXmlTag(OPT_Host))) hosts->InsertTag(cur->t); else return false; h->Serialize(true); } app->Opts.Unlock(); LUri u; u.sProtocol = "ssh"; u.sHost = GetCtrlName(IDC_HOSTNAME); u.sUser = GetCtrlName(IDC_USER); u.sPass = GetCtrlName(IDC_PASS); u.sPath = GetCtrlName(IDC_REMOTE_PATH); Uri = u.ToString(); // Fall through } case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return 0; } ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, AppName); if (a.IsOk()) { // LStructuredLog::UnitTest(); a.AppWnd = new App; a.Run(); DeleteObj(a.AppWnd); } LAssert(VcCommit::Instances == 0); return 0; } diff --git a/src/common/Net/Net.cpp b/src/common/Net/Net.cpp --- a/src/common/Net/Net.cpp +++ b/src/common/Net/Net.cpp @@ -1,2041 +1,2126 @@ /*hdr ** FILE: INet.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Internet sockets ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 #if defined(LINUX) #include #include #include #include #elif defined(MAC) #include #include #include #include #include #endif #include #include "lgi/common/File.h" #include "lgi/common/Net.h" #include "lgi/common/LgiString.h" #include "lgi/common/LgiCommon.h" #include "LgiOsClasses.h" #include "lgi/common/RegKey.h" #define USE_BSD_SOCKETS 1 #define DEBUG_CONNECT 0 #define ETIMEOUT 400 #define PROTO_UDP 0x100 #define PROTO_BROADCAST 0x200 #if defined WIN32 #include #include #include #include typedef HOSTENT HostEnt; typedef int socklen_t; typedef unsigned long in_addr_t; typedef BOOL option_t; #define LPrintSock "%I64x" #define MSG_NOSIGNAL 0 #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif #ifndef EISCONN #define EISCONN WSAEISCONN #endif #define OsAddr S_un.S_addr #elif defined POSIX #include #include #include #include #include #include #include #include #include #include #define SOCKET_ERROR -1 #define LPrintSock "%x" typedef hostent HostEnt; typedef int option_t; #define OsAddr s_addr #endif /////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 static bool SocketsOpen = false; #endif bool StartNetworkStack() { #ifdef WIN32 #ifndef WINSOCK_VERSION #define WINSOCK_VERSION MAKEWORD(2,2) #endif if (!SocketsOpen) { // Start sockets WSADATA WsaData; int Result = WSAStartup(WINSOCK_VERSION, &WsaData); if (!Result) { if (WsaData.wVersion == WINSOCK_VERSION) { SocketsOpen = Result == 0; } else { WSACleanup(); return false; } } } #endif return true; } void StopNetworkStack() { #ifdef WIN32 if (SocketsOpen) { WSACleanup(); SocketsOpen = false; } #endif } #ifdef WIN32 #include "..\..\Win\INet\MibAccess.h" #include #pragma comment(lib, "iphlpapi.lib") #endif bool LSocket::EnumInterfaces(LArray &Out) { bool Status = false; StartNetworkStack(); #ifdef WIN32 #if 0 PMIB_IF_TABLE2 Tbl; SecureZeroMemory(&Tbl, sizeof(Tbl)); auto r = GetIfTable2(&Tbl); for (size_t i=0; iNumEntries; i++) { auto &e = Tbl->Table[i]; WCHAR w[256] = {0}; r = ConvertInterfaceLuidToNameW(&e.InterfaceLuid, w, 256); MIB_UNICASTIPADDRESS_ROW Addr; InitializeUnicastIpAddressEntry(&Addr); Addr.Address.si_family = AF_INET; Addr.InterfaceLuid = e.InterfaceLuid; Addr.InterfaceIndex = e.InterfaceIndex; r = GetUnicastIpAddressEntry(&Addr); if (r == NO_ERROR) { int asd=0; } } FreeMibTable(Tbl); #else MibII m; m.Init(); MibInterface Intf[16] = {0}; int Count = m.GetInterfaces(Intf, CountOf(Intf)); if (Count) { for (int i=0; iifa_next) { if (a->ifa_addr && a->ifa_addr->sa_family == AF_INET) { sockaddr_in *in = (sockaddr_in*)a->ifa_addr; sockaddr_in *mask = (sockaddr_in*)a->ifa_netmask; auto &Intf = Out.New(); Intf.Ip4 = ntohl(in->sin_addr.s_addr); Intf.Netmask4 = ntohl(mask->sin_addr.s_addr); Intf.Name = a->ifa_name; Status = true; } } freeifaddrs(addrs); } #endif return Status; } //////////////////////////////////////////////////////////////////////////////////////////////// class LSocketImplPrivate : public LCancel { public: // Data int Blocking : 1; int NoDelay : 1; int Udp : 1; int Broadcast : 1; int LogType = NET_LOG_NONE; LString LogFile; int Timeout = -1; OsSocket Socket = INVALID_SOCKET; int LastError = 0; LCancel *Cancel = NULL; LString ErrStr; LSocketImplPrivate() { Cancel = this; Blocking = true; NoDelay = false; Udp = false; Broadcast = false; } ~LSocketImplPrivate() { } bool Select(int TimeoutMs, bool Read) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = Socket; if (ValidSocket(s) && !Cancel->IsCancelled()) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set set; FD_ZERO(&set); FD_SET(s, &set); int ret = select( (int)s+1, Read ? &set : NULL, !Read ? &set : NULL, NULL, TimeoutMs >= 0 ? &t : NULL); if (ret > 0 && FD_ISSET(s, &set)) { return true; } } return false; } // This is the timing granularity of the SelectWithCancel loop. Ie the // number of milliseconds between checking the Cancel object. constexpr static int CancelCheckMs = 50; bool SelectWithCancel(int TimeoutMs, bool Read) { if (!Cancel) // Regular select where we just wait the whole timeout... return Select(TimeoutMs, false); // Because select can't check out 'Cancel' value during the waiting we run the select // call in a much smaller timeout and a loop so that we can respond to Cancel being set // in a timely manner. auto Now = LCurrentTime(); auto End = Now + TimeoutMs; do { // Do the cancel check... if (Cancel->IsCancelled()) break; // How many ms to wait? Now = LCurrentTime(); auto Remain = MIN(CancelCheckMs, (int)(End - Now)); if (Remain <= 0) break; if (Select(Remain, Read)) return true; } while (Now < End); return false; } }; LSocket::LSocket(LStreamI *logger, void *unused_param) { StartNetworkStack(); BytesWritten = 0; BytesRead = 0; d = new LSocketImplPrivate; } LSocket::~LSocket() { Close(); DeleteObj(d); } bool LSocket::IsOK() { return #ifndef __llvm__ this != 0 && #endif d != 0; } LCancel *LSocket::GetCancel() { return d->Cancel; } void LSocket::SetCancel(LCancel *c) { d->Cancel = c; } void LSocket::OnDisconnect() { } OsSocket LSocket::ReleaseHandle() { auto h = d->Socket; d->Socket = INVALID_SOCKET; return h; } OsSocket LSocket::Handle(OsSocket Set) { if (Set != INVALID_SOCKET) { d->Socket = Set; } return d->Socket; } bool LSocket::IsOpen() { if (ValidSocket(d->Socket) && !d->Cancel->IsCancelled()) { return true; } return false; } int LSocket::GetTimeout() { return d->Timeout; } void LSocket::SetTimeout(int ms) { d->Timeout = ms; } bool LSocket::IsReadable(int TimeoutMs) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = d->Socket; if (ValidSocket(s) && !d->Cancel->IsCancelled()) { #ifdef LINUX // Because Linux doesn't return from select() when the socket is // closed elsewhere we have to do something different... damn Linux, // why can't you just like do the right thing? struct pollfd fds; fds.fd = s; fds.events = POLLIN | POLLRDHUP | POLLERR; fds.revents = 0; int r = poll(&fds, 1, TimeoutMs); if (r > 0) { return fds.revents != 0; } else if (r < 0) { Error(); } #else struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { return true; } else if (v < 0) { Error(); } #endif } else LgiTrace("%s:%i - Not a valid socket.\n", _FL); return false; } bool LSocket::IsWritable(int TimeoutMs) { return d->SelectWithCancel(TimeoutMs, false); } bool LSocket::CanAccept(int TimeoutMs) { return IsReadable(TimeoutMs); } bool LSocket::IsBlocking() { return d->Blocking != 0; } void LSocket::IsBlocking(bool block) { if (d->Blocking ^ block) { d->Blocking = block; #if defined WIN32 ulong NonBlocking = !block; ioctlsocket(d->Socket, FIONBIO, &NonBlocking); #elif defined POSIX fcntl(d->Socket, F_SETFL, d->Blocking ? 0 : O_NONBLOCK); #else #error Impl me. #endif } } bool LSocket::IsDelayed() { return !d->NoDelay; } void LSocket::IsDelayed(bool Delay) { bool NoDelay = !Delay; if (d->NoDelay ^ NoDelay) { d->NoDelay = NoDelay; option_t i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&i, sizeof(i)); } } int LSocket::GetLocalPort() { struct sockaddr_in addr; socklen_t size; ZeroObj(addr); size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) >= 0) { return ntohs(addr.sin_port); } return 0; } bool LSocket::GetLocalIp(char *IpAddr) { if (IpAddr) { struct sockaddr_in addr; socklen_t size; size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) < 0) return false; if (addr.sin_addr.s_addr == INADDR_ANY) return false; uchar *a = (uchar*)&addr.sin_addr.s_addr; sprintf_s( IpAddr, 16, "%i.%i.%i.%i", a[0], a[1], a[2], a[3]); return true; } return false; } bool LSocket::GetRemoteIp(uint32_t *IpAddr) { if (IpAddr) { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { *IpAddr = ntohl(a.sin_addr.s_addr); return true; } } return false; } bool LSocket::GetRemoteIp(char *IpAddr) { if (!IpAddr) return false; uint32_t Ip = 0; if (!GetRemoteIp(&Ip)) return false; sprintf_s( IpAddr, 16, "%u.%u.%u.%u", (Ip >> 24) & 0xff, (Ip >> 16) & 0xff, (Ip >> 8) & 0xff, (Ip) & 0xff); return false; } int LSocket::GetRemotePort() { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { return a.sin_port; } return 0; } int LSocket::Open(const char *HostAddr, int Port) { int Status = -1; Close(); if (HostAddr) { BytesWritten = 0; BytesRead = 0; sockaddr_in RemoteAddr; HostEnt *Host = 0; in_addr_t IpAddress = 0; ZeroObj(RemoteAddr); #ifdef WIN32 d->Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); #else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); #endif if (ValidSocket(d->Socket)) { LArray Buf(512); #if !defined(MAC) option_t i; socklen_t sz = sizeof(i); int r = getsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char*)&i, &sz); if (d->NoDelay ^ i) { i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char *) &i, sizeof(i)); } #endif if (IsDigit(*HostAddr) && strchr(HostAddr, '.')) { // Ip address // IpAddress = inet_addr(HostAddr); if (!inet_pton(AF_INET, HostAddr, &IpAddress)) { Error(); return 0; } /* This seems complete unnecessary? -fret Dec 2018 #if defined(WIN32) Host = c((const char*) &IpAddress, 4, AF_INET); if (!Host) Error(); #else Host = gethostbyaddr ( #ifdef MAC HostAddr, #else &IpAddress, #endif 4, AF_INET ); #endif */ } else { // Name address #ifdef LINUX Host = new HostEnt; if (Host) { memset(Host, 0, sizeof(*Host)); HostEnt *Result = 0; int Err = 0; int Ret; while ( ( !GetCancel() || !GetCancel()->IsCancelled() ) && ( Ret = gethostbyname_r(HostAddr, Host, &Buf[0], Buf.Length(), &Result, &Err) ) == ERANGE ) { Buf.Length(Buf.Length() << 1); } if (Ret) { auto ErrStr = LErrorCodeToString(Err); printf("%s:%i - gethostbyname_r('%s') returned %i, %i, %s\n", _FL, HostAddr, Ret, Err, ErrStr.Get()); DeleteObj(Host); } } #if DEBUG_CONNECT printf("%s:%i - Host=%p\n", __FILE__, __LINE__, Host); #endif #else Host = gethostbyname(HostAddr); #endif if (!Host) { Error(); Close(); return false; } } if (1) { RemoteAddr.sin_family = AF_INET; RemoteAddr.sin_port = htons(Port); if (Host) { if (Host->h_addr_list && Host->h_addr_list[0]) { memcpy(&RemoteAddr.sin_addr, Host->h_addr_list[0], sizeof(in_addr) ); } else return false; } else { memcpy(&RemoteAddr.sin_addr, &IpAddress, sizeof(IpAddress) ); } #ifdef WIN32 if (d->Timeout < 0) { // Do blocking connect Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); } else #endif { #define CONNECT_LOGGING 0 // Setup the connect bool Block = IsBlocking(); if (Block) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Setting non blocking\n", d->Socket); #endif IsBlocking(false); } // Do initial connect to kick things off.. #if CONNECT_LOGGING LgiTrace(LPrintSock " - Doing initial connect to %s:%i\n", d->Socket, HostAddr, Port); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Initial connect=%i Block=%i\n", d->Socket, Status, Block); #endif // Wait for the connect to finish? if (Status && Block) { Error(Host); #ifdef WIN32 // yeah I know... wtf? (http://itamarst.org/writings/win32sockets.html) #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == WSAEINVAL || d->LastError == WSAEWOULDBLOCK) #else #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == EINPROGRESS) #endif #if CONNECT_LOGGING LgiTrace(LPrintSock " - IsWouldBlock()=%i d->LastError=%i\n", d->Socket, IsWouldBlock(), d->LastError); #endif int64 End = LCurrentTime() + (d->Timeout > 0 ? d->Timeout : 30000); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket) && IsWouldBlock()) { int64 Remaining = End - LCurrentTime(); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Remaining " LPrintfInt64 "\n", d->Socket, Remaining); #endif if (Remaining < 0) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Leaving loop\n", d->Socket); #endif break; } if (IsWritable((int)MIN(Remaining, 1000))) { // Should be ready to connect now... #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect...\n", d->Socket); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect=%i\n", d->Socket, Status); #endif if (Status != 0) { Error(Host); if (d->LastError == EISCONN #ifdef WIN32 || d->LastError == WSAEISCONN // OMG windows, really? #endif ) { Status = 0; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connect=%i Err=%i\n", d->Socket, Status, d->LastError); #endif if (IsWouldBlock()) continue; } break; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connected...\n", d->Socket); #endif break; } } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Timout...\n", d->Socket); #endif } } } if (Block) IsBlocking(true); } if (!Status) { char Info[256]; sprintf_s(Info, sizeof(Info), "[INET] Socket Connect: %s [%i.%i.%i.%i], port: %i", HostAddr, (RemoteAddr.sin_addr.s_addr) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 8) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 16) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 24) & 0xFF, Port); OnInformation(Info); } } if (Status) { #ifdef WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; } } else { Error(); } } return Status == 0; } bool LSocket::Bind(int Port, bool reuseAddr) { if (!ValidSocket(d->Socket)) { OnError(0, "Attempt to use invalid socket to bind."); return false; } if (reuseAddr) { int so_reuseaddr = 1; if (setsockopt(Handle(), SOL_SOCKET, SO_REUSEADDR, (const char *)&so_reuseaddr, sizeof so_reuseaddr)) OnError(0, "Attempt to set SO_REUSEADDR failed."); // This might not be fatal... so continue on. } sockaddr_in add; add.sin_family = AF_INET; add.sin_addr.s_addr = htonl(INADDR_ANY); add.sin_port = htons(Port); int ret = bind(Handle(), (sockaddr*)&add, sizeof(add)); if (ret) { Error(); } return ret == 0; } bool LSocket::Listen(int Port) { Close(); d->Socket = socket(AF_INET, SOCK_STREAM, 0); if (d->Socket >= 0) { BytesWritten = 0; BytesRead = 0; sockaddr Addr; sockaddr_in *a = (sockaddr_in*) &Addr; ZeroObj(Addr); a->sin_family = AF_INET; a->sin_port = htons(Port); a->sin_addr.OsAddr = INADDR_ANY; if (bind(d->Socket, &Addr, sizeof(Addr)) >= 0) { if (listen(d->Socket, SOMAXCONN) != SOCKET_ERROR) { return true; } else { Error(); } } else { Error(); } } else { Error(); } return false; } bool LSocket::Accept(LSocketI *c) { if (!c) { LAssert(0); return false; } OsSocket NewSocket = INVALID_SOCKET; sockaddr Address; /* int Length = sizeof(Address); NewSocket = accept(d->Socket, &Address, &Length); */ // int Loop = 0; socklen_t Length = sizeof(Address); uint64 Start = LCurrentTime(); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket)) { if (IsReadable(100)) { NewSocket = accept(d->Socket, &Address, &Length); break; } else if (d->Timeout > 0) { uint64 Now = LCurrentTime(); if (Now - Start >= d->Timeout) { LString s; s.Printf("Accept timeout after %.1f seconds.", ((double)(Now-Start)) / 1000.0); OnInformation(s); return false; } } } if (!ValidSocket(NewSocket)) return false; return ValidSocket(c->Handle(NewSocket)); } int LSocket::Close() { if (ValidSocket(d->Socket)) { #if defined WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; OnDisconnect(); } return true; } void LSocket::Log(const char *Msg, ssize_t Ret, const char *Buf, ssize_t Len) { if (d->LogFile) { LFile f; if (f.Open(d->LogFile, O_WRITE)) { f.Seek(f.GetSize(), SEEK_SET); switch (d->LogType) { case NET_LOG_HEX_DUMP: { char s[256]; f.Write(s, sprintf_s(s, sizeof(s), "%s = %i\r\n", Msg, (int)Ret)); for (int i=0; iSocket) || !Data || d->Cancel->IsCancelled()) return -1; int Status = 0; if (d->Timeout < 0 || IsWritable(d->Timeout)) { Status = (int)send ( d->Socket, (char*)Data, (int) Len, Flags #ifndef MAC | MSG_NOSIGNAL #endif ); } if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesWritten += Status; OnWrite((char*)Data, Status); } return Status; } ssize_t LSocket::Read(void *Data, ssize_t Len, int Flags) { if (!ValidSocket(d->Socket) || !Data || d->Cancel->IsCancelled()) return -1; ssize_t Status = -1; if (d->Timeout < 0 || IsReadable(d->Timeout)) { Status = recv(d->Socket, (char*)Data, (int) Len, Flags #ifdef MSG_NOSIGNAL | MSG_NOSIGNAL #endif ); } Log("Read", (int)Status, (char*)Data, Status>0 ? Status : 0); if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesRead += Status; OnRead((char*)Data, (int)Status); } return (int)Status; } void LSocket::OnError(int ErrorCode, const char *ErrorDescription) { d->ErrStr.Printf("Error(%i): %s", ErrorCode, ErrorDescription); } const char *LSocket::GetErrorString() { return d->ErrStr; } int LSocket::Error(void *Param) { // Get the most recent error. if (!(d->LastError = #ifdef WIN32 WSAGetLastError() #else errno #endif )) return 0; // These are not really errors... if (d->LastError == EWOULDBLOCK || d->LastError == EISCONN) return 0; static class ErrorMsg { public: int Code; const char *Msg; } ErrorCodes[] = { {0, "Socket disconnected."}, #if defined WIN32 {WSAEACCES, "Permission denied."}, {WSAEADDRINUSE, "Address already in use."}, {WSAEADDRNOTAVAIL, "Cannot assign requested address."}, {WSAEAFNOSUPPORT, "Address family not supported by protocol family."}, {WSAEALREADY, "Operation already in progress."}, {WSAECONNABORTED, "Software caused connection abort."}, {WSAECONNREFUSED, "Connection refused."}, {WSAECONNRESET, "Connection reset by peer."}, {WSAEDESTADDRREQ, "Destination address required."}, {WSAEFAULT, "Bad address."}, {WSAEHOSTDOWN, "Host is down."}, {WSAEHOSTUNREACH, "No route to host."}, {WSAEINPROGRESS, "Operation now in progress."}, {WSAEINTR, "Interrupted function call."}, {WSAEINVAL, "Invalid argument."}, {WSAEISCONN, "Socket is already connected."}, {WSAEMFILE, "Too many open files."}, {WSAEMSGSIZE, "Message too long."}, {WSAENETDOWN, "Network is down."}, {WSAENETRESET, "Network dropped connection on reset."}, {WSAENETUNREACH, "Network is unreachable."}, {WSAENOBUFS, "No buffer space available."}, {WSAENOPROTOOPT, "Bad protocol option."}, {WSAENOTCONN, "Socket is not connected."}, {WSAENOTSOCK, "Socket operation on non-socket."}, {WSAEOPNOTSUPP, "Operation not supported."}, {WSAEPFNOSUPPORT, "Protocol family not supported."}, {WSAEPROCLIM, "Too many processes."}, {WSAEPROTONOSUPPORT,"Protocol not supported."}, {WSAEPROTOTYPE, "Protocol wrong type for socket."}, {WSAESHUTDOWN, "Cannot send after socket shutdown."}, {WSAESOCKTNOSUPPORT,"Socket type not supported."}, {WSAETIMEDOUT, "Connection timed out."}, {WSAEWOULDBLOCK, "Operation would block."}, {WSAHOST_NOT_FOUND, "Host not found."}, {WSANOTINITIALISED, "Successful WSAStartup not yet performed."}, {WSANO_DATA, "Valid name, no data record of requested type."}, {WSANO_RECOVERY, "This is a non-recoverable error."}, {WSASYSNOTREADY, "Network subsystem is unavailable."}, {WSATRY_AGAIN, "Non-authoritative host not found."}, {WSAVERNOTSUPPORTED,"WINSOCK.DLL version out of range."}, {WSAEDISCON, "Graceful shutdown in progress."}, #else {EACCES, "Permission denied."}, {EADDRINUSE, "Address already in use."}, {EADDRNOTAVAIL, "Cannot assign requested address."}, {EAFNOSUPPORT, "Address family not supported by protocol family."}, {EALREADY, "Operation already in progress."}, {ECONNABORTED, "Software caused connection abort."}, {ECONNREFUSED, "Connection refused."}, {ECONNRESET, "Connection reset by peer."}, {EFAULT, "Bad address."}, {EHOSTUNREACH, "No route to host."}, {EINPROGRESS, "Operation now in progress."}, {EINTR, "Interrupted function call."}, {EINVAL, "Invalid argument."}, {EISCONN, "Socket is already connected."}, {EMFILE, "Too many open files."}, {EMSGSIZE, "Message too long."}, {ENETDOWN, "Network is down."}, {ENETRESET, "Network dropped connection on reset."}, {ENETUNREACH, "Network is unreachable."}, {ENOBUFS, "No buffer space available."}, {ENOPROTOOPT, "Bad protocol option."}, {ENOTCONN, "Socket is not connected."}, {ENOTSOCK, "Socket operation on non-socket."}, {EOPNOTSUPP, "Operation not supported."}, {EPFNOSUPPORT, "Protocol family not supported."}, {EPROTONOSUPPORT, "Protocol not supported."}, {EPROTOTYPE, "Protocol wrong type for socket."}, {ESHUTDOWN, "Cannot send after socket shutdown."}, {ETIMEDOUT, "Connection timed out."}, {EWOULDBLOCK, "Resource temporarily unavailable."}, {HOST_NOT_FOUND, "Host not found."}, {NO_DATA, "Valid name, no data record of requested type."}, {NO_RECOVERY, "This is a non-recoverable error."}, {TRY_AGAIN, "Non-authoritative host not found."}, {ETIMEOUT, "Operation timed out."}, {EDESTADDRREQ, "Destination address required."}, {EHOSTDOWN, "Host is down."}, #ifndef HAIKU {ESOCKTNOSUPPORT, "Socket type not supported."}, #endif #endif {-1, 0} }; ErrorMsg *Error = ErrorCodes; while (Error->Code >= 0 && Error->Code != d->LastError) { Error++; } if (d->LastError == 10060 && Param) { HostEnt *He = (HostEnt*)Param; char s[256]; sprintf_s(s, sizeof(s), "%s (gethostbyname returned '%s')", Error->Msg, He->h_name); OnError(d->LastError, s); } else #if defined(MAC) if (d->LastError != 36) #endif { OnError(d->LastError, (Error->Code >= 0) ? Error->Msg : ""); } switch (d->LastError) { case 0: #ifdef WIN32 case 183: // I think this is a XP 'disconnect' ??? case WSAECONNABORTED: case WSAECONNRESET: case WSAENETRESET: #else case ECONNABORTED: case ECONNRESET: case ENETRESET: #endif { Close(); break; } } return d->LastError; } bool LSocket::GetUdp() { return d->Udp != 0; } void LSocket::SetUdp(bool isUdp) { if (d->Udp ^ isUdp) { d->Udp = isUdp; if (!ValidSocket(d->Socket)) { if (d->Udp) d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (d->Broadcast) { option_t enabled = d->Broadcast != 0; setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); } } } void LSocket::SetBroadcast(bool isBroadcast) { d->Broadcast = isBroadcast; } bool LSocket::AddMulticastMember(uint32_t MulticastIp, uint32_t LocalInterface) { if (!MulticastIp) return false; struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = htonl(MulticastIp); // your multicast address mreq.imr_interface.s_addr = htonl(LocalInterface); // your incoming interface IP int r = setsockopt(Handle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*) &mreq, sizeof(mreq)); if (!r) { LgiTrace("AddMulticastMember(%s, %s)\n", LIpToStr(MulticastIp).Get(), LIpToStr(LocalInterface).Get()); return true; } Error(); return false; } bool LSocket::SetMulticastInterface(uint32_t Interface) { if (!Interface) return false; struct sockaddr_in addr; addr.sin_addr.s_addr = Interface; auto r = setsockopt(Handle(), IPPROTO_IP, IP_MULTICAST_IF, (const char*) &addr, sizeof(addr)); if (!r) return true; Error(); return false; } bool LSocket::CreateUdpSocket() { if (!ValidSocket(d->Socket)) { d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (ValidSocket(d->Socket)) { if (d->Broadcast) { option_t enabled = d->Broadcast != 0; auto r = setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); if (r) Error(); } } } return ValidSocket(d->Socket); } int LSocket::ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip, uint16_t *Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; socklen_t AddrSize = sizeof(a); ZeroObj(a); a.sin_family = AF_INET; if (Port) a.sin_port = htons(*Port); #if defined(WINDOWS) a.sin_addr.S_un.S_addr = INADDR_ANY; #else a.sin_addr.s_addr = INADDR_ANY; #endif auto b = recvfrom(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, &AddrSize); if (b > 0) { OnRead((char*)Buffer, (int)b); if (Ip) *Ip = ntohl(a.sin_addr.OsAddr); if (Port) *Port = ntohs(a.sin_port); } return (int)b; } int LSocket::WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; ZeroObj(a); a.sin_family = AF_INET; a.sin_port = htons(Port); a.sin_addr.OsAddr = htonl(Ip); ssize_t b = sendto(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, sizeof(a)); if (b > 0) { OnWrite((char*)Buffer, (int)b); } else { printf("%s:%i - sendto failed with %i.\n", _FL, errno); } return (int)b; } ////////////////////////////////////////////////////////////////////////////// bool HaveNetConnection() { bool Status = false; // Check for dial up connection #if defined WIN32 typedef DWORD (__stdcall *RasEnumConnections_Proc)(LPRASCONN lprasconn, LPDWORD lpcb, LPDWORD lpcConnections); typedef DWORD (__stdcall *RasGetConnectStatus_Proc)(HRASCONN hrasconn, LPRASCONNSTATUS lprasconnstatus); HMODULE hRas = (HMODULE) LoadLibraryA("rasapi32.dll"); if (hRas) { RASCONN Con[10]; DWORD Connections = 0; DWORD Bytes = sizeof(Con); ZeroObj(Con); Con[0].dwSize = sizeof(Con[0]); RasEnumConnections_Proc pRasEnumConnections = (RasEnumConnections_Proc) GetProcAddress(hRas, "RasEnumConnectionsA"); RasGetConnectStatus_Proc pRasGetConnectStatus = (RasGetConnectStatus_Proc) GetProcAddress(hRas, "RasGetConnectStatusA"); if (pRasEnumConnections && pRasGetConnectStatus) { pRasEnumConnections(Con, &Bytes, &Connections); for (unsigned i=0; i alloc, + std::function setLength) { const char *e = s; static const char *WhiteSpace = " \r\t\n"; // Look for the end of the string while (*e) { if (*e == '\n') { if (!strchr(" \t", e[1])) { break; } } e++; } // Trim whitespace off each end of the string while (s < e && strchr(WhiteSpace, *s)) s++; while (e > s && strchr(" \r\t\n", e[-1])) e--; // Calc the length - size_t Size = e - s; - char *Str = new char[Size+1]; + auto Str = alloc(e - s); if (Str) { - const char *In = s; - char *Out = Str; + auto In = s; + auto Out = Str; while (In < e) { if (*In == '\r') { } else if (*In == 9) { *Out++ = ' '; } else { *Out++ = *In; } In++; } - *Out++ = 0; + setLength(Out - Str); } - - return Str; } -const char *SeekNextLine(const char *s, const char *End) +template +T *SeekNextLine(T *s, T *End) { - if (s) - { - for (; *s && *s != '\n' && (!End || s < End); s++); - if (*s == '\n' && (!End || s < End)) s++; - } + if (!s) + return s; + + for (; *s && *s != '\n' && (!End || s < End); s++) + ; + if (*s == '\n' && (!End || s < End)) + s++; return s; } // Search through headers for a field char *InetGetHeaderField( // Returns an allocated string or NULL on failure const char *Headers, // Pointer to all the headers const char *Field, // The field your after ssize_t Len) // Maximum len to run, or -1 for NULL terminated { if (Headers && Field) { // for all lines const char *End = Len < 0 ? 0 : Headers + Len; size_t FldLen = strlen(Field); for (const char *s = Headers; *s && (!End || s < End); s = SeekNextLine(s, End)) { if (*s != 9 && _strnicmp(s, Field, FldLen) == 0) { // found a match s += FldLen; if (*s == ':') { s++; while (*s) { if (strchr(" \t\r", *s)) { s++; } else if (*s == '\n') { if (strchr(" \r\n\t", s[1])) s += 2; else break; } else break; } - return InetGetField(s); + char *value = NULL; + InetGetField(s, + [&value](auto sz) + { + return value = new char[sz + 1]; + }, + [&value](auto sz) + { + value[sz] = 0; + }); + return value; } } } } - return 0; + return NULL; } -char *InetGetSubField(const char *s, const char *Field) +LString LGetHeaderField(LString Headers, const char *Field) { - char *Status = 0; + if (!Headers || !Field) + return LString(); - if (s && Field) + // for all lines + auto End = Headers.Get() + Headers.Length(); + auto FldLen = Strlen(Field); + for (auto s = Headers.Get(); + s < End; + s = SeekNextLine(s, End)) { - s = strchr(s, ';'); - if (s) + if (*s != '\t' && + Strnicmp(s, Field, FldLen) == 0 && + s[FldLen] == ':') { - s++; + // found a match + s += FldLen + 1; - size_t FieldLen = strlen(Field); - char White[] = " \t\r\n"; - while (*s) + while (*s && s < End) { - // Skip leading whitespace - while (*s && (strchr(White, *s) || *s == ';')) s++; + if (strchr(" \t\r", *s)) + { + s++; + } + else if (*s == '\n') + { + if (strchr(" \r\n\t", s[1])) + s += 2; + else + break; + } + else break; + } + + LString value; + InetGetField(s, + [&value](auto sz) + { + value.Length(sz); + return value.Get(); + }, + [&value](auto sz) + { + value.Length(sz); + }); + return value; + } + } + + return LString(); +} - // Parse field name - if (IsAlpha((uint8_t)*s)) +void InetGetSubField_Impl( const char *s, + const char *Field, + std::function output) +{ + if (!s || !Field) + return; + + s = strchr(s, ';'); + if (!s) + return; + s++; + + char *Status = NULL; + auto FieldLen = strlen(Field); + auto White = " \t\r\n"; + while (*s) + { + // Skip leading whitespace + while (*s && (strchr(White, *s) || *s == ';')) s++; + + // Parse field name + if (IsAlpha((uint8_t)*s)) + { + const char *f = s; + while (*s && (IsAlpha(*s) || *s == '-')) s++; + bool HasField = ((s-f) == FieldLen) && (_strnicmp(Field, f, FieldLen) == 0); + while (*s && strchr(White, *s)) s++; + if (*s == '=') + { + s++; + while (*s && strchr(White, *s)) s++; + if (*s && strchr("\'\"", *s)) { - const char *f = s; - while (*s && (IsAlpha(*s) || *s == '-')) s++; - bool HasField = ((s-f) == FieldLen) && (_strnicmp(Field, f, FieldLen) == 0); - while (*s && strchr(White, *s)) s++; - if (*s == '=') + // Quote Delimited Field + char d = *s++; + char *e = strchr((char*)s, d); + if (e) { - s++; - while (*s && strchr(White, *s)) s++; - if (*s && strchr("\'\"", *s)) + if (HasField) { - // Quote Delimited Field - char d = *s++; - char *e = strchr((char*)s, d); - if (e) - { - if (HasField) - { - Status = NewStr(s, e-s); - break; - } + output(s, e - s); + break; + } - s = e + 1; - } - else break; - } - else - { - // Space Delimited Field - const char *e = s; - while (*e && !strchr(White, *e) && *e != ';') e++; - - if (HasField) - { - Status = NewStr(s, e-s); - break; - } - - s = e; - } + s = e + 1; } else break; } - else break; + else + { + // Space Delimited Field + const char *e = s; + while (*e && !strchr(White, *e) && *e != ';') e++; + + if (HasField) + { + output(s, e - s); + break; + } + + s = e; + } } + else break; } + else break; } +} - return Status; +char *InetGetSubField(const char *HeaderValue, const char *Field) +{ + char *result = NULL; + InetGetSubField_Impl(HeaderValue, Field, + [&result](auto str, auto sz) + { + result = NewStr(str, sz); + }); + return result; +} + +LString LGetSubField(LString HeaderValue, const char *Field) +{ + LString result; + InetGetSubField_Impl(HeaderValue, Field, + [&result](auto str, auto sz) + { + result.Set(str, sz); + }); + return result; } LString LIpToStr(uint32_t ip) { LString s; s.Printf("%i.%i.%i.%i", (ip>>24)&0xff, (ip>>16)&0xff, (ip>>8)&0xff, (ip)&0xff); return s; } uint32_t LIpToInt(LString str) { auto p = str.Split("."); if (p.Length() != 4) return 0; uint32_t ip = 0; for (auto &s : p) { ip <<= 8; auto n = s.Int(); if (n > 255) { LAssert(0); return 0; } ip |= (uint8_t)s.Int(); } return ip; } uint32_t LHostnameToIp(const char *Host) { if (!Host) return 0; // struct addrinfo hints = {}; struct addrinfo *res = NULL; getaddrinfo(Host, NULL, NULL, &res); if (!res) return 0; uint32_t ip = 0; if (res->ai_addr) { auto fam = res->ai_addr->sa_family; if (fam == AF_INET) { auto a = (sockaddr_in*)res->ai_addr; ip = ntohl(a->sin_addr.s_addr); } } freeaddrinfo(res); return ip; } bool WhatsMyIp(LAutoString &Ip) { bool Status = false; StartNetworkStack(); #if defined WIN32 char hostname[256]; HostEnt *e = NULL; if (gethostname(hostname, sizeof(hostname)) != SOCKET_ERROR) { e = gethostbyname(hostname); } if (e) { int Which = 0; for (; e->h_addr_list[Which]; Which++); Which--; char IpAddr[32]; sprintf_s(IpAddr, sizeof(IpAddr), "%i.%i.%i.%i", (uchar)e->h_addr_list[Which][0], (uchar)e->h_addr_list[Which][1], (uchar)e->h_addr_list[Which][2], (uchar)e->h_addr_list[Which][3]); Status = Ip.Reset(NewStr(IpAddr)); } #endif return Status; } ////////////////////////////////////////////////////////////////////// #define SOCKS5_VER 5 #define SOCKS5_CMD_CONNECT 1 #define SOCKS5_CMD_BIND 2 #define SOCKS5_CMD_ASSOCIATE 3 #define SOCKS5_ADDR_IPV4 1 #define SOCKS5_ADDR_DOMAIN 3 #define SOCKS5_ADDR_IPV6 4 #define SOCKS5_AUTH_NONE 0 #define SOCKS5_AUTH_GSSAPI 1 #define SOCKS5_AUTH_USER_PASS 2 LSocks5Socket::LSocks5Socket() { Socks5Connected = false; } void LSocks5Socket::SetProxy(const LSocks5Socket *s) { Proxy.Reset(s ? NewStr(s->Proxy) : 0); Port = s ? s->Port : 0; UserName.Reset(s ? NewStr(s->UserName) : 0); Password.Reset(s ? NewStr(s->Password) : 0); } void LSocks5Socket::SetProxy(char *proxy, int port, char *username, char *password) { Proxy.Reset(NewStr(proxy)); Port = port; UserName.Reset(NewStr(username)); Password.Reset(NewStr(password)); } int LSocks5Socket::Open(const char *HostAddr, int port) { bool Status = false; if (HostAddr) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), "[SOCKS5] Connecting to proxy server '%s'", HostAddr); OnInformation(Msg); Status = LSocket::Open(Proxy, Port) != 0; if (Status) { char Buf[1024]; Buf[0] = SOCKS5_VER; Buf[1] = 2; // methods Buf[2] = SOCKS5_AUTH_NONE; Buf[3] = SOCKS5_AUTH_USER_PASS; // No idea how to implement this. // AuthReq[3] = SOCKS5_AUTH_GSSAPI; OnInformation("[SOCKS5] Connected, Requesting authentication type."); LSocket::Write(Buf, 4, 0); if (LSocket::Read(Buf, 2, 0) == 2) { if (Buf[0] == SOCKS5_VER) { bool Authenticated = false; switch (Buf[1]) { case SOCKS5_AUTH_NONE: { Authenticated = true; OnInformation("[SOCKS5] No authentication needed."); break; } case SOCKS5_AUTH_USER_PASS: { OnInformation("[SOCKS5] User/Pass authentication needed."); if (UserName && Password) { char *b = Buf; *b++ = 1; // ver of sub-negotiation ?? size_t NameLen = strlen(UserName); LAssert(NameLen < 0x80); *b++ = (char)NameLen; b += sprintf_s(b, NameLen+1, "%s", UserName.Get()); size_t PassLen = strlen(Password); LAssert(PassLen < 0x80); *b++ = (char)PassLen; b += sprintf_s(b, PassLen+1, "%s", Password.Get()); LSocket::Write(Buf, (int)(3 + NameLen + PassLen)); if (LSocket::Read(Buf, 2, 0) == 2) { Authenticated = (Buf[0] == 1 && Buf[1] == 0); } if (!Authenticated) { OnInformation("[SOCKS5] User/Pass authentication failed."); } } break; } } if (Authenticated) { OnInformation("[SOCKS5] Authentication successful."); int HostPort = htons(port); // Header char *b = Buf; *b++ = SOCKS5_VER; *b++ = SOCKS5_CMD_CONNECT; *b++ = 0; // reserved long IpAddr = inet_addr(HostAddr); if (IpAddr != -1) { // Ip *b++ = SOCKS5_ADDR_IPV4; memcpy(b, &IpAddr, 4); b += 4; } else { // Domain Name *b++ = SOCKS5_ADDR_DOMAIN; size_t Len = strlen(HostAddr); LAssert(Len < 0x80); *b++ = (char)Len; strcpy_s(b, Buf+sizeof(Buf)-b, HostAddr); b += Len; } // Port memcpy(b, &HostPort, 2); b += 2; LSocket::Write(Buf, (int)(b - Buf), 0); if (LSocket::Read(Buf, 10, 0) == 10) { if (Buf[0] == SOCKS5_VER) { switch (Buf[1]) { case 0: Socks5Connected = true; OnInformation("[SOCKS5] Connected!"); break; case 1: OnInformation("[SOCKS5] General SOCKS server failure"); break; case 2: OnInformation("[SOCKS5] Connection not allowed by ruleset"); break; case 3: OnInformation("[SOCKS5] Network unreachable"); break; case 4: OnInformation("[SOCKS5] Host unreachable"); break; case 5: OnInformation("[SOCKS5] Connection refused"); break; case 6: OnInformation("[SOCKS5] TTL expired"); break; case 7: OnInformation("[SOCKS5] Command not supported"); break; case 8: OnInformation("[SOCKS5] Address type not supported"); break; default: OnInformation("[SOCKS5] Unknown SOCKS server failure"); break; } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Connection request read failed."); } } else { OnInformation("[SOCKS5] Not authenticated."); } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Authentication type read failed."); } Status = Socks5Connected; if (!Status) { LSocket::Close(); OnInformation("[SOCKS5] Failure: Disconnecting."); } } } return Status; } diff --git a/test/ScriptingUnitTests/Main.cpp b/test/ScriptingUnitTests/Main.cpp --- a/test/ScriptingUnitTests/Main.cpp +++ b/test/ScriptingUnitTests/Main.cpp @@ -1,160 +1,162 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "../src/common/Coding/ScriptingPriv.h" #include "lgi/common/StringClass.h" #include "lgi/common/LgiRes.h" struct ConsoleLog : public LStream { ssize_t Write(const void *Ptr, ssize_t Size, int Flags) { return printf("%.*s", (int)Size, (char*)Ptr); } }; class App : public LApp, public LScriptContext, public LVmCallback { LScriptEngine *Engine; LAutoString SrcFile; ConsoleLog Log; bool CallCallback(LVirtualMachine &Vm, LString CallbackName, LScriptArguments &Args) { Args.Throw(_FL, "Not implemented."); return false; } LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { - return new LVmDebuggerWnd(NULL, this, Vm, Code, Assembly); + LAutoPtr vm(new LVirtualMachine(Vm)); + LAutoPtr code(new LCompiledCode(*Code)); + return new LVmDebuggerWnd(NULL, this, vm, code, Assembly); } bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) { return false; } public: int Status; App(OsAppArguments &AppArgs) : LApp(AppArgs, "LgiScript") { LFile::Path p(LSP_APP_INSTALL); p += "Resources"; p += "LgiScript.lr8"; LgiGetResObj(true, p); Engine = NULL; Status = 0; } LHostFunc *GetCommands() { return NULL; } void SetEngine(LScriptEngine *Eng) { Engine = Eng; } void OnReceiveFiles(LArray &Files) { for (int i=0; iGetOption("disassemble"); if (!LFileExists(File)) { printf("Error: '%s' not found.\n", File); return false; } if (!SrcFile.Reset(NewStr(File))) { printf("Error: Mem alloc failed.\n"); return false; } LScriptEngine Eng(NULL, NULL, this); Eng.SetConsole(&Log); LAutoString Src(::LReadTextFile(SrcFile)); if (!Src) { printf("Error: Failed to read '%s'.\n", SrcFile.Get()); return false; } LAutoPtr Obj; if (!Eng.Compile(Obj, NULL, Src, File)) { printf("Error: Compilation failed '%s'.\n", SrcFile.Get()); return false; } LVariant Ret; LExecutionStatus s = Eng.Run(Obj, &Ret); if (s == ScriptError) { printf("Error: Execution failed '%s'.\n", SrcFile.Get()); return false; } else if (s == ScriptWarning) { printf("Warning: Execution succeeded with warnings '%s'.\n", SrcFile.Get()); return false; } if (Ret.CastInt32()) printf("Success: %s\n", File); else { printf("Failed: %s\n", File); s = ScriptError; } if (Disassemble) { LString f = File; int Idx = f.RFind("."); if (Idx > 0) { f = f(0, Idx) + ".asm"; LAutoString a(LReadTextFile(f)); if (a) { printf("%s\n", a.Get()); } } } return s == ScriptSuccess; } }; int LgiMain(OsAppArguments &AppArgs) { App a(AppArgs); if (a.IsOk()) { a.OnCommandLine(); } return a.Status; } \ No newline at end of file diff --git a/test/UnitTests/src/NetworkTests.cpp b/test/UnitTests/src/NetworkTests.cpp --- a/test/UnitTests/src/NetworkTests.cpp +++ b/test/UnitTests/src/NetworkTests.cpp @@ -1,39 +1,71 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Net.h" #include "UnitTests.h" class PrivNetworkTests { public: }; NetworkTests::NetworkTests() : UnitTest("NetworkTests") { d = new PrivNetworkTests; } NetworkTests::~NetworkTests() { DeleteObj(d); } bool NetworkTests::Run() { /* Things to test: LgiFunc char *InetGetHeaderField(const char *Headers, const char *Field, ssize_t Len = -1); LgiExtern LString LGetHeaderField(LString Headers, const char *Field); LgiFunc char *InetGetSubField(const char *s, const char *Field); LgiExtern LString LGetSubField(LString HeaderValue, const char *Field); LUri */ + LString Headers = "Subject: =?utf-8?Q?Hi=20Matthew=2C=C2=A0=20check=20out=20what=27s=20coming=20up=20at=20LifeSource?=\r\n" + "Date: Thu, 27 Apr 2023 19:59:56 +0000\r\n" + "Received: from localhost (localhost [127.0.0.1])\r\n" + " by mail145.atl271.mcdlv.net (Mailchimp) with ESMTP id 4Q6mmX4djgzDSH7tT\r\n" + " for ; Thu, 27 Apr 2023 19:59:56 +0000 (GMT)\r\n" + "Content-Type: multipart/alternative; boundary=\"_----------=_MCPart_2128447118\""; + auto ExpectedReceived = "from localhost (localhost [127.0.0.1])\n" + " by mail145.atl271.mcdlv.net (Mailchimp) with ESMTP id 4Q6mmX4djgzDSH7tT\n" + " for ; Thu, 27 Apr 2023 19:59:56 +0000 (GMT)"; + + LAutoString h1(InetGetHeaderField(Headers, "Received")); + // LgiTrace("h1='%s'\n", LString::Escape(h1).Get()); + if (Strcmp(h1.Get(), ExpectedReceived)) + return FAIL(_FL, "InetGetHeaderField returned wrong value."); + + auto h2 = LGetHeaderField(Headers, "Received"); + if (Strcmp(h1.Get(), ExpectedReceived)) + return FAIL(_FL, "LGetHeaderField returned wrong value."); + + auto ExpectedType = "multipart/alternative; boundary=\"_----------=_MCPart_2128447118\""; + auto h3 = LGetHeaderField(Headers, "Content-Type"); + if (Strcmp(h3.Get(), ExpectedType)) + return FAIL(_FL, "LGetHeaderField returned wrong value."); + + auto ExpectedBoundary = "_----------=_MCPart_2128447118"; + LAutoString h4(InetGetSubField(h3, "boundary")); + if (Strcmp(h4.Get(), ExpectedBoundary)) + return FAIL(_FL, "InetGetSubField returned wrong value."); + + auto h5 = LGetSubField(h3, "boundary"); + if (Strcmp(h5.Get(), ExpectedBoundary)) + return FAIL(_FL, "LGetSubField returned wrong value."); return true; }