2019-05-08 13:47:52 +03:00
#!/usr/bin/env python
2010-09-15 17:28:56 +04:00
"""
2020-01-01 15:25:15 +03:00
Copyright ( c ) 2006 - 2020 sqlmap developers ( http : / / sqlmap . org / )
2017-10-11 15:50:46 +03:00
See the file ' LICENSE ' for copying permission
2010-09-15 17:28:56 +04:00
"""
2019-06-04 13:15:39 +03:00
from __future__ import division
2012-12-20 13:37:20 +04:00
import codecs
2010-09-27 17:26:46 +04:00
import doctest
2019-05-06 12:41:19 +03:00
import logging
2010-09-15 17:28:56 +04:00
import os
2019-04-29 15:19:56 +03:00
import random
2010-09-26 18:02:13 +04:00
import re
2010-09-27 15:20:48 +04:00
import shutil
2019-11-14 03:29:51 +03:00
import socket
2019-11-17 02:21:33 +03:00
import sqlite3
2010-09-15 17:28:56 +04:00
import sys
2010-09-26 18:02:13 +04:00
import tempfile
2019-04-19 14:28:11 +03:00
import threading
2010-09-26 14:47:04 +04:00
import time
2013-01-09 16:33:18 +04:00
import traceback
2010-09-15 17:28:56 +04:00
2012-12-11 15:02:06 +04:00
from extra . beep . beep import beep
2019-04-19 14:28:11 +03:00
from extra . vulnserver import vulnserver
2010-09-26 18:02:13 +04:00
from lib . controller . controller import start
2019-05-09 17:38:44 +03:00
from lib . core . common import clearColors
2010-11-24 00:00:42 +03:00
from lib . core . common import clearConsoleLine
2010-09-26 14:47:04 +04:00
from lib . core . common import dataToStdout
2012-12-19 19:58:06 +04:00
from lib . core . common import randomStr
2010-10-07 02:43:04 +04:00
from lib . core . common import readXmlFile
2019-04-19 14:28:11 +03:00
from lib . core . common import shellExec
2019-05-03 00:51:54 +03:00
from lib . core . compat import round
2019-05-03 14:20:15 +03:00
from lib . core . compat import xrange
2019-11-28 01:26:39 +03:00
from lib . core . convert import encodeBase64
2019-05-06 01:54:21 +03:00
from lib . core . convert import getUnicode
2010-09-15 17:28:56 +04:00
from lib . core . data import conf
2019-05-06 12:41:19 +03:00
from lib . core . data import kb
2010-09-15 17:28:56 +04:00
from lib . core . data import logger
from lib . core . data import paths
2019-06-05 11:37:11 +03:00
from lib . core . data import queries
2016-05-31 14:02:26 +03:00
from lib . core . enums import MKSTEMP_PREFIX
2013-01-14 14:23:40 +04:00
from lib . core . exception import SqlmapBaseException
2013-01-22 15:25:01 +04:00
from lib . core . exception import SqlmapNotVulnerableException
2012-12-19 18:23:38 +04:00
from lib . core . log import LOGGER_HANDLER
2010-09-26 18:02:13 +04:00
from lib . core . option import init
2013-01-29 20:23:30 +04:00
from lib . core . option import initOptions
2013-02-22 20:26:48 +04:00
from lib . core . option import setVerbosity
2010-09-27 17:26:46 +04:00
from lib . core . optiondict import optDict
2012-12-20 13:37:20 +04:00
from lib . core . settings import UNICODE_ENCODING
2010-09-26 18:02:13 +04:00
from lib . parse . cmdline import cmdLineParser
2010-09-15 17:28:56 +04:00
2014-01-13 21:24:49 +04:00
class Failures ( object ) :
failedItems = None
failedParseOn = None
failedTraceBack = None
2012-12-17 15:41:43 +04:00
2016-09-19 16:51:28 +03:00
_failures = Failures ( )
2019-05-03 14:20:15 +03:00
_rand = 0
2016-09-19 16:51:28 +03:00
2019-04-19 14:28:11 +03:00
def vulnTest ( ) :
"""
Runs the testing against ' vulnserver '
"""
2019-11-06 13:52:50 +03:00
TESTS = (
2019-11-30 06:42:38 +03:00
( u " -u <url> --flush-session --sql-query= \" SELECT ' \u0161 u \u0107 uraj ' \" --technique=U " , ( u " : ' \u0161 u \u0107 uraj ' " , ) ) ,
( u " -u <url> --flush-session --sql-query= \" SELECT ' \u0161 u \u0107 uraj ' \" --technique=B --no-escape " , ( u " : ' \u0161 u \u0107 uraj ' " , ) ) ,
2019-11-28 01:26:39 +03:00
( " --list-tampers " , ( " between " , " MySQL " , " xforwardedfor " ) ) ,
2019-11-30 00:03:16 +03:00
( " -r <request> --flush-session -v 5 " , ( " CloudFlare " , " possible DBMS: ' SQLite ' " , " User-agent: foobar " ) ) ,
2019-12-10 15:54:29 +03:00
( " -l <log> --flush-session --keep-alive --skip-waf -v 5 --technique=U --union-from=users --banner --parse-errors " , ( " banner: ' 3. " , " ORDER BY term out of range " , " ~xp_cmdshell " , " Connection: keep-alive " ) ) ,
2019-11-28 02:29:42 +03:00
( " -l <log> --offline --banner -v 5 " , ( " banner: ' 3. " , " ~[TRAFFIC OUT] " ) ) ,
( " -u <url> --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner " , ( " total of 2 targets " , " might be injectable " , " Type: UNION query " , " banner: ' 3. " ) ) ,
( " -u <url> --flush-session --data= ' { \" id \" : 1} ' --banner " , ( " might be injectable " , " 3 columns " , " Payload: { \" id \" " , " Type: boolean-based blind " , " Type: time-based blind " , " Type: UNION query " , " banner: ' 3. " ) ) ,
2019-11-30 00:03:16 +03:00
( " -u <url> --flush-session -H ' Foo: Bar ' -H ' Sna: Fu ' --data= ' <root><param name= \" id \" value= \" 1* \" /></root> ' --union-char=1 --mobile --answers= ' smartphone=3 ' --banner --smart -v 5 " , ( " might be injectable " , " Payload: <root><param name= \" id \" value= \" 1 " , " Type: boolean-based blind " , " Type: time-based blind " , " Type: UNION query " , " banner: ' 3. " , " Nexus " , " Sna: Fu " , " Foo: Bar " ) ) ,
2019-11-17 02:21:33 +03:00
( " -u <url> --flush-session --method=PUT --data= ' a=1&b=2&c=3&id=1 ' --skip-static --dump -T users --start=1 --stop=2 " , ( " might be injectable " , " Parameter: id (PUT) " , " Type: boolean-based blind " , " Type: time-based blind " , " Type: UNION query " , " 2 entries " ) ) ,
( " -u <url> --flush-session -H ' id: 1* ' --tables " , ( " might be injectable " , " Parameter: id #1* ((custom) HEADER) " , " Type: boolean-based blind " , " Type: time-based blind " , " Type: UNION query " , " users " ) ) ,
2019-12-10 15:54:29 +03:00
( " -u <url> --flush-session --banner --invalid-logical --technique=B --predict-output --test-filter= ' OR boolean ' --tamper=space2dash " , ( " banner: ' 3. " , " LIKE " ) ) ,
2019-11-17 02:21:33 +03:00
( " -u <url> --flush-session --cookie= \" PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2 \" --tables --union-cols=3 " , ( " might be injectable " , " Cookie #1* ((custom) HEADER) " , " Type: boolean-based blind " , " Type: time-based blind " , " Type: UNION query " , " users " ) ) ,
2019-11-28 02:29:42 +03:00
( " -u <url> --flush-session --null-connection --technique=B --tamper=between,randomcase --banner " , ( " NULL connection is supported with HEAD method " , " banner: ' 3. " ) ) ,
2019-11-26 01:47:29 +03:00
( " -u <url> --flush-session --parse-errors --test-filter= \" subquery \" --eval= \" import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest() \" --referer= \" localhost \" " , ( " might be injectable " , " : syntax error " , " back-end DBMS: SQLite " , " WHERE or HAVING clause (subquery " ) ) ,
2019-11-28 02:29:42 +03:00
( " -u <url> --banner --schema --dump -T users --binary-fields=surname --where \" id>3 \" " , ( " banner: ' 3. " , " INTEGER " , " TEXT " , " id " , " name " , " surname " , " 2 entries " , " 6E616D6569736E756C6C " ) ) ,
2019-11-30 00:03:16 +03:00
( " -u <url> --technique=U --fresh-queries --force-partial --dump -T users --answer= \" crack=n \" -v 3 " , ( " performed 6 queries " , " nameisnull " , " ~using default dictionary " ) ) ,
2019-11-18 00:08:18 +03:00
( " -u <url> --flush-session --all " , ( " 5 entries " , " Type: boolean-based blind " , " Type: time-based blind " , " Type: UNION query " , " luther " , " blisset " , " fluffy " , " 179ad45c6ce2cb97cf1029e212046e81 " , " NULL " , " nameisnull " , " testpass " ) ) ,
2019-11-17 20:54:33 +03:00
( " -u <url> -z \" tec=B \" --hex --fresh-queries --threads=4 --sql-query= \" SELECT * FROM users \" " , ( " SELECT * FROM users [5] " , " nameisnull " ) ) ,
2019-11-18 14:08:26 +03:00
( " -u ' <url>&echo=foobar* ' --flush-session " , ( " might be vulnerable to cross-site scripting " , ) ) ,
2019-11-28 02:29:42 +03:00
( " -u ' <url>&query=* ' --flush-session --technique=Q --banner " , ( " Title: SQLite inline queries " , " banner: ' 3. " ) ) ,
2019-11-17 02:36:39 +03:00
( " -d <direct> --flush-session --dump -T users --binary-fields=name --where \" id=3 \" " , ( " 7775 " , " 179ad45c6ce2cb97cf1029e212046e81 (testpass) " , ) ) ,
2019-11-28 02:29:42 +03:00
( " -d <direct> --flush-session --banner --schema --sql-query= \" UPDATE users SET name= ' foobar ' WHERE id=5; SELECT * FROM users; SELECT 987654321 \" " , ( " banner: ' 3. " , " INTEGER " , " TEXT " , " id " , " name " , " surname " , " 5, foobar, nameisnull " , " [*] 987654321 " , ) ) ,
2019-11-06 13:52:50 +03:00
)
2019-04-19 14:28:11 +03:00
retVal = True
2019-11-06 13:52:50 +03:00
count = 0
2019-04-29 15:19:56 +03:00
address , port = " 127.0.0.10 " , random . randint ( 1025 , 65535 )
2019-04-19 14:28:11 +03:00
def _thread ( ) :
vulnserver . init ( quiet = True )
2019-04-29 15:19:56 +03:00
vulnserver . run ( address = address , port = port )
2019-04-19 14:28:11 +03:00
thread = threading . Thread ( target = _thread )
thread . daemon = True
thread . start ( )
2019-11-14 03:29:51 +03:00
while True :
s = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
try :
s . connect ( ( address , port ) )
break
except :
time . sleep ( 1 )
2019-11-17 18:04:22 +03:00
handle , database = tempfile . mkstemp ( suffix = " .sqlite " )
2019-11-17 02:21:33 +03:00
os . close ( handle )
2019-11-17 18:04:22 +03:00
with sqlite3 . connect ( database ) as conn :
2019-11-17 02:21:33 +03:00
c = conn . cursor ( )
c . executescript ( vulnserver . SCHEMA )
2019-11-17 18:04:22 +03:00
handle , request = tempfile . mkstemp ( suffix = " .req " )
os . close ( handle )
2019-11-28 01:26:39 +03:00
handle , log = tempfile . mkstemp ( suffix = " .log " )
os . close ( handle )
2019-11-30 00:03:16 +03:00
content = " POST / HTTP/1.0 \n User-agent: foobar \n Host: %s : %s \n \n id=1 \n " % ( address , port )
2019-11-28 01:26:39 +03:00
open ( request , " w+ " ) . write ( content )
open ( log , " w+ " ) . write ( ' <port> %d </port><request base64= " true " ><![CDATA[ %s ]]></request> ' % ( port , encodeBase64 ( content , binary = False ) ) )
2019-11-17 18:04:22 +03:00
2019-11-17 02:21:33 +03:00
url = " http:// %s : %d /?id=1 " % ( address , port )
2019-11-17 18:04:22 +03:00
direct = " sqlite3:// %s " % database
2019-11-17 02:21:33 +03:00
2019-11-06 13:52:50 +03:00
for options , checks in TESTS :
2019-11-11 13:20:12 +03:00
status = ' %d / %d ( %d %% ) ' % ( count , len ( TESTS ) , round ( 100.0 * count / len ( TESTS ) ) )
dataToStdout ( " \r [ %s ] [INFO] complete: %s " % ( time . strftime ( " %X " ) , status ) )
2019-11-28 01:26:39 +03:00
cmd = " %s %s %s --batch " % ( sys . executable , os . path . abspath ( os . path . join ( os . path . dirname ( __file__ ) , " .. " , " .. " , " sqlmap.py " ) ) , options . replace ( " <url> " , url ) . replace ( " <direct> " , direct ) . replace ( " <request> " , request ) . replace ( " <log> " , log ) )
2019-05-09 17:38:44 +03:00
output = shellExec ( cmd )
2019-04-30 14:20:31 +03:00
2019-11-28 01:26:39 +03:00
if not all ( ( check in output if not check . startswith ( ' ~ ' ) else check [ 1 : ] not in output ) for check in checks ) :
2019-05-09 17:38:44 +03:00
dataToStdout ( " --- \n \n $ %s \n " % cmd )
dataToStdout ( " %s --- \n " % clearColors ( output ) )
2019-04-19 14:28:11 +03:00
retVal = False
count + = 1
clearConsoleLine ( )
if retVal :
logger . info ( " vuln test final result: PASSED " )
else :
logger . error ( " vuln test final result: FAILED " )
2019-04-19 15:36:23 +03:00
return retVal
2019-05-03 14:20:15 +03:00
def dirtyPatchRandom ( ) :
"""
Unifying random generated data across different Python versions
"""
def _lcg ( ) :
global _rand
a = 1140671485
c = 128201163
m = 2 * * 24
_rand = ( a * _rand + c ) % m
return _rand
def _randint ( a , b ) :
_ = a + ( _lcg ( ) % ( b - a + 1 ) )
return _
def _choice ( seq ) :
return seq [ _randint ( 0 , len ( seq ) - 1 ) ]
def _sample ( population , k ) :
return [ _choice ( population ) for _ in xrange ( k ) ]
def _seed ( seed ) :
global _rand
_rand = seed
random . choice = _choice
random . randint = _randint
random . sample = _sample
random . seed = _seed
2010-09-15 17:28:56 +04:00
def smokeTest ( ) :
"""
2013-03-14 00:57:09 +04:00
Runs the basic smoke testing of a program
2010-09-15 17:28:56 +04:00
"""
2013-03-14 00:57:09 +04:00
2019-05-03 14:20:15 +03:00
dirtyPatchRandom ( )
2020-01-03 15:46:12 +03:00
content = open ( paths . ERRORS_XML , " r " ) . read ( )
for regex in re . findall ( r ' <error regexp= " (.+?) " /> ' , content ) :
try :
re . compile ( regex )
except re . error :
errMsg = " smoke test failed at compiling ' %s ' " % regex
logger . error ( errMsg )
return False
2010-09-15 17:28:56 +04:00
retVal = True
2010-09-26 14:47:04 +04:00
count , length = 0 , 0
2010-09-27 15:20:48 +04:00
2019-01-06 02:37:30 +03:00
for root , _ , files in os . walk ( paths . SQLMAP_ROOT_PATH ) :
if any ( _ in root for _ in ( " thirdparty " , " extra " ) ) :
continue
for filename in files :
if os . path . splitext ( filename ) [ 1 ] . lower ( ) == " .py " and filename != " __init__.py " :
length + = 1
for root , _ , files in os . walk ( paths . SQLMAP_ROOT_PATH ) :
if any ( _ in root for _ in ( " thirdparty " , " extra " ) ) :
continue
for filename in files :
2019-12-06 00:20:00 +03:00
if os . path . splitext ( filename ) [ 1 ] . lower ( ) == " .py " and filename not in ( " __init__.py " , " gui.py " ) :
2019-01-06 02:37:30 +03:00
path = os . path . join ( root , os . path . splitext ( filename ) [ 0 ] )
path = path . replace ( paths . SQLMAP_ROOT_PATH , ' . ' )
path = path . replace ( os . sep , ' . ' ) . lstrip ( ' . ' )
try :
__import__ ( path )
module = sys . modules [ path ]
2019-01-22 03:20:27 +03:00
except Exception as ex :
2019-01-06 02:37:30 +03:00
retVal = False
dataToStdout ( " \r " )
2019-01-22 03:20:27 +03:00
errMsg = " smoke test failed at importing module ' %s ' ( %s ): \n %s " % ( path , os . path . join ( root , filename ) , ex )
2019-01-06 02:37:30 +03:00
logger . error ( errMsg )
else :
2019-05-06 12:41:19 +03:00
logger . setLevel ( logging . CRITICAL )
kb . smokeMode = True
2019-05-29 17:42:04 +03:00
( failure_count , _ ) = doctest . testmod ( module )
2019-05-06 12:41:19 +03:00
kb . smokeMode = False
logger . setLevel ( logging . INFO )
2019-01-06 02:37:30 +03:00
if failure_count > 0 :
2010-09-15 17:28:56 +04:00
retVal = False
2019-01-06 02:37:30 +03:00
count + = 1
status = ' %d / %d ( %d %% ) ' % ( count , length , round ( 100.0 * count / length ) )
dataToStdout ( " \r [ %s ] [INFO] complete: %s " % ( time . strftime ( " %X " ) , status ) )
2010-09-26 14:47:04 +04:00
2019-06-05 11:37:11 +03:00
def _ ( node ) :
for __ in dir ( node ) :
if not __ . startswith ( ' _ ' ) :
candidate = getattr ( node , __ )
if isinstance ( candidate , str ) :
if ' \\ ' in candidate :
try :
re . compile ( candidate )
except :
errMsg = " smoke test failed at compiling ' %s ' " % candidate
logger . error ( errMsg )
raise
else :
_ ( candidate )
for dbms in queries :
try :
_ ( queries [ dbms ] )
except :
retVal = False
2010-11-24 00:00:42 +03:00
clearConsoleLine ( )
2010-09-15 17:28:56 +04:00
if retVal :
2010-09-27 15:20:48 +04:00
logger . info ( " smoke test final result: PASSED " )
2010-09-15 17:28:56 +04:00
else :
2010-09-27 15:20:48 +04:00
logger . error ( " smoke test final result: FAILED " )
2010-09-15 17:28:56 +04:00
return retVal
2010-09-15 17:32:42 +04:00
2010-09-27 17:26:46 +04:00
def adjustValueType ( tagName , value ) :
2019-05-02 12:26:31 +03:00
for family in optDict :
2010-09-27 17:26:46 +04:00
for name , type_ in optDict [ family ] . items ( ) :
if type ( type_ ) == tuple :
type_ = type_ [ 0 ]
if tagName == name :
if type_ == " boolean " :
value = ( value == " True " )
elif type_ == " integer " :
value = int ( value )
elif type_ == " float " :
value = float ( value )
break
return value
2013-02-05 13:11:38 +04:00
def initCase ( switches , count ) :
2016-09-19 16:51:28 +03:00
_failures . failedItems = [ ]
_failures . failedParseOn = None
_failures . failedTraceBack = None
2012-12-17 15:41:43 +04:00
2016-05-31 14:02:26 +03:00
paths . SQLMAP_OUTPUT_PATH = tempfile . mkdtemp ( prefix = " %s %d - " % ( MKSTEMP_PREFIX . TESTING , count ) )
2011-04-30 17:20:05 +04:00
paths . SQLMAP_DUMP_PATH = os . path . join ( paths . SQLMAP_OUTPUT_PATH , " %s " , " dump " )
paths . SQLMAP_FILES_PATH = os . path . join ( paths . SQLMAP_OUTPUT_PATH , " %s " , " files " )
2012-12-17 15:29:33 +04:00
logger . debug ( " using output directory ' %s ' for this test case " % paths . SQLMAP_OUTPUT_PATH )
2013-02-22 20:26:48 +04:00
LOGGER_HANDLER . stream = sys . stdout = tempfile . SpooledTemporaryFile ( max_size = 0 , mode = " w+b " , prefix = " sqlmapstdout- " )
2010-09-26 18:02:13 +04:00
cmdLineOptions = cmdLineParser ( )
2010-09-27 15:20:48 +04:00
if switches :
for key , value in switches . items ( ) :
2010-09-27 17:26:46 +04:00
if key in cmdLineOptions . __dict__ :
cmdLineOptions . __dict__ [ key ] = value
2010-09-27 15:20:48 +04:00
2013-01-29 20:23:30 +04:00
initOptions ( cmdLineOptions , True )
init ( )
2010-09-26 18:56:55 +04:00
def cleanCase ( ) :
2010-09-27 15:20:48 +04:00
shutil . rmtree ( paths . SQLMAP_OUTPUT_PATH , True )
2010-09-26 18:02:13 +04:00
2013-02-03 19:39:07 +04:00
def runCase ( parse ) :
2012-12-19 18:34:34 +04:00
retVal = True
2013-01-14 14:23:40 +04:00
handled_exception = None
unhandled_exception = None
2012-12-19 18:23:38 +04:00
result = False
console = " "
try :
result = start ( )
except KeyboardInterrupt :
2013-01-26 19:33:09 +04:00
pass
2019-01-22 03:20:27 +03:00
except SqlmapBaseException as ex :
handled_exception = ex
except Exception as ex :
unhandled_exception = ex
2012-12-19 18:23:38 +04:00
finally :
sys . stdout . seek ( 0 )
console = sys . stdout . read ( )
2012-12-19 18:34:34 +04:00
LOGGER_HANDLER . stream = sys . stdout = sys . __stdout__
2012-12-19 18:23:38 +04:00
2013-01-14 14:23:40 +04:00
if unhandled_exception :
2016-09-19 16:51:28 +03:00
_failures . failedTraceBack = " unhandled exception: %s " % str ( traceback . format_exc ( ) )
2013-01-18 17:02:35 +04:00
retVal = None
2013-01-14 14:23:40 +04:00
elif handled_exception :
2016-09-19 16:51:28 +03:00
_failures . failedTraceBack = " handled exception: %s " % str ( traceback . format_exc ( ) )
2013-01-18 17:02:35 +04:00
retVal = None
elif result is False : # this means no SQL injection has been detected - if None, ignore
2010-09-26 18:56:55 +04:00
retVal = False
2014-11-04 02:34:35 +03:00
console = getUnicode ( console , encoding = sys . stdin . encoding )
2013-01-14 17:42:50 +04:00
2012-12-17 15:29:33 +04:00
if parse and retVal :
2012-12-20 13:37:20 +04:00
with codecs . open ( conf . dumper . getOutputFile ( ) , " rb " , UNICODE_ENCODING ) as f :
content = f . read ( )
2012-12-19 17:47:17 +04:00
2013-01-30 14:32:56 +04:00
for item , parse_from_console_output in parse :
parse_on = console if parse_from_console_output else content
2012-12-19 19:58:06 +04:00
2010-09-27 15:20:48 +04:00
if item . startswith ( " r ' " ) and item . endswith ( " ' " ) :
2012-12-19 19:58:06 +04:00
if not re . search ( item [ 2 : - 1 ] , parse_on , re . DOTALL ) :
2013-01-18 17:02:35 +04:00
retVal = None
2016-09-19 16:51:28 +03:00
_failures . failedItems . append ( item )
2012-12-20 13:37:20 +04:00
elif item not in parse_on :
2013-01-18 17:02:35 +04:00
retVal = None
2016-09-19 16:51:28 +03:00
_failures . failedItems . append ( item )
2010-09-26 18:56:55 +04:00
2016-09-19 16:51:28 +03:00
if _failures . failedItems :
_failures . failedParseOn = console
2013-01-14 03:15:56 +04:00
2013-01-15 19:51:03 +04:00
elif retVal is False :
2016-09-19 16:51:28 +03:00
_failures . failedParseOn = console
2013-01-14 14:23:40 +04:00
2010-09-26 18:56:55 +04:00
return retVal
2010-09-26 18:02:13 +04:00
2010-09-27 15:20:48 +04:00
def replaceVars ( item , vars_ ) :
2010-09-26 18:02:13 +04:00
retVal = item
2012-12-19 18:25:29 +04:00
2010-09-27 15:20:48 +04:00
if item and vars_ :
2019-05-08 13:28:50 +03:00
for var in re . findall ( r " \ $ \ { ([^}]+) \ } " , item ) :
2010-09-27 15:20:48 +04:00
if var in vars_ :
retVal = retVal . replace ( " $ { %s } " % var , vars_ [ var ] )
2012-12-19 18:25:29 +04:00
2012-12-06 17:15:44 +04:00
return retVal