C4AulDebug::ProcessLineResult C4AulDebug::ProcessLine(const StdStrBuf &Line) { // Get command StdStrBuf Cmd; Cmd.CopyUntil(Line.getData(), ' '); // Get data const char *szData = Line.getPtr(Cmd.getLength()); if (*szData) szData++; // Identify command const char *szCmd = Cmd.getData(); if (SEqualNoCase(szCmd, "HELP")) return ProcessLineResult(false, "Yeah, like I'm going to explain that /here/"); else if (SEqualNoCase(szCmd, "BYE") || SEqualNoCase(szCmd, "QUIT")) C4NetIOTCP::Close(PeerAddr); else if (SEqualNoCase(szCmd, "SAY")) ::Control.DoInput(CID_Message, new C4ControlMessage(C4CMT_Normal, szData), CDT_Direct); else if (SEqualNoCase(szCmd, "CMD")) ::MessageInput.ProcessCommand(szData); else if (SEqualNoCase(szCmd, "STP") || SEqualNoCase(szCmd, "S")) eState = DS_Step; else if (SEqualNoCase(szCmd, "GO") || SEqualNoCase(szCmd, "G")) eState = DS_Go; else if (SEqualNoCase(szCmd, "STO") || SEqualNoCase(szCmd, "O")) eState = DS_StepOver; else if (SEqualNoCase(szCmd, "STR") || SEqualNoCase(szCmd, "R")) eState = DS_StepOut; else if (SEqualNoCase(szCmd, "EXC") || SEqualNoCase(szCmd, "E")) { C4AulScriptContext* context = pExec->GetContext(pExec->GetContextDepth()-1); int32_t objectNum = C4ControlScript::SCOPE_Global; if (context && context->Obj && context->Obj->GetObject()) objectNum = context->Obj->GetObject()->Number; ::Control.DoInput(CID_Script, new C4ControlScript(szData, objectNum, true), CDT_Decide); } else if (SEqualNoCase(szCmd, "PSE")) if (Game.IsPaused()) { Game.Unpause(); return ProcessLineResult(true, "Game unpaused."); } else { Game.Pause(); return ProcessLineResult(true, "Game paused."); } else if (SEqualNoCase(szCmd, "LST")) { for (C4AulScript* script = ScriptEngine.Child0; script; script = script->Next) { SendLine(RelativePath(script->ScriptName)); } } // toggle breakpoint else if (SEqualNoCase(szCmd, "TBR")) { using namespace std; // FIXME: this doesn't find functions which were included/appended string scriptPath = szData; size_t colonPos = scriptPath.find(':'); if (colonPos == string::npos) return ProcessLineResult(false, "Missing line in breakpoint request"); int line = atoi(&scriptPath[colonPos+1]); scriptPath.erase(colonPos); C4AulScript *script; for (script = ScriptEngine.Child0; script; script = script->Next) { if (SEqualNoCase(RelativePath(script->ScriptName), scriptPath.c_str())) break; } auto sh = script ? script->GetScriptHost() : NULL; if (sh) { C4AulBCC * found = NULL; for (auto script = ::ScriptEngine.Child0; script; script = script->Next) for (C4PropList *props = script->GetPropList(); props; props = props->GetPrototype()) for (auto fname = props->EnumerateOwnFuncs(); fname; fname = props->EnumerateOwnFuncs(fname)) { C4Value val; if (!props->GetPropertyByS(fname, &val)) continue; auto func = val.getFunction(); if (!func) continue; auto sfunc = func->SFunc(); if (!sfunc) continue; if (sfunc->pOrgScript != sh) continue; for (auto chunk = sfunc->GetCode(); chunk->bccType != AB_EOFN; chunk++) { if (chunk->bccType == AB_DEBUG) { int lineOfThisOne = sfunc->GetLineOfCode(chunk); if (lineOfThisOne == line) { found = chunk; goto Found; } } } } Found: if (found) found->Par.i = !found->Par.i; // activate breakpoint else return ProcessLineResult(false, "Can't set breakpoint (wrong line?)"); } else return ProcessLineResult(false, "Can't find script"); } else if (SEqualNoCase(szCmd, "SST")) { std::list<StdStrBuf*>::iterator it = StackTrace.begin(); for (it++; it != StackTrace.end(); it++) { SendLine("AT", (*it)->getData()); } SendLine("EST"); } else if (SEqualNoCase(szCmd, "VAR")) { C4Value *val = NULL; int varIndex; C4AulScriptContext* pCtx = pExec->GetContext(pExec->GetContextDepth() - 1); if (pCtx) { if ((varIndex = pCtx->Func->ParNamed.GetItemNr(szData)) != -1) { val = &pCtx->Pars[varIndex]; } else if ((varIndex = pCtx->Func->VarNamed.GetItemNr(szData)) != -1) { val = &pCtx->Vars[varIndex]; } } const char* typeName = val ? GetC4VName(val->GetType()) : "any"; StdStrBuf output = FormatString("%s %s %s", szData, typeName, val ? val->GetDataString().getData() : "Unknown"); SendLine("VAR", output.getData()); } else return ProcessLineResult(false, "Can't do that"); return ProcessLineResult(true, ""); }
bool ValidateString(StdStrBuf &rsString, ValidationOption eOption) { bool fValid = true; // validation depending on option // check min length if (!rsString.getLength()) { // empty if not allowed? if (eOption != VAL_NameAllowEmpty && eOption != VAL_NameExAllowEmpty && eOption != VAL_Comment) { rsString.Copy("empty"); fValid = false; } } switch (eOption) { case VAL_Filename: // regular filenames only // absolutely no directory traversal if (rsString.ReplaceChar('/', '_')) fValid = false; if (rsString.ReplaceChar('\\', '_')) fValid = false; // fallthrough to general file name validation case VAL_SubPathFilename: // filenames and optional subpath // do not traverse upwards in file hierarchy if (rsString.Replace("..", "__")) fValid = false; if (*rsString.getData() == '/' || *rsString.getData() == '\\') { *rsString.getMData() = '_'; fValid = false; } // fallthrough to general file name validation case VAL_FullPath: // full filename paths // some characters are prohibited in filenames in general if (rsString.ReplaceChar('*', '_')) fValid = false; if (rsString.ReplaceChar('?', '_')) fValid = false; if (rsString.ReplaceChar('<', '_')) fValid = false; if (rsString.ReplaceChar('>', '_')) fValid = false; // ';' and '|' is never allowed in filenames, because it would cause problems in many engine internal file lists if (rsString.ReplaceChar(';', '_')) fValid = false; if (rsString.ReplaceChar('|', '_')) fValid = false; // the colon is generally prohibited except at pos 2 (C:\...), because it could lead to creation of (invisible) streams on NTFS if (rsString.ReplaceChar(':', '_', 2)) fValid = false; if (*rsString.getData() == ':') { *rsString.getMData() = '_'; fValid = false; } // validate drive letter if (rsString.getLength()>=2 && *rsString.getPtr(1) == ':') { if (eOption != VAL_FullPath) { *rsString.getMPtr(1)='_'; fValid = false; } else if (!isalpha((unsigned char)*rsString.getData()) || (*rsString.getPtr(2)!='\\' && *rsString.getPtr(2)!='/')) { *rsString.getMData()=*rsString.getMPtr(1)='_'; fValid = false; } } break; case VAL_NameNoEmpty: case VAL_NameAllowEmpty: // no markup if (CMarkup::StripMarkup(&rsString)) { fValid = false; } // trim spaces if (rsString.TrimSpaces()) fValid = false; // min length if (eOption == VAL_NameNoEmpty) if (!rsString.getLength()) { fValid = false; rsString.Copy("Unknown"); } // max length if (rsString.getLength() > C4MaxName) { fValid = false; rsString.SetLength(C4MaxName); } break; case VAL_NameExNoEmpty: case VAL_NameExAllowEmpty: // trim spaces if (rsString.TrimSpaces()) fValid = false; // min length if (eOption == VAL_NameExNoEmpty) if (!rsString.getLength()) { fValid = false; rsString.Copy("Unknown"); } // max length if (rsString.getLength() > C4MaxLongName) { fValid = false; rsString.SetLength(C4MaxLongName); } break; case VAL_IRCName: // nickname for IRC. a-z, A-Z, _^{[]} only; 0-9|- inbetween; max 30 characters if (rsString.getLength() > 30) fValid = false; if (rsString.getLength() < 2) fValid = false; if (!rsString.ValidateChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_^{[]}", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_^{[]}0123456789|-")) { fValid = false; rsString.Copy("Guest"); } if (SEqualNoCase(rsString.getData(), "NickServ") || SEqualNoCase(rsString.getData(), "ChanServ") || SEqualNoCase(rsString.getData(), "MemoServ") || SEqualNoCase(rsString.getData(), "OperServ") || SEqualNoCase(rsString.getData(), "HelpServ")) fValid = false; if (!fValid) rsString.Copy("Guest"); break; case VAL_IRCPass: // password for IRC; max 31 characters // max length; no spaces if (rsString.getLength() > 31) { fValid = false; rsString.SetLength(31); } if (rsString.getLength() < 2) { fValid = false; rsString.Copy("secret"); } if (rsString.ReplaceChar(' ', '_')) fValid = false; break; case VAL_IRCChannel: // IRC channel name if (rsString.getLength() > 32) { fValid = false; rsString.SetLength(32); } else if (rsString.getLength() < 2) { fValid = false; rsString.Copy("#clonken"); } else if (*rsString.getData() != '#' && *rsString.getData() != '+') { fValid = false; *rsString.getMData() = '#'; } if (rsString.ReplaceChar(' ', '_')) fValid = false; break; case VAL_Comment: // comment - just limit length if (rsString.getLength() > C4MaxComment) { fValid = false; rsString.SetLength(C4MaxComment); } break; default: assert(!"not yet implemented"); } // issue warning for invalid adjustments if (!fValid) { const char *szOption = "unknown"; switch (eOption) { case VAL_Filename: szOption = "filename"; break; case VAL_SubPathFilename: szOption = "(sub-)filename"; break; case VAL_FullPath: szOption = "free filename"; break; case VAL_NameNoEmpty: szOption = "strict name"; break; case VAL_NameExNoEmpty: szOption = "name"; break; case VAL_NameAllowEmpty: szOption = "strict name*"; break; case VAL_NameExAllowEmpty: szOption = "name*"; break; case VAL_IRCName: szOption = "IRC nick"; break; case VAL_IRCPass: szOption = "IRC password"; break; case VAL_IRCChannel: szOption = "IRC channel"; break; case VAL_Comment: szOption = "Comment"; break; } //LogF("WARNING: Adjusted invalid user input for \"%s\" to \"%s\"", szOption, rsString.getData()); } return !fValid; }