1 /* 2 KeePass Password Safe - The Open-Source Password Manager 3 Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 20 using System; 21 using System.Collections; 22 using System.Collections.Generic; 23 using System.Diagnostics; 24 using System.Globalization; 25 using System.IO; 26 using System.Text; 27 using System.Text.RegularExpressions; 28 29 using KeePass.App.Configuration; 30 using KeePass.Forms; 31 32 using KeePassLib; 33 using KeePassLib.Collections; 34 using KeePassLib.Native; 35 using KeePassLib.Security; 36 using KeePassLib.Utility; 37 38 namespace KeePass.Util.Spr 39 { 40 /// <summary> 41 /// String placeholders and field reference replacement engine. 42 /// </summary> 43 public static partial class SprEngine 44 { 45 private const uint MaxRecursionDepth = 12; 46 private const StringComparison ScMethod = StringComparison.OrdinalIgnoreCase; 47 48 // private static readonly char[] m_vPlhEscapes = new char[] { '{', '}', '%' }; 49 50 // Important notes for plugin developers subscribing to the following events: 51 // * If possible, prefer subscribing to FilterCompile instead of 52 // FilterCompilePre. 53 // * If your plugin provides an active transformation (e.g. replacing a 54 // placeholder that changes some state or requires UI interaction), you 55 // must only perform the transformation if the ExtActive bit is set in 56 // args.Context.Flags of the event arguments object args provided to the 57 // event handler. 58 // * Non-active transformations should only be performed if the ExtNonActive 59 // bit is set in args.Context.Flags. 60 // * If your plugin provides a placeholder (like e.g. {EXAMPLE}), you 61 // should add this placeholder to the FilterPlaceholderHints list 62 // (e.g. add the string "{EXAMPLE}"). Please remove your strings from 63 // the list when your plugin is terminated. 64 public static event EventHandler<SprEventArgs> FilterCompilePre; 65 public static event EventHandler<SprEventArgs> FilterCompile; 66 67 private static List<string> m_lFilterPlh = new List<string>(); 68 // See the events above 69 public static List<string> FilterPlaceholderHints 70 { 71 get { return m_lFilterPlh; } 72 } 73 74 [Obsolete] Compile(string strText, bool bIsAutoTypeSequence, PwEntry pwEntry, PwDatabase pwDatabase, bool bEscapeForAutoType, bool bEscapeQuotesForCommandLine)75 public static string Compile(string strText, bool bIsAutoTypeSequence, 76 PwEntry pwEntry, PwDatabase pwDatabase, bool bEscapeForAutoType, 77 bool bEscapeQuotesForCommandLine) 78 { 79 SprContext ctx = new SprContext(pwEntry, pwDatabase, SprCompileFlags.All, 80 bEscapeForAutoType, bEscapeQuotesForCommandLine); 81 return Compile(strText, ctx); 82 } 83 Compile(string strText, SprContext ctx)84 public static string Compile(string strText, SprContext ctx) 85 { 86 if(strText == null) { Debug.Assert(false); return string.Empty; } 87 if(strText.Length == 0) return string.Empty; 88 89 if(ctx == null) ctx = new SprContext(); 90 ctx.RefCache.Clear(); 91 92 string str = SprEngine.CompileInternal(strText, ctx, 0); 93 94 // if(bEscapeForAutoType && !bIsAutoTypeSequence) 95 // str = SprEncoding.MakeAutoTypeSequence(str); 96 97 return str; 98 } 99 CompileInternal(string strText, SprContext ctx, uint uRecursionLevel)100 private static string CompileInternal(string strText, SprContext ctx, 101 uint uRecursionLevel) 102 { 103 if(strText == null) { Debug.Assert(false); return string.Empty; } 104 if(ctx == null) { Debug.Assert(false); ctx = new SprContext(); } 105 106 if(uRecursionLevel >= SprEngine.MaxRecursionDepth) 107 { 108 Debug.Assert(false); // Most likely a recursive reference 109 return string.Empty; // Do not return strText (endless loop) 110 } 111 112 string str = strText; 113 MainForm mf = Program.MainForm; 114 115 bool bExt = ((ctx.Flags & (SprCompileFlags.ExtActive | 116 SprCompileFlags.ExtNonActive)) != SprCompileFlags.None); 117 if(bExt && (SprEngine.FilterCompilePre != null)) 118 { 119 SprEventArgs args = new SprEventArgs(str, ctx.Clone()); 120 SprEngine.FilterCompilePre(null, args); 121 str = args.Text; 122 } 123 124 if((ctx.Flags & SprCompileFlags.Comments) != SprCompileFlags.None) 125 str = RemoveComments(str); 126 127 // The following realizes {T-CONV:/Text/Raw/}, which should be 128 // one of the first transformations (except comments) 129 if((ctx.Flags & SprCompileFlags.TextTransforms) != SprCompileFlags.None) 130 str = PerformTextTransforms(str, ctx, uRecursionLevel); 131 132 if((ctx.Flags & SprCompileFlags.Run) != SprCompileFlags.None) 133 str = RunCommands(str, ctx, uRecursionLevel); 134 135 if((ctx.Flags & SprCompileFlags.DataActive) != SprCompileFlags.None) 136 str = PerformClipboardCopy(str, ctx, uRecursionLevel); 137 138 if(((ctx.Flags & SprCompileFlags.DataNonActive) != SprCompileFlags.None) && 139 (str.IndexOf(@"{CLIPBOARD}", SprEngine.ScMethod) >= 0)) 140 { 141 string strCb = null; 142 try { strCb = ClipboardUtil.GetText(); } 143 catch(Exception) { Debug.Assert(false); } 144 str = Fill(str, @"{CLIPBOARD}", strCb ?? string.Empty, ctx, null); 145 } 146 147 if((ctx.Flags & SprCompileFlags.AppPaths) != SprCompileFlags.None) 148 str = AppLocator.FillPlaceholders(str, ctx); 149 150 if(ctx.Entry != null) 151 { 152 if((ctx.Flags & SprCompileFlags.PickChars) != SprCompileFlags.None) 153 str = ReplacePickPw(str, ctx, uRecursionLevel); 154 155 if((ctx.Flags & SprCompileFlags.EntryStrings) != SprCompileFlags.None) 156 str = FillEntryStrings(str, ctx, uRecursionLevel); 157 158 if((ctx.Flags & SprCompileFlags.EntryStringsSpecial) != SprCompileFlags.None) 159 str = FillEntryStringsSpecial(str, ctx, uRecursionLevel); 160 161 if(((ctx.Flags & SprCompileFlags.EntryProperties) != SprCompileFlags.None) && 162 (str.IndexOf(@"{UUID}", SprEngine.ScMethod) >= 0)) 163 str = Fill(str, @"{UUID}", ctx.Entry.Uuid.ToHexString(), ctx, null); 164 165 if(((ctx.Flags & SprCompileFlags.PasswordEnc) != SprCompileFlags.None) && 166 (str.IndexOf(@"{PASSWORD_ENC}", SprEngine.ScMethod) >= 0)) 167 { 168 string strPwCmp = SprEngine.CompileInternal(@"{PASSWORD}", 169 ctx.WithoutContentTransformations(), uRecursionLevel + 1); 170 str = Fill(str, @"{PASSWORD_ENC}", StrUtil.EncryptString( 171 strPwCmp), ctx, null); 172 } 173 174 PwGroup pg = ctx.Entry.ParentGroup; 175 if(((ctx.Flags & SprCompileFlags.Group) != SprCompileFlags.None) && 176 (pg != null)) 177 str = FillGroupPlh(str, @"{GROUP", pg, ctx, uRecursionLevel); 178 } 179 180 if((ctx.Flags & SprCompileFlags.Paths) != SprCompileFlags.None) 181 { 182 if(mf != null) 183 { 184 PwGroup pgSel = mf.GetSelectedGroup(); 185 if(pgSel != null) 186 str = FillGroupPlh(str, @"{GROUP_SEL", pgSel, ctx, uRecursionLevel); 187 } 188 189 str = Fill(str, @"{APPDIR}", UrlUtil.GetFileDirectory( 190 WinUtil.GetExecutable(), false, false), ctx, uRecursionLevel); 191 192 str = Fill(str, @"{ENV_DIRSEP}", Path.DirectorySeparatorChar.ToString(), 193 ctx, null); 194 195 string strPF86 = Environment.GetEnvironmentVariable("ProgramFiles(x86)"); 196 if(string.IsNullOrEmpty(strPF86)) 197 strPF86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); 198 if(strPF86 != null) 199 str = Fill(str, @"{ENV_PROGRAMFILES_X86}", strPF86, ctx, uRecursionLevel); 200 else { Debug.Assert(false); } 201 202 if(ctx.Database != null) 203 { 204 string strPath = ctx.Database.IOConnectionInfo.Path; 205 string strDir = UrlUtil.GetFileDirectory(strPath, false, false); 206 string strName = UrlUtil.GetFileName(strPath); 207 208 // For backward compatibility only 209 str = Fill(str, @"{DOCDIR}", strDir, ctx, uRecursionLevel); 210 211 str = Fill(str, @"{DB_PATH}", strPath, ctx, uRecursionLevel); 212 str = Fill(str, @"{DB_DIR}", strDir, ctx, uRecursionLevel); 213 str = Fill(str, @"{DB_NAME}", strName, ctx, uRecursionLevel); 214 str = Fill(str, @"{DB_BASENAME}", UrlUtil.StripExtension( 215 strName), ctx, uRecursionLevel); 216 str = Fill(str, @"{DB_EXT}", UrlUtil.GetExtension( 217 strPath), ctx, uRecursionLevel); 218 } 219 } 220 221 if((ctx.Flags & SprCompileFlags.AutoType) != SprCompileFlags.None) 222 { 223 // Use Bksp instead of Del (in order to avoid Ctrl+Alt+Del); 224 // https://sourceforge.net/p/keepass/discussion/329220/thread/4f1aa6b8/ 225 str = StrUtil.ReplaceCaseInsensitive(str, @"{CLEARFIELD}", 226 @"{HOME}+({END}){BKSP}{DELAY 50}"); 227 } 228 229 if(((ctx.Flags & SprCompileFlags.DateTime) != SprCompileFlags.None) && 230 (str.IndexOf(@"{DT_", SprEngine.ScMethod) >= 0)) 231 { 232 DateTime dtNow = DateTime.UtcNow; 233 str = Fill(str, @"{DT_UTC_YEAR}", dtNow.Year.ToString("D4"), 234 ctx, null); 235 str = Fill(str, @"{DT_UTC_MONTH}", dtNow.Month.ToString("D2"), 236 ctx, null); 237 str = Fill(str, @"{DT_UTC_DAY}", dtNow.Day.ToString("D2"), 238 ctx, null); 239 str = Fill(str, @"{DT_UTC_HOUR}", dtNow.Hour.ToString("D2"), 240 ctx, null); 241 str = Fill(str, @"{DT_UTC_MINUTE}", dtNow.Minute.ToString("D2"), 242 ctx, null); 243 str = Fill(str, @"{DT_UTC_SECOND}", dtNow.Second.ToString("D2"), 244 ctx, null); 245 str = Fill(str, @"{DT_UTC_SIMPLE}", dtNow.ToString("yyyyMMddHHmmss"), 246 ctx, null); 247 248 dtNow = dtNow.ToLocalTime(); 249 str = Fill(str, @"{DT_YEAR}", dtNow.Year.ToString("D4"), 250 ctx, null); 251 str = Fill(str, @"{DT_MONTH}", dtNow.Month.ToString("D2"), 252 ctx, null); 253 str = Fill(str, @"{DT_DAY}", dtNow.Day.ToString("D2"), 254 ctx, null); 255 str = Fill(str, @"{DT_HOUR}", dtNow.Hour.ToString("D2"), 256 ctx, null); 257 str = Fill(str, @"{DT_MINUTE}", dtNow.Minute.ToString("D2"), 258 ctx, null); 259 str = Fill(str, @"{DT_SECOND}", dtNow.Second.ToString("D2"), 260 ctx, null); 261 str = Fill(str, @"{DT_SIMPLE}", dtNow.ToString("yyyyMMddHHmmss"), 262 ctx, null); 263 } 264 265 if((ctx.Flags & SprCompileFlags.References) != SprCompileFlags.None) 266 str = SprEngine.FillRefPlaceholders(str, ctx, uRecursionLevel); 267 268 if(((ctx.Flags & SprCompileFlags.EnvVars) != SprCompileFlags.None) && 269 (str.IndexOf('%') >= 0)) 270 { 271 foreach(DictionaryEntry de in Environment.GetEnvironmentVariables()) 272 { 273 string strKey = (de.Key as string); 274 if(string.IsNullOrEmpty(strKey)) { Debug.Assert(false); continue; } 275 276 string strValue = (de.Value as string); 277 if(strValue == null) { Debug.Assert(false); strValue = string.Empty; } 278 279 str = Fill(str, @"%" + strKey + @"%", strValue, ctx, uRecursionLevel); 280 } 281 } 282 283 if((ctx.Flags & SprCompileFlags.Env) != SprCompileFlags.None) 284 str = FillUriSpecial(str, ctx, @"{BASE", (ctx.Base ?? string.Empty), 285 ctx.BaseIsEncoded, uRecursionLevel); 286 287 str = EntryUtil.FillPlaceholders(str, ctx, uRecursionLevel); 288 289 if((ctx.Flags & SprCompileFlags.PickChars) != SprCompileFlags.None) 290 str = ReplacePickChars(str, ctx, uRecursionLevel); 291 292 if(bExt && (SprEngine.FilterCompile != null)) 293 { 294 SprEventArgs args = new SprEventArgs(str, ctx.Clone()); 295 SprEngine.FilterCompile(null, args); 296 str = args.Text; 297 } 298 299 if(ctx.EncodeAsAutoTypeSequence) 300 { 301 str = StrUtil.NormalizeNewLines(str, false); 302 str = str.Replace("\n", @"{ENTER}"); 303 } 304 305 return str; 306 } 307 Fill(string strData, string strPlaceholder, string strReplacement, SprContext ctx, uint? ouRecursionLevel)308 private static string Fill(string strData, string strPlaceholder, 309 string strReplacement, SprContext ctx, uint? ouRecursionLevel) 310 { 311 if(strData == null) { Debug.Assert(false); return string.Empty; } 312 if(string.IsNullOrEmpty(strPlaceholder)) { Debug.Assert(false); return strData; } 313 if(strReplacement == null) { Debug.Assert(false); strReplacement = string.Empty; } 314 315 if(strData.IndexOf(strPlaceholder, SprEngine.ScMethod) < 0) return strData; 316 317 string strValue = strReplacement; 318 if(ouRecursionLevel.HasValue) 319 strValue = SprEngine.CompileInternal(strValue, ((ctx != null) ? 320 ctx.WithoutContentTransformations() : null), 321 ouRecursionLevel.Value + 1); 322 323 return StrUtil.ReplaceCaseInsensitive(strData, strPlaceholder, 324 SprEngine.TransformContent(strValue, ctx)); 325 } 326 Fill(string strData, string strPlaceholder, ProtectedString psReplacement, SprContext ctx, uint? ouRecursionLevel)327 private static string Fill(string strData, string strPlaceholder, 328 ProtectedString psReplacement, SprContext ctx, uint? ouRecursionLevel) 329 { 330 if(strData == null) { Debug.Assert(false); return string.Empty; } 331 if(string.IsNullOrEmpty(strPlaceholder)) { Debug.Assert(false); return strData; } 332 if(psReplacement == null) { Debug.Assert(false); psReplacement = ProtectedString.Empty; } 333 334 if(strData.IndexOf(strPlaceholder, SprEngine.ScMethod) < 0) return strData; 335 336 return Fill(strData, strPlaceholder, psReplacement.ReadString(), 337 ctx, ouRecursionLevel); 338 } 339 TransformContent(string strContent, SprContext ctx)340 public static string TransformContent(string strContent, SprContext ctx) 341 { 342 if(strContent == null) { Debug.Assert(false); return string.Empty; } 343 344 string str = strContent; 345 346 if(ctx != null) 347 { 348 if(ctx.EncodeForCommandLine) 349 str = SprEncoding.EncodeForCommandLine(str); 350 351 if(ctx.EncodeAsAutoTypeSequence) 352 str = SprEncoding.EncodeAsAutoTypeSequence(str); 353 } 354 355 return str; 356 } 357 UntransformContent(string strContent, SprContext ctx)358 private static string UntransformContent(string strContent, SprContext ctx) 359 { 360 if(strContent == null) { Debug.Assert(false); return string.Empty; } 361 362 string str = strContent; 363 364 if(ctx != null) 365 { 366 if(ctx.EncodeAsAutoTypeSequence) { Debug.Assert(false); } 367 368 if(ctx.EncodeForCommandLine) 369 str = SprEncoding.DecodeCommandLine(str); 370 } 371 372 return str; 373 } 374 FillEntryStrings(string str, SprContext ctx, uint uRecursionLevel)375 private static string FillEntryStrings(string str, SprContext ctx, 376 uint uRecursionLevel) 377 { 378 List<string> vKeys = ctx.Entry.Strings.GetKeys(); 379 380 // Ensure that all standard field names are in the list 381 // (this is required in order to replace the standard placeholders 382 // even if the corresponding standard field isn't present in 383 // the entry) 384 List<string> vStdNames = PwDefs.GetStandardFields(); 385 foreach(string strStdField in vStdNames) 386 { 387 if(!vKeys.Contains(strStdField)) vKeys.Add(strStdField); 388 } 389 390 // Do not directly enumerate the strings in ctx.Entry.Strings, 391 // because strings might change during the Spr compilation 392 foreach(string strField in vKeys) 393 { 394 string strKey = (PwDefs.IsStandardField(strField) ? 395 (@"{" + strField + @"}") : 396 (@"{" + PwDefs.AutoTypeStringPrefix + strField + @"}")); 397 398 if(!ctx.ForcePlainTextPasswords && strKey.Equals(@"{" + 399 PwDefs.PasswordField + @"}", StrUtil.CaseIgnoreCmp) && 400 Program.Config.MainWindow.IsColumnHidden(AceColumnType.Password)) 401 { 402 str = Fill(str, strKey, PwDefs.HiddenPassword, ctx, null); 403 continue; 404 } 405 406 // Use GetSafe because the field doesn't necessarily exist 407 // (might be a standard field that has been added above) 408 str = Fill(str, strKey, ctx.Entry.Strings.GetSafe(strField), 409 ctx, uRecursionLevel); 410 } 411 412 return str; 413 } 414 FillEntryStringsSpecial(string str, SprContext ctx, uint uRecursionLevel)415 private static string FillEntryStringsSpecial(string str, SprContext ctx, 416 uint uRecursionLevel) 417 { 418 return FillUriSpecial(str, ctx, @"{URL", ctx.Entry.Strings.ReadSafe( 419 PwDefs.UrlField), false, uRecursionLevel); 420 } 421 FillUriSpecial(string strText, SprContext ctx, string strPlhInit, string strData, bool bDataIsEncoded, uint uRecursionLevel)422 private static string FillUriSpecial(string strText, SprContext ctx, 423 string strPlhInit, string strData, bool bDataIsEncoded, 424 uint uRecursionLevel) 425 { 426 Debug.Assert(strPlhInit.StartsWith(@"{") && !strPlhInit.EndsWith(@"}")); 427 Debug.Assert(strData != null); 428 429 string[] vPlhs = new string[] { 430 strPlhInit + @"}", 431 strPlhInit + @":RMVSCM}", 432 strPlhInit + @":SCM}", 433 strPlhInit + @":HOST}", 434 strPlhInit + @":PORT}", 435 strPlhInit + @":PATH}", 436 strPlhInit + @":QUERY}", 437 strPlhInit + @":USERINFO}", 438 strPlhInit + @":USERNAME}", 439 strPlhInit + @":PASSWORD}" 440 }; 441 442 string str = strText; 443 string strDataCmp = null; 444 Uri uri = null; 445 for(int i = 0; i < vPlhs.Length; ++i) 446 { 447 string strPlh = vPlhs[i]; 448 if(str.IndexOf(strPlh, SprEngine.ScMethod) < 0) continue; 449 450 if(strDataCmp == null) 451 { 452 SprContext ctxData = (bDataIsEncoded ? 453 ctx.WithoutContentTransformations() : ctx); 454 strDataCmp = SprEngine.CompileInternal(strData, ctxData, 455 uRecursionLevel + 1); 456 } 457 458 string strRep = null; 459 if(i == 0) strRep = strDataCmp; 460 // UrlUtil supports prefixes like cmd:// 461 else if(i == 1) strRep = UrlUtil.RemoveScheme(strDataCmp); 462 else if(i == 2) strRep = UrlUtil.GetScheme(strDataCmp); 463 else 464 { 465 try 466 { 467 if(uri == null) uri = new Uri(strDataCmp); 468 469 int t; 470 switch(i) 471 { 472 // case 2: strRep = uri.Scheme; break; // No cmd:// support 473 case 3: strRep = uri.Host; break; 474 case 4: 475 strRep = uri.Port.ToString( 476 NumberFormatInfo.InvariantInfo); 477 break; 478 case 5: strRep = uri.AbsolutePath; break; 479 case 6: strRep = uri.Query; break; 480 case 7: strRep = uri.UserInfo; break; 481 case 8: 482 strRep = uri.UserInfo; 483 t = strRep.IndexOf(':'); 484 if(t >= 0) strRep = strRep.Substring(0, t); 485 break; 486 case 9: 487 strRep = uri.UserInfo; 488 t = strRep.IndexOf(':'); 489 if(t < 0) strRep = string.Empty; 490 else strRep = strRep.Substring(t + 1); 491 break; 492 default: Debug.Assert(false); break; 493 } 494 } 495 catch(Exception) { } // Invalid URI 496 } 497 if(strRep == null) strRep = string.Empty; // No assert 498 499 str = StrUtil.ReplaceCaseInsensitive(str, strPlh, strRep); 500 } 501 502 return str; 503 } 504 505 private const string StrRemStart = @"{C:"; 506 private const string StrRemEnd = @"}"; RemoveComments(string strSeq)507 private static string RemoveComments(string strSeq) 508 { 509 string str = strSeq; 510 511 while(true) 512 { 513 int iStart = str.IndexOf(StrRemStart, SprEngine.ScMethod); 514 if(iStart < 0) break; 515 int iEnd = str.IndexOf(StrRemEnd, iStart + 1, SprEngine.ScMethod); 516 if(iEnd <= iStart) break; 517 518 str = (str.Substring(0, iStart) + str.Substring(iEnd + StrRemEnd.Length)); 519 } 520 521 return str; 522 } 523 524 internal const string StrRefStart = @"{REF:"; 525 internal const string StrRefEnd = @"}"; FillRefPlaceholders(string strSeq, SprContext ctx, uint uRecursionLevel)526 private static string FillRefPlaceholders(string strSeq, SprContext ctx, 527 uint uRecursionLevel) 528 { 529 if(ctx.Database == null) return strSeq; 530 531 string str = strSeq; 532 533 int nOffset = 0; 534 for(int iLoop = 0; iLoop < 20; ++iLoop) 535 { 536 str = ctx.RefCache.Fill(str, ctx); 537 538 int nStart = str.IndexOf(StrRefStart, nOffset, SprEngine.ScMethod); 539 if(nStart < 0) break; 540 int nEnd = str.IndexOf(StrRefEnd, nStart + 1, SprEngine.ScMethod); 541 if(nEnd <= nStart) break; 542 543 string strFullRef = str.Substring(nStart, nEnd - nStart + 1); 544 char chScan, chWanted; 545 PwEntry peFound = FindRefTarget(strFullRef, ctx, out chScan, out chWanted); 546 547 if(peFound != null) 548 { 549 string strInsData; 550 if(chWanted == 'T') 551 strInsData = peFound.Strings.ReadSafe(PwDefs.TitleField); 552 else if(chWanted == 'U') 553 strInsData = peFound.Strings.ReadSafe(PwDefs.UserNameField); 554 else if(chWanted == 'A') 555 strInsData = peFound.Strings.ReadSafe(PwDefs.UrlField); 556 else if(chWanted == 'P') 557 strInsData = peFound.Strings.ReadSafe(PwDefs.PasswordField); 558 else if(chWanted == 'N') 559 strInsData = peFound.Strings.ReadSafe(PwDefs.NotesField); 560 else if(chWanted == 'I') 561 strInsData = peFound.Uuid.ToHexString(); 562 else { nOffset = nStart + 1; continue; } 563 564 if((chWanted == 'P') && !ctx.ForcePlainTextPasswords && 565 Program.Config.MainWindow.IsColumnHidden(AceColumnType.Password)) 566 strInsData = PwDefs.HiddenPassword; 567 568 SprContext sprSub = ctx.WithoutContentTransformations(); 569 sprSub.Entry = peFound; 570 571 string strInnerContent = SprEngine.CompileInternal(strInsData, 572 sprSub, uRecursionLevel + 1); 573 strInnerContent = SprEngine.TransformContent(strInnerContent, ctx); 574 575 // str = str.Substring(0, nStart) + strInnerContent + str.Substring(nEnd + 1); 576 ctx.RefCache.Add(strFullRef, strInnerContent, ctx); 577 str = ctx.RefCache.Fill(str, ctx); 578 } 579 else { nOffset = nStart + 1; continue; } 580 } 581 582 return str; 583 } 584 FindRefTarget(string strFullRef, SprContext ctx, out char chScan, out char chWanted)585 public static PwEntry FindRefTarget(string strFullRef, SprContext ctx, 586 out char chScan, out char chWanted) 587 { 588 chScan = char.MinValue; 589 chWanted = char.MinValue; 590 591 if(strFullRef == null) { Debug.Assert(false); return null; } 592 if(!strFullRef.StartsWith(StrRefStart, SprEngine.ScMethod) || 593 !strFullRef.EndsWith(StrRefEnd, SprEngine.ScMethod)) 594 return null; 595 if((ctx == null) || (ctx.Database == null)) { Debug.Assert(false); return null; } 596 597 string strRef = strFullRef.Substring(StrRefStart.Length, 598 strFullRef.Length - StrRefStart.Length - StrRefEnd.Length); 599 if(strRef.Length <= 4) return null; 600 if(strRef[1] != '@') return null; 601 if(strRef[3] != ':') return null; 602 603 chScan = char.ToUpper(strRef[2]); 604 chWanted = char.ToUpper(strRef[0]); 605 606 SearchParameters sp = SearchParameters.None; 607 sp.SearchString = strRef.Substring(4); 608 sp.RespectEntrySearchingDisabled = false; 609 610 if(chScan == 'T') sp.SearchInTitles = true; 611 else if(chScan == 'U') sp.SearchInUserNames = true; 612 else if(chScan == 'A') sp.SearchInUrls = true; 613 else if(chScan == 'P') sp.SearchInPasswords = true; 614 else if(chScan == 'N') sp.SearchInNotes = true; 615 else if(chScan == 'I') sp.SearchInUuids = true; 616 else if(chScan == 'O') sp.SearchInOther = true; 617 else return null; 618 619 PwObjectList<PwEntry> lFound = new PwObjectList<PwEntry>(); 620 ctx.Database.RootGroup.SearchEntries(sp, lFound); 621 622 return ((lFound.UCount > 0) ? lFound.GetAt(0) : null); 623 } 624 625 // internal static bool MightChange(string strText) 626 // { 627 // if(string.IsNullOrEmpty(strText)) return false; 628 // return (strText.IndexOfAny(m_vPlhEscapes) >= 0); 629 // } 630 631 /* internal static bool MightChange(string str) 632 { 633 if(str == null) { Debug.Assert(false); return false; } 634 635 int iBStart = str.IndexOf('{'); 636 if(iBStart >= 0) 637 { 638 int iBEnd = str.LastIndexOf('}'); 639 if(iBStart < iBEnd) return true; 640 } 641 642 int iPFirst = str.IndexOf('%'); 643 if(iPFirst >= 0) 644 { 645 int iPLast = str.LastIndexOf('%'); 646 if(iPFirst < iPLast) return true; 647 } 648 649 return false; 650 } */ 651 MightChange(char[] v)652 internal static bool MightChange(char[] v) 653 { 654 if(v == null) { Debug.Assert(false); return false; } 655 656 int iBStart = Array.IndexOf<char>(v, '{'); 657 if(iBStart >= 0) 658 { 659 int iBEnd = Array.LastIndexOf<char>(v, '}'); 660 if(iBStart < iBEnd) return true; 661 } 662 663 int iPFirst = Array.IndexOf<char>(v, '%'); 664 if(iPFirst >= 0) 665 { 666 int iPLast = Array.LastIndexOf<char>(v, '%'); 667 if(iPFirst < iPLast) return true; 668 } 669 670 return false; 671 } 672 673 /// <summary> 674 /// Fast probabilistic test whether a string might be 675 /// changed when compiling with <c>SprCompileFlags.Deref</c>. 676 /// </summary> MightDeref(string strText)677 internal static bool MightDeref(string strText) 678 { 679 if(strText == null) return false; 680 return (strText.IndexOf('{') >= 0); 681 } 682 DerefFn(string str, PwEntry pe)683 internal static string DerefFn(string str, PwEntry pe) 684 { 685 if(!MightDeref(str)) return str; 686 687 SprContext ctx = new SprContext(pe, 688 Program.MainForm.DocumentManager.SafeFindContainerOf(pe), 689 SprCompileFlags.Deref); 690 // ctx.ForcePlainTextPasswords = false; 691 692 return Compile(str, ctx); 693 } 694 695 /// <summary> 696 /// Parse and remove a placeholder of the form 697 /// <c>{PLH:/Param1/Param2/.../}</c>. 698 /// </summary> ParseAndRemovePlhWithParams(ref string str, SprContext ctx, uint uRecursionLevel, string strPlhStart, out int iStart, out List<string> lParams, bool bSprCmpParams)699 internal static bool ParseAndRemovePlhWithParams(ref string str, 700 SprContext ctx, uint uRecursionLevel, string strPlhStart, 701 out int iStart, out List<string> lParams, bool bSprCmpParams) 702 { 703 Debug.Assert(strPlhStart.StartsWith(@"{") && !strPlhStart.EndsWith(@"}")); 704 705 iStart = str.IndexOf(strPlhStart, SprEngine.ScMethod); 706 if(iStart < 0) { lParams = null; return false; } 707 708 lParams = new List<string>(); 709 710 try 711 { 712 int p = iStart + strPlhStart.Length; 713 if(p >= str.Length) throw new FormatException(); 714 715 char chSep = str[p]; 716 717 while(true) 718 { 719 if((p + 1) >= str.Length) throw new FormatException(); 720 721 if(str[p + 1] == '}') break; 722 723 int q = str.IndexOf(chSep, p + 1); 724 if(q < 0) throw new FormatException(); 725 726 lParams.Add(str.Substring(p + 1, q - p - 1)); 727 p = q; 728 } 729 730 Debug.Assert(str[p + 1] == '}'); 731 str = str.Remove(iStart, (p + 1) - iStart + 1); 732 } 733 catch(Exception) 734 { 735 str = str.Substring(0, iStart); 736 } 737 738 if(bSprCmpParams && (ctx != null)) 739 { 740 SprContext ctxSub = ctx.WithoutContentTransformations(); 741 for(int i = 0; i < lParams.Count; ++i) 742 lParams[i] = CompileInternal(lParams[i], ctxSub, uRecursionLevel); 743 } 744 745 return true; 746 } 747 PerformTextTransforms(string strText, SprContext ctx, uint uRecursionLevel)748 private static string PerformTextTransforms(string strText, SprContext ctx, 749 uint uRecursionLevel) 750 { 751 string str = strText; 752 int iStart; 753 List<string> lParams; 754 755 // {T-CONV:/Text/Raw/} should be the first transformation 756 757 while(ParseAndRemovePlhWithParams(ref str, ctx, uRecursionLevel, 758 @"{T-CONV:", out iStart, out lParams, true)) 759 { 760 if(lParams.Count < 2) continue; 761 762 try 763 { 764 string strNew = lParams[0]; 765 string strCmd = lParams[1].ToLower(); 766 767 if((strCmd == "u") || (strCmd == "upper")) 768 strNew = strNew.ToUpper(); 769 else if((strCmd == "l") || (strCmd == "lower")) 770 strNew = strNew.ToLower(); 771 else if(strCmd == "base64") 772 { 773 byte[] pbUtf8 = StrUtil.Utf8.GetBytes(strNew); 774 strNew = Convert.ToBase64String(pbUtf8); 775 } 776 else if(strCmd == "hex") 777 { 778 byte[] pbUtf8 = StrUtil.Utf8.GetBytes(strNew); 779 strNew = MemUtil.ByteArrayToHexString(pbUtf8); 780 } 781 else if(strCmd == "uri") 782 strNew = Uri.EscapeDataString(strNew); 783 else if(strCmd == "uri-dec") 784 strNew = Uri.UnescapeDataString(strNew); 785 // "raw": no modification 786 787 if(strCmd != "raw") 788 strNew = TransformContent(strNew, ctx); 789 790 str = str.Insert(iStart, strNew); 791 } 792 catch(Exception) { Debug.Assert(false); } 793 } 794 795 while(ParseAndRemovePlhWithParams(ref str, ctx, uRecursionLevel, 796 @"{T-REPLACE-RX:", out iStart, out lParams, true)) 797 { 798 if(lParams.Count < 2) continue; 799 if(lParams.Count == 2) lParams.Add(string.Empty); 800 801 try 802 { 803 string strNew = Regex.Replace(lParams[0], lParams[1], lParams[2]); 804 strNew = TransformContent(strNew, ctx); 805 str = str.Insert(iStart, strNew); 806 } 807 catch(Exception) { } 808 } 809 810 return str; 811 } 812 PerformClipboardCopy(string strText, SprContext ctx, uint uRecursionLevel)813 private static string PerformClipboardCopy(string strText, SprContext ctx, 814 uint uRecursionLevel) 815 { 816 string str = strText; 817 int iStart; 818 List<string> lParams; 819 SprContext ctxData = ((ctx != null) ? ctx.WithoutContentTransformations() : null); 820 821 while(ParseAndRemovePlhWithParams(ref str, ctxData, uRecursionLevel, 822 @"{CLIPBOARD-SET:", out iStart, out lParams, true)) 823 { 824 if(lParams.Count < 1) continue; 825 826 try 827 { 828 ClipboardUtil.Copy(lParams[0] ?? string.Empty, false, 829 true, null, null, IntPtr.Zero); 830 } 831 catch(Exception) { Debug.Assert(false); } 832 } 833 834 return str; 835 } 836 FillGroupPlh(string strData, string strPlhPrefix, PwGroup pg, SprContext ctx, uint uRecursionLevel)837 private static string FillGroupPlh(string strData, string strPlhPrefix, 838 PwGroup pg, SprContext ctx, uint uRecursionLevel) 839 { 840 Debug.Assert(strPlhPrefix.StartsWith("{")); 841 Debug.Assert(!strPlhPrefix.EndsWith("_")); 842 Debug.Assert(!strPlhPrefix.EndsWith("}")); 843 844 string str = strData; 845 846 str = Fill(str, strPlhPrefix + @"}", pg.Name, ctx, uRecursionLevel); 847 848 string strGroupPath = pg.GetFullPath(); 849 str = Fill(str, strPlhPrefix + @"_PATH}", strGroupPath, 850 ctx, uRecursionLevel); 851 str = Fill(str, strPlhPrefix + @"PATH}", strGroupPath, 852 ctx, uRecursionLevel); // Obsolete; for backward compatibility 853 854 str = Fill(str, strPlhPrefix + @"_NOTES}", pg.Notes, ctx, uRecursionLevel); 855 856 return str; 857 } 858 RunCommands(string strText, SprContext ctx, uint uRecursionLevel)859 private static string RunCommands(string strText, SprContext ctx, 860 uint uRecursionLevel) 861 { 862 string str = strText; 863 int iStart; 864 List<string> lParams; 865 866 while(ParseAndRemovePlhWithParams(ref str, ctx, uRecursionLevel, 867 @"{CMD:", out iStart, out lParams, false)) 868 { 869 if(lParams.Count == 0) continue; 870 871 string strBaseRaw = null; 872 if((ctx != null) && (ctx.Base != null)) 873 { 874 if(ctx.BaseIsEncoded) 875 strBaseRaw = UntransformContent(ctx.Base, ctx); 876 else strBaseRaw = ctx.Base; 877 } 878 879 string strCmd = WinUtil.CompileUrl((lParams[0] ?? string.Empty), 880 ((ctx != null) ? ctx.Entry : null), true, strBaseRaw, true); 881 if(WinUtil.IsCommandLineUrl(strCmd)) 882 strCmd = WinUtil.GetCommandLineFromUrl(strCmd); 883 if(string.IsNullOrEmpty(strCmd)) continue; 884 885 Process p = null; 886 try 887 { 888 StringComparison sc = StrUtil.CaseIgnoreCmp; 889 890 string strOpt = ((lParams.Count >= 2) ? lParams[1] : 891 string.Empty); 892 Dictionary<string, string> d = SplitParams(strOpt); 893 894 ProcessStartInfo psi = new ProcessStartInfo(); 895 896 string strApp, strArgs; 897 StrUtil.SplitCommandLine(strCmd, out strApp, out strArgs); 898 if(string.IsNullOrEmpty(strApp)) continue; 899 psi.FileName = strApp; 900 if(!string.IsNullOrEmpty(strArgs)) psi.Arguments = strArgs; 901 902 string strMethod = GetParam(d, "m", "s"); 903 bool bShellExec = !strMethod.Equals("c", sc); 904 psi.UseShellExecute = bShellExec; 905 906 string strO = GetParam(d, "o", (bShellExec ? "0" : "1")); 907 bool bStdOut = strO.Equals("1", sc); 908 if(bStdOut) psi.RedirectStandardOutput = true; 909 910 string strWS = GetParam(d, "ws", "n"); 911 if(strWS.Equals("h", sc)) 912 { 913 psi.CreateNoWindow = true; 914 psi.WindowStyle = ProcessWindowStyle.Hidden; 915 } 916 else if(strWS.Equals("min", sc)) 917 psi.WindowStyle = ProcessWindowStyle.Minimized; 918 else if(strWS.Equals("max", sc)) 919 psi.WindowStyle = ProcessWindowStyle.Maximized; 920 else { Debug.Assert(psi.WindowStyle == ProcessWindowStyle.Normal); } 921 922 string strVerb = GetParam(d, "v", null); 923 if(!string.IsNullOrEmpty(strVerb)) 924 psi.Verb = strVerb; 925 926 bool bWait = GetParam(d, "w", "1").Equals("1", sc); 927 928 p = NativeLib.StartProcessEx(psi); 929 if(p == null) { Debug.Assert(false); continue; } 930 931 if(bStdOut) 932 { 933 string strOut = (p.StandardOutput.ReadToEnd() ?? string.Empty); 934 935 // Remove trailing new-line characters, like $(...); 936 // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html 937 // https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html#Command-Substitution 938 strOut = strOut.TrimEnd('\r', '\n'); 939 940 strOut = TransformContent(strOut, ctx); 941 str = str.Insert(iStart, strOut); 942 } 943 944 if(bWait) p.WaitForExit(); 945 } 946 catch(Exception ex) 947 { 948 string strMsg = strCmd + MessageService.NewParagraph + ex.Message; 949 MessageService.ShowWarning(strMsg); 950 } 951 finally 952 { 953 try { if(p != null) p.Dispose(); } 954 catch(Exception) { Debug.Assert(false); } 955 } 956 } 957 958 return str; 959 } 960 SplitParams(string str)961 private static Dictionary<string, string> SplitParams(string str) 962 { 963 Dictionary<string, string> d = new Dictionary<string, string>(); 964 if(string.IsNullOrEmpty(str)) return d; 965 966 char[] vSplitPrm = new char[] { ',' }; 967 char[] vSplitKvp = new char[] { '=' }; 968 969 string[] v = str.Split(vSplitPrm); 970 foreach(string strOption in v) 971 { 972 if(string.IsNullOrEmpty(strOption)) continue; 973 974 string[] vKvp = strOption.Split(vSplitKvp); 975 if(vKvp.Length != 2) continue; 976 977 string strKey = (vKvp[0] ?? string.Empty).Trim().ToLower(); 978 string strValue = (vKvp[1] ?? string.Empty).Trim(); 979 980 d[strKey] = strValue; 981 } 982 983 return d; 984 } 985 GetParam(Dictionary<string, string> d, string strName, string strDefaultValue)986 private static string GetParam(Dictionary<string, string> d, 987 string strName, string strDefaultValue) 988 { 989 if(d == null) { Debug.Assert(false); return strDefaultValue; } 990 if(strName == null) { Debug.Assert(false); return strDefaultValue; } 991 992 Debug.Assert(strName == strName.ToLower()); 993 994 string strValue; 995 if(d.TryGetValue(strName, out strValue)) return strValue; 996 997 return strDefaultValue; 998 } 999 } 1000 } 1001