Implementing OWASP ZAP Full Layout

Introduction to Full Layout

In this article I'll present how I implemented the Full Layout into ZAP OWASP. Since I'm always using ZAP on small screens, it just isn't enough space to actually make use of the two layouts that are available in ZAP: the “Maximize left Sites tab” and the “Maximize bottom History tabs”. The first layout can be seen on the picture below, where it's evident that the left side of the Sites tab is maximized. This will become clear in a moment, when we present the second layout.

zap1

The second supported layout can be seen on the picture below, where the history tabs are maximized.

zap2

But no matter which of the above layouts you use, you simply can't effectively use ZAP on smaller screens; if you do use it, you will constantly have to scroll left and right or you simply won't have enough space to actually see anything useful. This bothered me for quite some time and then I realized: hey, this is open source, I can change that and commit the changes upstream. I actually did that in a few days and named it Full Layout: I was surprised about how little time it was actually required to implement the functionality. The result can be seen below.

zap3

We can immediately see a clean look without multiple splitted panels where you have maximum space available. You can see that all tabs from all previous panels are now accessible in a single panel above. This isn't perfect yet, because if you select a request in Sites tab, it will be updated in the Request/Response tab and you would have to switch tabs manually to see the change. I guess the only solution to see whether this will be annoying is to actually use ZAP for some time and see whether this will cause an annoyance. If that proves to be right, I will probably implement a functionality where all the tabs from the previous upper right panel, the Quick Start, Break, Script Console, Request, Response and Output, will be implemented as a sub-tabs under the Sites tab when in Full Layout.

I went even further and also implemented the optional names in tabs; the option is accessible in settings under Tools – Options – Display - “Show tab names”. The option is selected by default and displays icons as well as text in each tab. But when we disable the option only icons will be shown in every tab, so text will be hidden.

zap4

I also added a shortcut icon to the main toolbar panel as can be seen below (highlighted in red). That icon can be used to toggle the “Show tab names” option from the settings in order to show/hide the names in tabs.

zap_toolbar

By disabling the names in tabs, we're given even more space to work with on our little screen. The changes can be seen on the picture below, where only icons are shown in each tab and the text has been hidden. If you hover over the tab a corresponding tooltip with the shown, so you can immediately know what tab you're looking at: in case you're not a long-time user of ZAP in which case you probably know the icons by heart. This will probably be useful for beginners, which are not used to the icons of the ZAP yet, but they still want to use only icons without the text being printed in each tab.

zap6

So far I've presented exactly what I did and how the results look, but till the end of the article I'll present how I did what I did. I'll present some source code, but will try to keep it brief so you can follow through without a problem.

Downloading the Source Code

I want to start at how I downloaded the source code and started changing it for every beginner trying to get into ZAP development. The first thing you should do is checkout the source with the the svn checkout command:

svn checkout http://zaproxy.googlecode.com/svn/trunk/ zaproxy

After that the source code will be downloaded into the zaproxy/src/ directory, where we can browse and change it. When we would like to recompile the code we have changed, we can enter the zaproxy/build/ directory and issue the ant command as follows.

# ant

After the source code is re-compiler, we can enter the newly created zaproxy/build/zap/ directory and execute the zap.sh script, which will start the new version of ZAP. I tend to compile and run the ZAP with the same command, but I want to start ZAP only if the compilation was successful. I can do that by entering the build/ directory and executing the command below.

# ant && ( cd zap; ./zap.sh; cd .. )

Implementing the Full Layout Icons

At first I opened the src/lang/Messages.properties file, which defines all the English strings used in the ZAP. Then I grepped for the text I needed. When you hover the first layout, you can see the text “Expand Sites Tab”, which is what I searched for in the Messages.properties. I immediately found the following entry.

view.toolbar.expandSites = Expand Sites Tab

It's only one line, which means that we need to further search for the string “view.toolbar.expandSites” in the source code of ZAP to see where the string is being referenced. We can find out that it's being used in the src/org/zaproxy/zap/view/MainToolbarPanel.java in the getBtnExpandSites function presented below.

zap7

The function checks whether the btnExpandSites variable is null and if it is, it creates a new JToggleButton object, which presents the layout icon in ZAP. This is the first of the layout icons presented on the picture below. The function also adds a tooltip, which is displayed when hovering over the icon. Notice the new “Full Layout” icon being selected on the picture below?

zap8

