这篇文章与前一篇原理相同,都是对生成的可能链接进行试错验证,所以我们不再讨论原理部分,主要内容放在语言的继承和窗口的可视化上。
图形化界面我采用了tkinter标准库,没有什么特殊原因,只是不需要另外安装。
首先,先明确一件事情,tkinter不属于Python语言本身。tkinter是一个开放源码的图形接口开发工具,最初是用TCL编写的GUI函数库,它提供许多图形接口,Python将其移植到自己的内建模块里。所以说,tkinter就是tkinter它自己,Python只是借用,并不属于自己的一部分。
其中每个部件我们有个统一的名称叫“控件”,英文为widget。
你可以选择任意你习惯的第三方库,能达成效果就好。
因为核心部分在命令行版就已经做了分析与设计,所以我们从GUI开始。
简单将其分成五部分来进行说明。(在实际的设计中,应当是预先想好设计图纸,然后再根据特性去实现。我们现在反过来了,看着已经实现的东西,来编写代码。)
这里很好找到适合的控件,只是个简单的接收输入功能嘛。用Entry就好:
window = tk.Tk()
window.title('Web Scanner by 刑者')
window.geometry('600x400')
window.config(background='#CCE8CF')
entry = tk.Entry(window)
entry.place(x=100, y=10)
第二部分的两个使用了同一widget——Combobox,它属于tkinter里ttk的内容。
Combobox可以当成Entry和下拉菜单的组合,文章开头的截图里没有进行数值选择的演示,大家可以自行操作。
from tkinter import ttk
thread_num = tk.StringVar()
comboxlist = ttk.Combobox(window, textvariable=thread_num)
comboxlist["values"] = ('1', '3', '5')
comboxlist.current(0)
comboxlist.place(x=100, y=50)
timeout = tk.StringVar()
comboxlist1 = ttk.Combobox(window, textvariable=timeout)
comboxlist1["values"] = ('1', '0.5', '0.1')
comboxlist1.current(0)
comboxlist1.place(x=100, y=90)
二者分别规定了可选范围(values),其中的current方法是设置默认数值,比如两个combobox都是使用current(0)作为默认值,在运行是它就会分别取values中下标为0的值,作为显示值。
到这大家可能看到了,我所有的widget都是使用place方法进行的配置。我必须要承认,这不是一个好的配置方式。在大部分情况下,应当优先选择pack方法,pack使用相对位置的概念处理控件配置,是三个配置器中,灵活性最高,延展性最好的一个。(另一个是grid,一般在设计表格型布局的时候使用,比如我们这里的第三部分——复选框。)
上面所说的三个方法呢,用来包装和定位各组件在容器或者窗口内的位置,我们也叫做窗口控件配置管理器(Widget Layout Manager,有的书上翻译成了配置管理员,我觉得不好听)。
而我这里使用place绝对位置来处理,直接固定住控件的位置的原因,只是单纯的因为懒。(使用pack会多一些参数,这个人懒得调参~)
在图中的第三部分,我们给了一组复选框,让用户根据网站开发情况,有针对性的选择合适的字典进行扫描。
复选框,使用Checkbutton。
你会怎样写这6个Checkbutton呢?可以这样:
status_asp = tk.BooleanVar()
status_aspx = tk.BooleanVar()
status_dir = tk.BooleanVar()
status_jsp = tk.BooleanVar()
status_mdb = tk.BooleanVar()
status_php = tk.BooleanVar()
cb_asp = tk.Checkbutton(window, text='ASP', variable=status_asp).place(x=400, y=10)
cb_aspx = tk.Checkbutton(window, text='ASPX', variable=status_aspx).place(x=400, y=50)
cb_dir = tk.Checkbutton(window, text="DIR", variable=status_dir).place(x=400, y=90)
cb_jsp = tk.Checkbutton(window, text="JSP", variable=status_jsp).place(x=500, y=10)
cb_mdb = tk.Checkbutton(window, text="MDB", variable=status_mdb).place(x=500, y=50)
cb_php = tk.Checkbutton(window, text="PHP", variable=status_php).place(x=500, y=90)
读取字典的时候这样写:
def read_dict():
''' load the dict '''
combin = []
global status_asp, status_aspx, status_dir, status_jsp, status_mdb, status_php
if status_asp.get():
with open('ASP.txt') as f:
combin.extend(f.read().split())
if status_aspx.get():
with open('ASPX.txt') as f:
combin.extend(f.read().split())
if status_dir.get():
with open('DIR.txt') as f:
combin.extend(f.read().split())
if status_jsp.get():
with open('JSP.txt') as f:
combin.extend(f.read().split())
if status_mdb.get():
with open('MDB.txt') as f:
combin.extend(f.read().split())
if status_php.get():
with open('PHP.txt') as f:
combin.extend(f.read().split())
return combin
当然了,这并不优雅。如果你是用pack来进行配置的话,修改起来可能会更加的简单,place的灵活性太差了。不过我们还是可以尝试把它写的优雅一些,如:
dicts = ["ASP", "ASPX", "DIR", "JSP", "MDB", "PHP"]
checkboxes = []
for i in range(3):
checkboxes.append(tk.BooleanVar())
tk.Checkbutton(window, text=dicts[i], variable=checkboxes[i]).place(x=400, y=10+40*i)
for i in range(3,6):
checkboxes.append(tk.BooleanVar())
tk.Checkbutton(window, text=dicts[i], variable=checkboxes[i]).place(x=500, y=10+40*(i-3))
是不是少了很多代码?看着就舒服。还是那个问题,因为place太死板了,我们这里需要两个循环,你可以尝试使用pack来配置,一个循环解决问题哈。
那读取字典的操作,也就可以简单化了:
def read_dict():
''' load the dict '''
global dicts, checkboxes
combin = []
for i in range(len(checkboxes)):
if checkboxes[i].get():
with open(dicts[i]+".txt", "r") as f:
combin.extend(f.read().split())
return combin
不多说了,优不优雅自己体会。(在命令行版中的读取字典的操作,也向这里一样相当的冗余,你尝试着将它修改的优雅简洁些吧~)
第四部分,就一个按钮,不用想,用Button。
当点击按钮时,启动扫描器。
def run():
WST = WebScannerTkl()
WST.run()
def scan():
threading.Thread(target=run).start()
button = tk.Button(window, text="Let's go", command=scan)
button.place(x=280, y=150)
这里没有过多要说的,在run函数里创建扫描器对象,并调用对象的run方法进行扫描(两个run不要搞混啊)。这个类的设计大部分功能与上篇文章里的命令行版相同,我们只需要继承过来,重写一些方法即可。(放在后面说)
那,点击按钮的时候,为什么不让它直接执行run,而是通过scan创建个线程,让线程去执行呢?
还记得第一篇文章(端口扫描器),我们最后讨论的GIL吗?窗口的显示是个不断的循环输出,如果让进程去执行函数功能,在这段执行时间段里,没人来循环界面,界面就会出现“卡死”的情况。忘记的小伙伴去翻下吧。传送门
第五部分,实时输出扫描结果。它不需要什么其他的功能,用Text就好。
t = tk.Text(window, height=10)
t.place(x=10, y=220)
(再次认错,代码是很久之前写的,里面变量起的名字让人抓狂,你在编写的时候记得改正哈。)
五大部分完成了,其实还有一部分,就是在窗口的右下角有个进度标签,可以回到开头动态图片里查看。
它的设计呢,我是使用的Label标签,然后实时的修改变量的值。除了此处的标签,还有输入框前面的"HTTP URL",“线程数”,“超时”都是使用的标签。
var_progress = tk.StringVar()
tk.Label(window, text='HTTP URL:').place(x=20, y=10)
tk.Label(window, text='进程数:').place(x=20, y=50)
tk.Label(window, text='请求超时:').place(x=20, y=90)
progress = tk.Label(window, textvariable=var_progress).place(x=500, y=370)
至此,可视化完成。
我们前面说了,进行扫描的核心代码与命令行版本近乎相同,我们先把命令行版的贴出来:
class WebScanner(object):
_running_time = 0
_number_of_threads_completed = 0
def __init__(self, host, thread_num, paths):
"""initialization object"""
self.host = host if host[-1] != '/' else host[:-1]
self.thread_num = thread_num
self.paths = paths
def animation(self):
"""opening animation"""
return r'''
__ __ ___. _________
/ \ / \ ____\_ |__ / _____/ ____ _____ ____ ____ ___________
\ \/\/ // __ \| __ \ \_____ \_/ ___\\__ \ / \ / \_/ __ \_ __ \
\ /\ ___/| \_\ \/ \ \___ / __ \| | \ | \ ___/| | \/
\__/\ / \___ >___ /_______ /\___ >____ /___| /___| /\___ >__|
\/ \/ \/ \/ \/ \/ \/ \/ \/
'''
def run(self):
"""start scanner"""
WebScanner._running_time = time.time()
for i in range(self.thread_num):
threading.Thread(target=self._subthread).start()
def _subthread(self):
"get url from dictionary and try to connect"
while self.paths:
sub_url = self.paths.pop()
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/51.0.2704.63 Safari/537.36'}
url = self.host + sub_url
req = urllib.request.Request(url=url, headers=headers)
try:
##Discard requests longer than two seconds
request = urllib.request.urlopen(req, timeout=1)
result = request.geturl(), request.getcode(), len(request.read())
print(result)
except:
pass
WebScanner._number_of_threads_completed += 1
if WebScanner._number_of_threads_completed == self.thread_num:
print("Cost time {} seconds.".format(time.time() - WebScanner._running_time))
试图分析,我们需要修改哪些部分。
animation我们不需要,不管他。run函数不需要动,没有问题。进程函数呢~,它就需要改变了。
在命令行版本中,对扫描结果我们直接进行print输出,而在GUI版中,我们需要输出到Text里,这是需要改变的地方。还需要做别的事吗?需要,还有个进度条呢?每次执行扫描,都要把右下角的Label内容加一,这样才是实时的情况。这里的Text和Label绑定的变量,都将其作为对象初始化的一个属性。那加上超时属性,总共三个新属性需要添加。分析完毕。
综上,只需修改_subthread和__init__函数即可:
class WebScannerTkl(WebScanner):
def __init__(self, host, thread_num, paths, timeout, print_, progress):
super().__init__(host, thread_num, paths)
self.print = print_
self.progress = progress
self.timeout = timeout
def _subthread(self):
"get url from dictionary and try to connect"
length = len(self.paths)
progress = 0
while self.paths:
sub_url = self.paths.pop()
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/51.0.2704.63 Safari/537.36'}
url = self.host + sub_url
req = urllib.request.Request(url=url, headers=headers)
try:
##Discard requests longer than two seconds
request = urllib.request.urlopen(req, timeout=self.timeout)
result = request.geturl(), request.getcode(), len(request.read())
self.print.insert("insert", str(result)+"\n")
except:
pass
finally:
progress += 1
self.progress.set("进度:" + str(progress) + r'/' + str(length))
WebScanner._number_of_threads_completed += 1
if WebScanner._number_of_threads_completed == self.thread_num:
self.print.insert("insert","Cost time {} seconds.".format(time.time() - WebScanner._running_time))
现在是不是体会到继承的强大了,少写了多少代码呀~。
现在,按钮的执行函数也就可以确认了:
def run():
WST = WebScannerTkl(entry.get(), int(thread_num.get()), \
read_dict(), float(timeout.get()), \
t, var_progress)
WST.run()
分别传入,根目录,线程数,字典列表,超时时间,输出框(Text),进度标签变量进行初始化。(还是那个问题,变量名没有起好,理解了就行哈。)
完整代码:
import tkinter as tk
from tkinter import ttk
from web import WebScanner
import threading
import urllib.request
import time
class WebScannerTkl(WebScanner):
def __init__(self, host, thread_num, paths, timeout, print_, progress):
super().__init__(host, thread_num, paths)
self.print = print_
self.progress = progress
self.timeout = timeout
def _subthread(self):
"get url from dictionary and try to connect"
length = len(self.paths)
progress = 0
while self.paths:
sub_url = self.paths.pop()
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/51.0.2704.63 Safari/537.36'}
url = self.host + sub_url
req = urllib.request.Request(url=url, headers=headers)
try:
##Discard requests longer than two seconds
request = urllib.request.urlopen(req, timeout=self.timeout)
result = request.geturl(), request.getcode(), len(request.read())
self.print.insert("insert", str(result)+"\n")
except:
pass
finally:
progress += 1
self.progress.set("进度:" + str(progress) + r'/' + str(length))
WebScanner._number_of_threads_completed += 1
if WebScanner._number_of_threads_completed == self.thread_num:
self.print.insert("insert","Cost time {} seconds.".format(time.time() - WebScanner._running_time))
def read_dict():
''' load the dict '''
global dicts, checkboxes
combin = []
for i in range(len(checkboxes)):
if checkboxes[i].get():
with open(dicts[i]+".txt", "r") as f:
combin.extend(f.read().split())
return combin
def run():
WST = WebScannerTkl(entry.get(), int(thread_num.get()), \
read_dict(), float(timeout.get()), \
t, var_progress)
WST.run()
window = tk.Tk()
window.title('Web Scanner by 刑者')
window.geometry('600x400')
window.config(background='#CCE8CF')
entry = tk.Entry(window)
entry.place(x=100, y=10)
thread_num = tk.StringVar()
comboxlist = ttk.Combobox(window, textvariable=thread_num)
comboxlist["values"] = ('1', '3', '5')
comboxlist.current(0)
comboxlist.place(x=100, y=50)
timeout = tk.StringVar()
comboxlist1 = ttk.Combobox(window, textvariable=timeout)
comboxlist1["values"] = ('1', '0.5', '0.1')
comboxlist1.current(0)
comboxlist1.place(x=100, y=90)
dicts = ["ASP", "ASPX", "DIR", "JSP", "MDB", "PHP"]
checkboxes = []
for i in range(3):
checkboxes.append(tk.BooleanVar())
tk.Checkbutton(window, text=dicts[i], variable=checkboxes[i]).place(x=400, y=10+40*i)
for i in range(3,6):
checkboxes.append(tk.BooleanVar())
tk.Checkbutton(window, text=dicts[i], variable=checkboxes[i]).place(x=500, y=10+40*(i-3))
t = tk.Text(window, height=10)
t.place(x=10, y=220)
def scan():
threading.Thread(target=run).start()
button = tk.Button(window, text="Let's go", command=scan)
button.place(x=280, y=150)
var_progress = tk.StringVar()
tk.Label(window, text='HTTP URL:').place(x=20, y=10)
tk.Label(window, text='进程数:').place(x=20, y=50)
tk.Label(window, text='请求超时:').place(x=20, y=90)
progress = tk.Label(window, textvariable=var_progress).place(x=500, y=370)
window.mainloop()
有些地方没有写注释,不过我们都一步步的分析过,应该在理解上没有什么问题。
发现我结束的有点突兀,但确实没有什么点值得写了,很多问题我们都在前两篇文章里讨论过,无需多谈。如仍旧有疑问,我们评论里见吧。
(下一篇是,PHP一句话连接工具(GUI版),下个月见吧。)
完。