From bb93bced04a2b9e58a76452cb7541cfc9605673a Mon Sep 17 00:00:00 2001 From: Warmlight <344053630@qq.com> Date: Sun, 12 Jan 2025 23:38:02 +0800 Subject: [PATCH] =?UTF-8?q?entries=E6=89=B9=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sqlmap-master/plugins/generic/entries.py | 239 ++++++++++++++----- 1 file changed, 177 insertions(+), 62 deletions(-) diff --git a/src/sqlmap-master/plugins/generic/entries.py b/src/sqlmap-master/plugins/generic/entries.py index f6e8c01..ba10471 100644 --- a/src/sqlmap-master/plugins/generic/entries.py +++ b/src/sqlmap-master/plugins/generic/entries.py @@ -5,10 +5,14 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ +# 导入正则表达式模块 import re +# 从lib.core.agent模块导入agent对象,用于处理注入过程中的各种细节 from lib.core.agent import agent +# 从lib.core.bigarray模块导入BigArray类,用于高效存储和处理大量数据 from lib.core.bigarray import BigArray +# 从lib.core.common模块导入各种常用函数和常量,如Backend、clearConsoleLine、getLimitRange等 from lib.core.common import Backend from lib.core.common import clearConsoleLine from lib.core.common import getLimitRange @@ -25,33 +29,47 @@ from lib.core.common import singleTimeLogMessage from lib.core.common import singleTimeWarnMessage from lib.core.common import unArrayizeValue from lib.core.common import unsafeSQLIdentificatorNaming +# 从lib.core.convert模块导入getConsoleLength函数,用于获取控制台字符串的长度 from lib.core.convert import getConsoleLength from lib.core.convert import getUnicode +# 从lib.core.data模块导入conf和kb对象,用于存储配置信息和知识库信息 from lib.core.data import conf from lib.core.data import kb +# 从lib.core.data模块导入logger对象,用于记录日志信息 from lib.core.data import logger +# 从lib.core.data模块导入queries对象,用于存储各种数据库的查询语句 from lib.core.data import queries +# 从lib.core.dicts模块导入DUMP_REPLACEMENTS字典,用于替换转储数据中的特殊字符 from lib.core.dicts import DUMP_REPLACEMENTS +# 从lib.core.enums模块导入各种枚举类型,如CHARSET_TYPE、DBMS、EXPECTED、PAYLOAD等 from lib.core.enums import CHARSET_TYPE from lib.core.enums import DBMS from lib.core.enums import EXPECTED from lib.core.enums import PAYLOAD +# 从lib.core.exception模块导入各种自定义异常类 from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUnsupportedFeatureException +# 从lib.core.settings模块导入各种配置常量 from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD from lib.core.settings import CURRENT_DB from lib.core.settings import METADB_SUFFIX from lib.core.settings import NULL from lib.core.settings import PLUS_ONE_DBMSES from lib.core.settings import UPPER_CASE_DBMSES +# 从lib.request模块导入inject函数,用于执行SQL注入 from lib.request import inject +# 从lib.utils.hash模块导入attackDumpedTable函数,用于对转储的表数据进行攻击 from lib.utils.hash import attackDumpedTable +# 从lib.utils.pivotdumptable模块导入pivotDumpTable函数,用于执行透视转储表操作 from lib.utils.pivotdumptable import pivotDumpTable +# 导入第三方six库,用于兼容Python 2和Python 3 from thirdparty import six +# 导入第三方six库的zip函数,并重命名为_zip from thirdparty.six.moves import zip as _zip +# 定义Entries类,用于封装枚举数据库条目的相关功能 class Entries(object): """ This class defines entries' enumeration functionalities for plugins. @@ -60,9 +78,12 @@ class Entries(object): def __init__(self): pass + # 定义dumpTable方法,用于转储指定表的条目 def dumpTable(self, foundData=None): + # 强制执行数据库枚举,确保已获取数据库类型 self.forceDbmsEnum() + # 如果没有指定数据库或指定为当前数据库,则获取当前数据库 if conf.db is None or conf.db == CURRENT_DB: if conf.db is None: warnMsg = "missing database parameter. sqlmap is going " @@ -71,65 +92,81 @@ class Entries(object): logger.warning(warnMsg) conf.db = self.getCurrentDb() - + # 如果指定了数据库 elif conf.db is not None: + # 如果数据库是属于大写数据库类型,则将其转换为大写 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: conf.db = conf.db.upper() + # 如果数据库名包含逗号,则抛出异常,因为只允许一个数据库名 if ',' in conf.db: errMsg = "only one database name is allowed when enumerating " errMsg += "the tables' columns" raise SqlmapMissingMandatoryOptionException(errMsg) + # 如果数据库名匹配排除模式,则跳过 if conf.exclude and re.search(conf.exclude, conf.db, re.I) is not None: infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(conf.db) singleTimeLogMessage(infoMsg) return + # 对数据库名进行安全处理 conf.db = safeSQLIdentificatorNaming(conf.db) or "" + # 如果指定了表 if conf.tbl: + # 如果表名是属于大写数据库类型,则将其转换为大写 if Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: conf.tbl = conf.tbl.upper() - + # 将表名拆分为列表 tblList = conf.tbl.split(',') + # 如果没有指定表 else: + # 获取所有表 self.getTables() - + # 如果已缓存表信息 if len(kb.data.cachedTables) > 0: + # 获取表列表 tblList = list(six.itervalues(kb.data.cachedTables)) - + # 如果表列表嵌套,则解包 if tblList and isListLike(tblList[0]): tblList = tblList[0] + # 如果指定了数据库但未能获取表信息 elif conf.db and not conf.search: errMsg = "unable to retrieve the tables " errMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) raise SqlmapNoneDataException(errMsg) else: return - + # 对表名列表中的表名进行安全处理 for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) - + # 遍历表列表 for tbl in tblList: + # 如果检测到键盘中断,则跳出循环 if kb.dumpKeyboardInterrupt: break - + # 如果表名匹配排除模式,则跳过 if conf.exclude and re.search(conf.exclude, tbl, re.I) is not None: infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) singleTimeLogMessage(infoMsg) continue - + # 设置当前表名 conf.tbl = tbl + # 初始化已转储的表数据 kb.data.dumpedTable = {} - + # 如果没有传入已发现的列数据 if foundData is None: + # 清空缓存的列信息 kb.data.cachedColumns = {} + # 获取列信息,仅获取列名,并设置转储模式 self.getColumns(onlyColNames=True, dumpMode=True) + # 如果传入了已发现的列数据,则直接使用 else: kb.data.cachedColumns = foundData try: + # 根据数据库类型设置转储表名 if Backend.isDbms(DBMS.INFORMIX): kb.dumpTable = "%s:%s" % (conf.db, tbl) elif Backend.isDbms(DBMS.SQLITE): @@ -139,6 +176,7 @@ class Entries(object): else: kb.dumpTable = "%s.%s" % (conf.db, tbl) + # 如果未能获取列信息,则跳过当前表 if safeSQLIdentificatorNaming(conf.db) not in kb.data.cachedColumns or safeSQLIdentificatorNaming(tbl, True) not in kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]: warnMsg = "unable to enumerate the columns for table '%s'" % unsafeSQLIdentificatorNaming(tbl) if METADB_SUFFIX.upper() not in conf.db.upper(): @@ -148,12 +186,16 @@ class Entries(object): continue + # 获取当前表的列信息 columns = kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)] + # 对列名列表进行排序 colList = sorted(column for column in columns if column) + # 如果指定了排除模式,则从列名列表中排除匹配的列 if conf.exclude: colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] + # 如果没有可用的列名,则跳过当前表 if not colList: warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) if METADB_SUFFIX.upper() not in conf.db.upper(): @@ -161,9 +203,11 @@ class Entries(object): warnMsg += " (no usable column names)" logger.warning(warnMsg) continue - + # 设置全局变量 kb.dumpColumns 为当前表需要转储的列名列表 kb.dumpColumns = [unsafeSQLIdentificatorNaming(_) for _ in colList] + # 将列名列表转换为逗号分隔的字符串 colNames = colString = ','.join(column for column in colList) + # 获取转储表的根查询 rootQuery = queries[Backend.getIdentifiedDbms()].dump_table infoMsg = "fetching entries" @@ -173,18 +217,22 @@ class Entries(object): if METADB_SUFFIX.upper() not in conf.db.upper(): infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) logger.info(infoMsg) - + + # 遍历列名列表,对每个列名进行预处理 for column in colList: _ = agent.preprocessField(tbl, column) if _ != column: colString = re.sub(r"\b%s\b" % re.escape(column), _.replace("\\", r"\\"), colString) + # 初始化条目计数 entriesCount = 0 + # 如果存在可用的注入技术,或使用了--direct参数 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: + # 初始化条目列表 entries = [] query = None - + # 根据数据库类型构建查询语句 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): query = rootQuery.inband.query % (colString, tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): @@ -247,9 +295,10 @@ class Entries(object): query = rootQuery.inband.query % (colString, conf.db, tbl) query = agent.whereQuery(query) - + # 如果没有获取到条目,并且查询语句存在,并且没有检测到键盘中断 if not entries and query and not kb.dumpKeyboardInterrupt: try: + # 执行查询语句,获取条目信息 entries = inject.getValue(query, blind=False, time=False, dump=True) except KeyboardInterrupt: entries = None @@ -257,7 +306,7 @@ class Entries(object): clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warning(warnMsg) - + # 如果成功获取到条目 if not isNoneValue(entries): if isinstance(entries, six.string_types): entries = [entries] @@ -266,6 +315,7 @@ class Entries(object): entriesCount = len(entries) + # 遍历每个列名和条目,更新转储的表数据信息 for index, column in enumerate(colList): if column not in kb.data.dumpedTable: kb.data.dumpedTable[column] = {"length": len(column), "values": BigArray()} @@ -273,19 +323,20 @@ class Entries(object): for entry in entries: if entry is None or len(entry) == 0: continue - + # 如果条目是字符串类型 if isinstance(entry, six.string_types): colEntry = entry + # 否则,获取指定索引的条目 else: colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u'' maxLen = max(getConsoleLength(column), getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colEntry), getUnicode(colEntry)))) - + # 更新最大长度 if maxLen > kb.data.dumpedTable[column]["length"]: kb.data.dumpedTable[column]["length"] = maxLen - + # 添加条目值 kb.data.dumpedTable[column]["values"].append(colEntry) - + # 如果没有转储表数据,并且可以使用盲注方式,且不是直接模式 if not kb.data.dumpedTable and isInferenceAvailable() and not conf.direct: infoMsg = "fetching number of " if conf.col: @@ -293,7 +344,8 @@ class Entries(object): infoMsg += "entries for table '%s' " % unsafeSQLIdentificatorNaming(tbl) infoMsg += "in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) logger.info(infoMsg) - + + # 构建盲注获取条目计数的查询语句 if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): query = rootQuery.blind.count % (tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper()))) elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.MAXDB, DBMS.ACCESS, DBMS.FIREBIRD, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.RAIMA): @@ -307,21 +359,22 @@ class Entries(object): query = agent.whereQuery(query) + # 执行盲注查询,获取条目计数 count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) lengths = {} entries = {} - + # 如果条目计数为0 if count == 0: warnMsg = "table '%s' " % unsafeSQLIdentificatorNaming(tbl) warnMsg += "in database '%s' " % unsafeSQLIdentificatorNaming(conf.db) warnMsg += "appears to be empty" logger.warning(warnMsg) - + # 初始化长度和条目 for column in colList: lengths[column] = len(column) entries[column] = [] - + # 如果未能获取条目计数 elif not isNumPosStrValue(count): warnMsg = "unable to retrieve the number of " if conf.col: @@ -331,7 +384,7 @@ class Entries(object): logger.warning(warnMsg) continue - + # 对于特定数据库 elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL, DBMS.INFORMIX, DBMS.MCKOI, DBMS.RAIMA): if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.RAIMA): table = tbl @@ -339,7 +392,7 @@ class Entries(object): table = "%s.%s" % (conf.db, tbl) if conf.db else tbl elif Backend.isDbms(DBMS.INFORMIX): table = "%s:%s" % (conf.db, tbl) if conf.db else tbl - + # 如果是mssql并且没有强制透视 if Backend.isDbms(DBMS.MSSQL) and not conf.forcePivoting: warnMsg = "in case of table dumping problems (e.g. column entry order) " warnMsg += "you are advised to rerun with '--force-pivoting'" @@ -369,7 +422,7 @@ class Entries(object): clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warning(warnMsg) - + # 如果没有获取到条目,且没有检测到键盘中断 if not entries and not kb.dumpKeyboardInterrupt: try: retVal = pivotDumpTable(table, colList, count, blind=True) @@ -382,12 +435,12 @@ class Entries(object): if retVal: entries, lengths = retVal - + # 对于其他数据库 else: emptyColumns = [] plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES indexRange = getLimitRange(count, plusOne=plusOne) - + # 如果列的数量小于行数,且大于阈值,则进行空列检查 if len(colList) < len(indexRange) > CHECK_ZERO_COLUMNS_THRESHOLD: debugMsg = "checking for empty columns" logger.debug(infoMsg) @@ -398,7 +451,7 @@ class Entries(object): debugMsg = "column '%s' of table '%s' will not be " % (column, kb.dumpTable) debugMsg += "dumped as it appears to be empty" logger.debug(debugMsg) - + try: for index in indexRange: for column in colList: @@ -409,7 +462,7 @@ class Entries(object): if column not in entries: entries[column] = BigArray() - + # 根据不同的数据库类型,构建盲注查询语句 if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE): query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, sorted(colList, key=len)[0], index) elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,): @@ -428,10 +481,10 @@ class Entries(object): query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index) query = agent.whereQuery(query) - + # 执行盲注查询,获取值 value = NULL if column in emptyColumns else inject.getValue(query, union=False, error=False, dump=True) value = '' if value is None else value - + # 更新最大长度和条目值 lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) entries[column].append(value) @@ -440,14 +493,15 @@ class Entries(object): clearConsoleLine() warnMsg = "Ctrl+C detected in dumping phase" logger.warning(warnMsg) - + # 遍历获取到的条目,将结果保存到kb.data.dumpedTable中 for column, columnEntries in entries.items(): length = max(lengths[column], len(column)) kb.data.dumpedTable[column] = {"length": length, "values": columnEntries} - + # 获取总行数 entriesCount = len(columnEntries) + # 如果没有转储表数据,或者条目数为0,且有权限标识 if len(kb.data.dumpedTable) == 0 or (entriesCount == 0 and kb.permissionFlag): warnMsg = "unable to retrieve the entries " if conf.col: @@ -456,15 +510,18 @@ class Entries(object): warnMsg += "in database '%s'%s" % (unsafeSQLIdentificatorNaming(conf.db), " (permission denied)" if kb.permissionFlag else "") logger.warning(warnMsg) else: + # 保存转储的信息,包括总行数、表名和数据库名 kb.data.dumpedTable["__infos__"] = {"count": entriesCount, "table": safeSQLIdentificatorNaming(tbl, True), "db": safeSQLIdentificatorNaming(conf.db)} try: + # 对转储的数据进行攻击 attackDumpedTable() except (IOError, OSError) as ex: errMsg = "an error occurred while attacking " errMsg += "table dump ('%s')" % getSafeExString(ex) logger.critical(errMsg) + # 将转储的数据传递给dumper conf.dumper.dbTableValues(kb.data.dumpedTable) except SqlmapConnectionException as ex: @@ -473,40 +530,46 @@ class Entries(object): logger.critical(errMsg) finally: + # 清空全局变量 kb.dumpColumns = None kb.dumpTable = None + # 定义dumpAll方法,用于转储所有数据库中的所有表的所有条目 def dumpAll(self): + # 如果指定了数据库,但没有指定表,则只转储该数据库下的表 if conf.db is not None and conf.tbl is None: self.dumpTable() return - + # 如果是MySQL数据库,且没有information_schema if Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: errMsg = "information_schema not available, " errMsg += "back-end DBMS is MySQL < 5.0" raise SqlmapUnsupportedFeatureException(errMsg) - + infoMsg = "sqlmap will dump entries of all tables from all databases now" logger.info(infoMsg) - + + # 清空表和列的全局变量 conf.tbl = None conf.col = None + # 获取所有表 self.getTables() - + + # 如果有缓存的表信息 if kb.data.cachedTables: if isinstance(kb.data.cachedTables, list): kb.data.cachedTables = {None: kb.data.cachedTables} - + + # 遍历数据库和表 for db, tables in kb.data.cachedTables.items(): conf.db = db - + for table in tables: if conf.exclude and re.search(conf.exclude, table, re.I) is not None: infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table) logger.info(infoMsg) continue - try: conf.tbl = table kb.data.cachedColumns = {} @@ -517,45 +580,57 @@ class Entries(object): infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table) logger.info(infoMsg) + # 定义dumpFoundColumn方法,用于转储已发现的列 def dumpFoundColumn(self, dbs, foundCols, colConsider): message = "do you want to dump found column(s) entries? [Y/n] " - + # 询问用户是否要转储已发现的列 if not readInput(message, default='Y', boolean=True): return dumpFromDbs = [] - message = "which database(s)?\n[a]ll (default)\n" + message = "which database(s)?\ +[a]ll (default)\ +" + # 构建数据库选项 for db, tblData in dbs.items(): if tblData: - message += "[%s]\n" % unsafeSQLIdentificatorNaming(db) + message += "[%s]\ +" % unsafeSQLIdentificatorNaming(db) message += "[q]uit" + # 接收用户选择 choice = readInput(message, default='a') - + # 处理用户选择 if not choice or choice in ('a', 'A'): dumpFromDbs = list(dbs.keys()) elif choice in ('q', 'Q'): return else: dumpFromDbs = choice.replace(" ", "").split(',') - + # 遍历数据库 for db, tblData in dbs.items(): if db not in dumpFromDbs or not tblData: continue conf.db = db dumpFromTbls = [] - message = "which table(s) of database '%s'?\n" % unsafeSQLIdentificatorNaming(db) - message += "[a]ll (default)\n" + message = "which table(s) of database '%s'?\ +" % unsafeSQLIdentificatorNaming(db) + message += "[a]ll (default)\ +" + # 构建表选项 for tbl in tblData: - message += "[%s]\n" % tbl + message += "[%s]\ +" % tbl - message += "[s]kip\n" + message += "[s]kip\ +" message += "[q]uit" + # 接收用户选择 choice = readInput(message, default='a') - + # 处理用户选择 if not choice or choice in ('a', 'A'): dumpFromTbls = tblData elif choice in ('s', 'S'): @@ -564,80 +639,120 @@ class Entries(object): return else: dumpFromTbls = choice.replace(" ", "").split(',') - + # 遍历表 for table, columns in tblData.items(): if table not in dumpFromTbls: continue conf.tbl = table colList = [_ for _ in columns if _] - + # 如果指定了排除模式,则排除匹配的列 if conf.exclude: colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None] - + # 设置需要转储的列 conf.col = ','.join(colList) kb.data.cachedColumns = {} kb.data.dumpedTable = {} data = self.dumpTable(dbs) - + # 如果成功转储了数据,则传递给dumper if data: conf.dumper.dbTableValues(data) def dumpFoundTables(self, tables): + # 1. 定义一个消息,询问用户是否要转储发现的表条目 message = "do you want to dump found table(s) entries? [Y/n] " + # 2. 使用 readInput 函数获取用户输入,默认值为 'Y' (是),并将其转换为布尔值 + # 如果用户输入 'n' (否) 或者其他非 'y' 的值,则返回 False,否则返回 True。 + # 如果用户输入为否,则直接返回,不进行后续的转储操作 if not readInput(message, default='Y', boolean=True): return + # 3. 初始化一个空列表 dumpFromDbs,用于存储用户选择要转储的数据库 dumpFromDbs = [] - message = "which database(s)?\n[a]ll (default)\n" + # 4. 构建一个消息,用于提示用户选择要转储的数据库,其中 [a]ll (default) 表示默认选择全部数据库 + message = "which database(s)?\ + [a]ll (default)\ + " + # 5. 遍历传入的 tables 字典,该字典的键是数据库名,值是该数据库下的表列表 for db, tablesList in tables.items(): + # 6. 如果该数据库有表,则将数据库名添加到消息中,并进行安全 SQL 标识符命名处理 if tablesList: - message += "[%s]\n" % unsafeSQLIdentificatorNaming(db) + message += "[%s]\ + " % unsafeSQLIdentificatorNaming(db) + # 7. 在消息中添加一个选项 [q]uit,允许用户退出 message += "[q]uit" + # 8. 使用 readInput 函数获取用户输入,默认值为 'a',表示选择全部数据库 choice = readInput(message, default='a') + # 9. 如果用户没有输入或者输入为 'a' (或 'A'),则将所有数据库添加到 dumpFromDbs 列表中 if not choice or choice.lower() == 'a': dumpFromDbs = list(tables.keys()) + # 10. 如果用户输入为 'q' (或 'Q'),则直接返回,不进行后续的转储操作 elif choice.lower() == 'q': return + # 11. 否则,将用户输入的数据库名按逗号分割,并添加到 dumpFromDbs 列表中 else: dumpFromDbs = choice.replace(" ", "").split(',') + # 12. 遍历 tables 字典,键是数据库名,值是该数据库下的表列表 for db, tablesList in tables.items(): + # 13. 如果当前数据库不在 dumpFromDbs 列表中,或者当前数据库没有表,则跳过该数据库 if db not in dumpFromDbs or not tablesList: continue + # 14. 将当前数据库名赋值给 conf.db (全局配置参数) conf.db = db + # 15. 初始化一个空列表 dumpFromTbls,用于存储用户选择要转储的表 dumpFromTbls = [] - message = "which table(s) of database '%s'?\n" % unsafeSQLIdentificatorNaming(db) - message += "[a]ll (default)\n" - + # 16. 构建一个消息,用于提示用户选择当前数据库下要转储的表 + message = "which table(s) of database '%s'?\ + " % unsafeSQLIdentificatorNaming(db) + # 17. 在消息中添加一个选项 [a]ll (default),表示默认选择全部表 + message += "[a]ll (default)\ + " + + # 18. 遍历当前数据库下的表列表,将表名添加到消息中,并进行安全 SQL 标识符命名处理 for tbl in tablesList: - message += "[%s]\n" % unsafeSQLIdentificatorNaming(tbl) + message += "[%s]\ + " % unsafeSQLIdentificatorNaming(tbl) - message += "[s]kip\n" + # 19. 在消息中添加一个选项 [s]kip,允许用户跳过当前数据库的表 + message += "[s]kip\ + " + # 20. 在消息中添加一个选项 [q]uit,允许用户退出 message += "[q]uit" + # 21. 使用 readInput 函数获取用户输入,默认值为 'a',表示选择全部表 choice = readInput(message, default='a') + # 22. 如果用户没有输入或者输入为 'a' (或 'A'),则将所有表添加到 dumpFromTbls 列表中 if not choice or choice.lower() == 'a': dumpFromTbls = tablesList + # 23. 如果用户输入为 's' (或 'S'),则跳过当前数据库的表,继续处理下一个数据库 elif choice.lower() == 's': continue + # 24. 如果用户输入为 'q' (或 'Q'),则直接返回,不进行后续的转储操作 elif choice.lower() == 'q': return + # 25. 否则,将用户输入的表名按逗号分割,并添加到 dumpFromTbls 列表中 else: dumpFromTbls = choice.replace(" ", "").split(',') + # 26. 遍历 dumpFromTbls 列表,该列表存储当前数据库下要转储的表名 for table in dumpFromTbls: + # 27. 将当前表名赋值给 conf.tbl (全局配置参数) conf.tbl = table + # 28. 清空 kb.data.cachedColumns (全局配置参数) 缓存的列信息 kb.data.cachedColumns = {} + # 29. 清空 kb.data.dumpedTable (全局配置参数) 缓存的表数据 kb.data.dumpedTable = {} + # 30. 调用 self.dumpTable() 函数,转储当前表的数据 data = self.dumpTable() + # 31. 如果转储成功 (data 不为空),则将转储的数据传递给 conf.dumper.dbTableValues() 函数进行后续处理 if data: - conf.dumper.dbTableValues(data) + conf.dumper.dbTableValues(data) \ No newline at end of file