The most important thing is the action ChangeDisplayOptionAction, which is called when we click on the layout icon in the toolbar. Therefore we must further search the ChangeDisplayOptionAction text in the source code of ZAP. We can immediately discover that the ChangeDisplayOptionAction class is defined in the same MainToolbarPanel.java source code file and can be seen below.

zap9

The class extends from the AbstractAction, which requires that the class extending it implements the actionPerformed function, which detects when certain action has occurred on the object. Our object in this case is just an icon representing a layout in the toolbar, so the action is triggered when we select that layout and the actionPerformed() function is called. The function first checks whether we're selected a different layout that we currently had in which case it saves the options to config. Also, the changeDisplayOption function from the MainFrame is called passing it the current displayOption parameter, which is just a number specifying the currently chosen layout. This is the function that actually changes the view. That code of that function is presented below.

zap10

Notice that we're settings the displayOption of multiple objects, so they all know the currently selected layout. The line 4 “this.getWorkbench().changeDisplayOption(displayOption);” gets the current WorkbenchPanel and calls it's changeDisplayOption function shown below.

zap11

The function first changes the current displayOption variable, which presents the currently chosen layout and removes all the elements from the current layout, so we're starting fresh with a clean layout. After that we're also setting some variables to null and calling the initialize function, where I've changed the code that was needed to initialize the layout. The function contains some other functionality, but basically the code before I changed anything looked like the following. We can see that it's deciding about the currently chosen layout upon which it calls appropriate function to add some elements to it.

zap12

I've changed the code into the following: essentially I only added another case statement used for the Full Layout.

zap13

Before proceeding any further, we must present how the layout is constructed in ZAP. Let's take a look at the picture below, where I've highlighted the three tabbed panels: green, red and blue. The green panel is constructed by calling the getTabbedSelect() function, the red is constructed by calling the getTabbedWork() function and the blue is constructed by calling the getTabbedStatus() function. All of those functions essentially return an object of type TabbedPanel2, which is used to represent all of the panels.

zap14

An example of getTabbedStatus() can be seen below, where it's evident that tabbedStatus is created if it doesn't already exist and then returned to the caller.

zap15

The getTabbed* functions use the following objects, which are all of type TabbedPanel2:

  • getTabbedSelect : tabbedSelect
  • getTabbedWork : tabbedWork
  • getTabbedStatus : tabbedStatus

When constructing the layout of ZAP, we must add the objects to those three panels to make it shown. Therefore if we would like to add the 'Request' tab to the tabbedStatus panel, we have to call the addTab() function defined in TabbedPanel2. An example of calling addTab() to add Request/Response tabs to the tabbedWork panel is presented below (taken from the View.java class). Note that I actually deleted those two lines from View.java, because they were always added to the tabbedWork panel, but when using Full Layout, we have to add it all to the tabbedStatus panel. We could choose any panel from the above three and add all the tabs to that and then represent just that panel in a full view, but I've chosen the tabbedStatus panel.

zap16

One very nice feature of getTab function is that it automatically deletes the tab from any other panel where it was presented before. So if a Request tab is shown in tabbedWork panel (as is the default) and I call addTab to add it to tabbedStatus panel, it will automatically be deleted from the tabbedWork panel. The function basically doesn't create a new tab, but just moves the old tab to the new panel. I must say that this feature solved a lot of the work I would have done if this wasn't already implemented.

In the previous layouts, the tabs in tabbedSelect and tabbedWork where 'hardwired' into the code, which is why I had to delete some code from the other files like View.java (as I have already mentioned) and rewrite it in the initialize function of the WorkbenchPanel.java. More specifically, the following tabs are hardwired into the code:

  • Sites
  • Scripts
  • Quick Start
  • Break
  • Script Console
  • Request
  • Response

The Sites/Output/Request/Response tabs were easily configured properly by deleting the appropriate addTab function calls from View.java and adding them to WorkbenchPanel.java. Basically I needed to add the following code to the WorkbenchPanel.java in order for the tabs to be added to the right panel based on which layout was selected. This code is a little bit more complicated, but essentially does what it has to do: there are quite some comments on the code, so I won't comment it any further.

