wireshark/epan/wslua/dtd_gen.lua

323 lines
7.5 KiB
Lua

-- dtd_gen.lua
--
-- a DTD generator for wireshark
--
-- (c) 2006 Luis E. Garcia Ontanon <luis@ontanon.org>
--
-- Wireshark - Network traffic analyzer
-- By Gerald Combs <gerald@wireshark.org>
-- Copyright 1998 Gerald Combs
--
-- SPDX-License-Identifier: GPL-2.0-or-later
if gui_enabled() then
local xml_fld = Field.new("xml")
local function dtd_generator()
local displayed = {} -- whether or not a dtd is already displayed
local dtds = {} -- the dtds
local changed = {} -- whether or not a dtd has been modified
local dtd -- the dtd being dealt with
local dtd_name -- its name
-- we'll tap onto every frame that has xml
local ws = {} -- the windows for each dtd
local w = TextWindow.new("DTD Generator")
local function help()
local wh = TextWindow.new("DTD Generator Help")
-- XXX write help
wh:set('DTD Generator Help\n')
end
local function get_dtd_from_xml(text,d,parent)
-- obtains dtd information from xml
-- text: xml to be parsed
-- d: the current dtd (if any)
-- parent: parent entity (if any)
-- cleanup the text from useless chars
text = string.gsub(text ,"%s*<%s*","<");
text = string.gsub(text ,"%s*>%s*",">");
text = string.gsub(text ,"<%-%-(.-)%-%->"," ");
text = string.gsub(text ,"%s+"," ");
while true do
-- find the first tag
local open_tag = string.match(text,"%b<>")
if open_tag == nil then
-- no more tags, we're done
return true
end
local name = string.match(open_tag,"[%w%d_-]+")
local this_ent = nil
if d == nil then
-- there's no current dtd, this is entity is it
d = dtds[name]
if d == nil then
d = {ents = {}, attrs = {}}
dtds[name] = d
end
dtd = d
dtd_name = name
end
this_ent = d[name]
if this_ent == nil then
-- no entity by this name in this dtd, create it
this_ent = {ents = {}, attrs = {}}
d.ents[name] = this_ent
changed[dtd_name] = true
end
if parent ~= nil then
-- add this entity to its parent
parent.ents[name] = 1
changed[dtd_name] = true
end
-- add the attrs to the entity
for att in string.gmatch(open_tag, "([%w%d_-]+)%s*=") do
if not this_ent.attrs[att] then
changed[dtd_name] = true
this_ent.attrs[att] = true
end
end
if string.match(open_tag,"/>") then
-- this tag is "self closed" just remove it and continue
text = string.gsub(text,"%b<>","",1)
else
local close_tag_pat = "</%s*" .. name .. "%s*>"
if not string.match(text,close_tag_pat) then return false end
local span,left = string.match(text,"%b<>(.-)" .. close_tag_pat .. "(.*)")
if span ~= nil then
-- recurse to find child entities
if not get_dtd_from_xml(span,d,this_ent) then
return false
end
end
-- continue with what's left
text = left
end
end
return true
end
local function entity_tostring(name,entity_data)
-- name: the name of the entity
-- entity_data: a table containg the entity data
-- returns the dtd text for that entity
local text = ''
text = text .. '\t<!ELEMENT ' .. name .. ' (' --)
for e,j in pairs(entity_data.ents) do
text = text .. " " .. e .. ' |'
end
text = text .. " #PCDATA ) >\n"
text = text .. "\t<!ATTLIST " .. name
for a,j in pairs(entity_data.attrs) do
text = text .. "\n\t\t" .. a .. ' CDTATA #IMPLIED'
end
text = text .. " >\n\n"
text = string.gsub(text,"<!ATTLIST " .. name .. " >\n","")
return text
end
local function dtd_tostring(name,doctype)
local text = '<? wireshark:protocol proto_name="' .. name ..'" hierarchy="yes" ?>\n\n'
local root = doctype.ents[name]
doctype.ents[name] = nil
text = text .. entity_tostring(name,root)
for n,es in pairs(doctype.ents) do
text = text .. entity_tostring(n,es)
end
doctype.ents[name] = root
return text
end
local function element_body(name,text)
-- get the entity's children from dtd text
-- name: the name of the element
-- text: the list of children
text = string.gsub(text,"[%s%?%*%#%+%(%)]","")
text = string.gsub(text,"$","|")
text = string.gsub(text,
"^(.-)|",
function(s)
if dtd.ents[name] == nil then
dtd.ents[name] = {ents={},attrs={}}
end
dtd.ents[name].ents[s] = true
return ""
end
)
return ''
end
local function attlist_body(name,text)
-- get the entity's attributes from dtd text
-- name: the name of the element
-- text: the list of attributes
text = string.gsub(text,"([%w%d_-]+) [A-Z]+ #[A-Z]+",
function(s)
dtd.atts[s] = true
return ""
end
)
return ''
end
local function dtd_body(buff)
-- get the dtd's entities from dtd text
-- buff: the dtd text
local old_buff = buff
buff = string.gsub(buff,"<!ELEMENT ([%w%d_-]+) (%b())>%s*",element_body)
buff = string.gsub(buff,"<!ATTLIST ([%w%d_-]+) (.-)>%s*",attlist_body)
end
local function load_dtd(filename)
local dtd_filename = USER_DIR .. "/dtds/" .. filename
local buff = ''
local wireshark_info
dtd_name = nil
dtd = nil
for line in io.lines(dtd_filename) do
buff = buff .. line
end
buff = string.gsub(buff ,"%s*<%!%s*","<!");
buff = string.gsub(buff ,"%s*>%s*",">");
buff = string.gsub(buff ,"<!%-%-(.-)%-%->"," ");
buff = string.gsub(buff ,"%s+"," ");
buff = string.gsub(buff ,"^%s+","");
buff = string.gsub(buff,'(<%?%s*wireshark:protocol%s+.-%s*%?>)',
function(s)
wireshark_info = s
end
)
buff = string.gsub(buff,"^<!DOCTYPE ([%w%d_-]+) (%b[])%s*>",
function(name,body)
dtd = { ents = {}, attrs = {}}
dtd_name = name
dtds[name] = dtd
dtd_body(body)
return ""
end
)
if not dtd then
dtd_body(buff)
end
if wireshark_info then
dtd.wstag = wireshark_info
end
end
local function load_dtds()
-- loads all existing dtds in the user directory
local dirname = persconffile_path("dtds")
local status, dir = pcall(Dir.open,dirname,".dtd")
w:set('Loading DTDs from ' .. dirname .. ' \n')
if not status then
w:append("Error: could not open the directory" .. dirname .. " , make sure it exists.\n")
return
end
for dtd_filename in dir do
w:append("File:" .. dtd_filename .. "\n")
load_dtd(dtd_filename)
end
end
local function dtd_window(name)
return function()
local wd = TextWindow.new(name .. '.dtd')
wd:set(dtd_tostring(name,dtds[name]))
wd:set_editable()
local function save()
local file = io.open(persconffile_path("dtds/") .. name .. ".dtd" ,"w")
file:write(wd:get_text())
file:close()
end
wd:add_button("Save",save)
end
end
local function close()
if li ~= nil then
li:remove()
li = nil
end
end
w:set_atclose(close)
-- w:add_button("Help",help)
load_dtds()
local li = Listener.new("frame","xml")
w:append('Running')
function li.packet()
w:append('.')
local txt = xml_fld().range:string();
get_dtd_from_xml(txt)
end
function li.draw()
for name,j in pairs(changed) do
w:append("\n" .. name .. " has changed\n")
if not displayed[name] then
w:add_button(name,dtd_window(name))
displayed[name] = true
end
end
end
retap_packets()
w:append(t2s(dtds))
w:append('\n')
end
register_menu("DTD Generator",dtd_generator,MENU_TOOLS_UNSORTED)
end