switch (displayOption) {
case View.DISPLAY_OPTION_TOP_FULL:
// save the arrangements of tabs when going into 'Full Layout'
if(previousDisplayOption != View.DISPLAY_OPTION_TOP_FULL) {
tabbedOldSelect = tabbedSelect;
tabbedOldStatus = tabbedStatus;
tabbedOldWork = tabbedWork;
}
// Tabs in sequence: request, response, output, sites.

getTabbedStatus().addTab(View.getSingleton().getRequestPanel().getName(),
View.getSingleton().getRequestPanel().getIcon(),
View.getSingleton().getRequestPanel(), false);

getTabbedStatus().addTab(View.getSingleton().getResponsePanel().getName(),
View.getSingleton().getResponsePanel().getIcon(),
View.getSingleton().getResponsePanel(), false);

getTabbedStatus().addTab(View.getSingleton().getOutputPanel().getName(),
View.getSingleton().getOutputPanel().getIcon(),
View.getSingleton().getOutputPanel(), false);

getTabbedStatus().addTab(View.getSingleton().getSiteTreePanel().getName(),
View.getSingleton().getSiteTreePanel().getIcon(),
View.getSingleton().getSiteTreePanel(), false);

// go over all tabs that extensions added and move them to
tabbedStatus
for(Component c: getTabbedWork().getTabList()) {
if(c instanceof AbstractPanel) {
getTabbedStatus().addTab(c.getName(), ((AbstractPanel)c).getIcon(),
c,((AbstractPanel)c).isHideable(), ((AbstractPanel)c).getTabIndex());
}
}
for(Component c: getTabbedSelect().getTabList()) {
if(c instanceof AbstractPanel) {
getTabbedStatus().addTab(c.getName(), ((AbstractPanel)c).getIcon(),
c, ((AbstractPanel)c).isHideable(), ((AbstractPanel)c).getTabIndex());
}
}
break;
case View.DISPLAY_OPTION_BOTTOM_FULL:
case View.DISPLAY_OPTION_LEFT_FULL:
default:
// we shouldn't check against 'previousDisplayOption ==
View.DISPLAY_OPTION_TOP_FULL',
// because the previousDisplayOption can be null when starting ZAP.
if((previousDisplayOption != View.DISPLAY_OPTION_BOTTOM_FULL) ||
(previousDisplayOption != View.DISPLAY_OPTION_LEFT_FULL)) {
// Tabs in sequence: request, response, output, sites.

getTabbedWork().addTab(View.getSingleton().getRequestPanel().getName(),
View.getSingleton().getRequestPanel().getIcon(),
View.getSingleton().getRequestPanel(), false);

getTabbedWork().addTab(View.getSingleton().getResponsePanel().getName(),
View.getSingleton().getResponsePanel().getIcon(),
View.getSingleton().getResponsePanel(), false);

getTabbedStatus().addTab(View.getSingleton().getOutputPanel().getName(),
View.getSingleton().getOutputPanel().getIcon(),
View.getSingleton().getOutputPanel(), false);

getTabbedSelect().addTab(View.getSingleton().getSiteTreePanel().getName(),
View.getSingleton().getSiteTreePanel().getIcon(),
View.getSingleton().getSiteTreePanel(), false);
}

// parse the tabs correctly when previous display option was 'Full
Layout'
if(previousDisplayOption == View.DISPLAY_OPTION_TOP_FULL) {
for(Component c: getTabbedOldWork().getTabList()) {
if(c instanceof AbstractPanel) {
getTabbedWork().addTab(c.getName(), ((AbstractPanel)c).getIcon(), c,
((AbstractPanel)c).isHideable(), ((AbstractPanel)c).getTabIndex());
}
}
for(Component c: getTabbedOldSelect().getTabList()) {
if(c instanceof AbstractPanel) {
getTabbedSelect().addTab(c.getName(), ((AbstractPanel)c).getIcon(),
c, ((AbstractPanel)c).isHideable(), ((AbstractPanel)c).getTabIndex());
}
}
}
}

So far, we've added some tabs to the right panel in order to be correctly shown in the “Full Layout” layout, but the problem remained for the “Scripts/Quick Start/Break/Script Console” tabs. I realized that those tabs are part of extensions, which are being loaded in the ExtensionLoader.java file and not in the WorkbenchPanel.java - more specifically in the hookView function. Therefore, I also needed to add a switch statement into the ExtensionLoaded.java to add the extension panel to the currently selected layout. The changed code for the function is presented below, where in case we're using Full Layout, I'm adding all tabs to the tabbedStatus panel.

private void hookView(View view, ExtensionHook hook) {
if (view == null) {
return;
}

ExtensionHookView pv = hook.getHookView();
if (pv == null) {
return;
}

// Add the three panels to the current window/workbench: add extension
tabs to the Full layout
// when chosen, otherwise they are as before.
int displayOption =
Model.getSingleton().getOptionsParam().getViewParam().getDisplayOption();
addTabPanel(pv.getSelectPanel(),
view.getWorkbench().getTabbedSelect());
addTabPanel(pv.getWorkPanel(), view.getWorkbench().getTabbedWork());
addTabPanel(pv.getStatusPanel(),
view.getWorkbench().getTabbedStatus());

// remember the position of tabs in status position
if(displayOption == View.DISPLAY_OPTION_TOP_FULL) {
// save the current normal instances to old instance variables, so
they are both
// referencing the same TabbedPanel2 instances. Used when going into
'Full Layout'
// so the state is preserved.

view.getWorkbench().setTabbedOldWork(view.getWorkbench().getTabbedWork());

view.getWorkbench().setTabbedOldSelect(view.getWorkbench().getTabbedSelect());

view.getWorkbench().setTabbedOldStatus(view.getWorkbench().getTabbedStatus());

// switch the layout to Full Layout
addTabPanel(pv.getSelectPanel(),
view.getWorkbench().getTabbedStatus());
addTabPanel(pv.getWorkPanel(),
view.getWorkbench().getTabbedStatus());
addTabPanel(pv.getStatusPanel(),
view.getWorkbench().getTabbedStatus());
}

// ZAP: removed session dialog parameter
addParamPanel(pv.getSessionPanel(), view.getSessionDialog());
addParamPanel(pv.getOptionsPanel(), view.getOptionsDialog(""));
}

This works beautifully and now every tab is loaded to the tabbedStatus panel, but there's a catch. This only works when we start ZAP in Full Layout, but doesn't change when switching between different layouts dynamically; this happens because the hookView function is not invoked every time we change a layout in ZAP. I must confess, I had quite some problem with keeping the extension tabs in the right place, but eventually I figured it out. I defined a second group of tabbed panels in WorkbenchPanel.java called tabbedOldStatus, tabbedOldWork and tabbedOldSelect as presented on the picture below.

zap17

I also updated the initialize function in WorkbenchPanel.java, where I had to differentiate between DISPLAY_OPTION_TOP_FULL (Full Layout) and the DISPLAY_OPTION_BOTTOM_FULL, DISPLAY_OPTION_LEFT_FULL layouts. The code below is executed when we change the layout to Full Layout. If the previous layout was not full layout (to which we're changing now), we have to save the old state of the tabs. We can do that with the code below; at this point we haven't yet moved all the tabs to the status panel, so a correct copy of tab positions will be made.

zap18

After that code we must move all the tabs to the status panel, which we can do by using the code below, where we're iterating over the work/select tabbed panels and moving them to status panel (the one that is maximized in Full Layout view).

zap19

Thus when switching to “Full Layout” mode, all the tabs will be correctly added to the tabbedStatus panel, but the old ones will be preserved. The problem is that when switching back from the “Full Layout”, those tabs will stay in the status panel and won't be moved back to their appropriate layouts, which is why we had to save the old tab's positions. To switch them back we need to call the code below when changing to “maximize bottom” or “maximize left” layout; this must be done only when the previous layout was full layout.

zap20

Notice that we're iterating over the old work/select panels and re-adding the tabs to the currently active work/select tabs (which are empty, because the previous layout was full layout).

The above code is good and works the way it should, but there's still a little bug when starting ZAP in full layout mode. When we start ZAP in full layout mode, the old work/select panels will be null by default, because they haven't been populated with the addTab function calls. This is why we must also change the ExtensionLoader.java's hookView function into the following.

zap21

Notice that we're not immediately adding all the tabs to the status panel when starting ZAP in full layout mode (DISPLAY_OPTION_TOP_FULL). Rather than that we're first adding all the tabs to their normal positions without talking into account that we're in fact starting ZAP in full layout mode: notice the beginning addTabPanel function calls. But this is done, so we can later remember the positions and save them into the old panels by calling the setTabbedOld* functions. Immediately after that we're repositioning the tabs to the status panel, so we have the correct previous tab positions (although there was actually no previous tab positions) as well as the correct current positions.

At this point we've loaded all the tabs – the normal ones as well as ones from various extensions into the status panel, which need to be done when using “Full Layout” layout. By doing that, all the tabs are shown correctly when switching between different layouts supported by ZAP. There were some other stuff I needed to work out in order for everything to be working flawlessly, but in order to keep the article brief I didn't include it here. The most important stuff you have to remember are that there are three panels in ZAP and to change the layout we have to reposition the tabs from one panel to the other.

Implementing the Optional Tab Names

Here we're going to talk about how adding a feature into ZAP, which allows us to have optional tab names, which means that we can decide if we only want icons or icons+text shown in tabs. The option can be seen on the picture below as “Show tab names”, which when checked uses icons+text in tabs (this is the default). If we disable the option, only the icons will be shown.

zap22

I won't go into the long process of describing every little code change I made, but essentially I added a new entry into the Message.properties, which holds the string “Show tab names”. Then I edited some files, which allowed me to actually present the option in ZAP preferences. At last I added this little piece of code that sets the title variable to “” when the above option is used: this is in TabbedPanel2.java. The title variable represents the name of the tab, which is passed into the addTab function. This was needed in order to show empty tab names when “Show tab names” is disabled.

zap23

Below in the same file, there is also an option, which adds tooltips for every tab name, so we know which tab we're looking at when tab text is not shown. This was done very quickly, but you had to restart ZAP in order for the change to take effect. I didn't want that, but wanted the change to be taken in to effect immediately, which is why I had to dig deeper into the code.

We can clearly see that I'm changing the text of the tab in addTab and previously we've seen that I'm calling addTab practically the whole time. So when toggling the “Show icons and text in tabs” option and saving the preferences, the current layout is not redrawn, but stays the same. But if we change a layout to some other layout, the following happens.

zap24

Notice that some tab names are empty and others are not. This is because we're re-adding just the select/work panel tabs into their correct positions, but the status tabs are not re-added, which is why they keep their name. We shouldn't really poke around re-adding those tabs too, because we still need to change the layout for changes to take effect, which is unacceptable.

Rather than that we'll add a simple function to the TabbedPanel2.java, which is seen below. In the function, we're essentially moving over all defined tabs in every panel and calling the setTitle function to change the names names of tabs accordingly to the settings.

zap25

I also added the toggleTabNames function into the WorkbenchPanel.java, which calls the setShowTabNames for every panel as can be seen below.

zap26

At the end, we still must call this function when saving the preferences dialog upon changing the “Show tab names” option. First let's take a look at where the toggleTabNames is called, which can be seen in the code below taken from OptionsParamView.java.

zap27

Notice that the setShowTabNames function calls to toggleTabNames? Therefore, we need to call setShowTabNames upon closing the preferences dialog by clicking on the button OK. That happens in the saveParam function in OptionsViewPanel.java, where a new line of code needs to added, which saves the chosen preference.

zap28

Note that the saveParam function is calling setShowTabNames to get the value of the setting “Show tab names” in Preferences dialog. Additionally, when the setShowTabNames is called, it also calls the toggleTabNames, which take care of showing/hiding tab names in real time without needing to restart ZAP.

Additonally, I also added a shortcut icon to the main toolbar panel as can be seen on the picture below (highlighted in red). That icon can be used to toggle the “Show tab names” option from the settings in order to show/hide the names in tabs.

zap_toolbar

This was implemented by adding a function getBtnShowTabIconNames to the MainToolbarPanel.java file. Basically we're creating the btnShowTabIconNames button, which is of type ZapToggleButton, which already provides everything needed in order to create a toggle button. We're adding two toggle button icons with their corresponding tooltips as well as implementing the action listener, which toggles the settings "Set tab names" in preferences. The change is also taken into account in real time.

zap_toggle

After saving all the changes and recompiling ZAP, we're finally there. We started our journey with the following ZAP interface.

zap31

And we're ending our journey with the changes outlined below.

zap_final

Conclusion

In this article we've presented the implementation of a new “Full Layout” in ZAP, which is useful for smaller screens and also for the people used to Burp. I implemented that layout because I'm mostly used to Burp and wasn't satisfied with the layout ZAP uses; the new layout feels more at home and I'll be more than happy to use it.

We also implemented a new feature, which enables us to disable the text in all tabs, so just icons are shown. This gives us more space to work with and is a nice feature to have when you get used to the what the icons in tabs mean, so you can switch to tabs more quickly.

This article was written, because I wanted to show other developers how easy it is to contribute to the ZAP project and to encourage contributions from other people as well. I also have to mention the excellent community behind ZAP project: whenever I had a problem with this or that, they were always happy to help out. The code I committed to the repository was also reviewed by other members of the community, which showed great care about the project. I fixed all the received comments and also fixed quite some bugs and now the feature is finally ready for being included into upstream.

Comments