Previous Table of Contents Index Next
Perforce 2013.1: Javascript API for Visual Tools



Chapter 1
Overview
The Perforce JavaScript API for Visual Tools enables you to extend P4V, Perforce's Visual Client, and P4Admin, Perforce's Administration Tool, using applets written in JavaScript and HTML. Your applets can take full advantage of the capabilities offered by JavaScript and the World Wide Web. For example, you can incorporate Google charting widgets into a tab to graph data. Specifically, you can:
Add alerts: By default, the Administration Tool displays three standard alerts, which are displayed on the P4Admin Home Page. You can define your own alerts (for example, to notify the Perforce administrator that a new superuser has been created).
Override P4V performance settings: to reduce the server load imposed by a large number of P4V users connected to the same Perforce server, you can use a centrally-administered settings file to override performance-related defaults.
Create a custom Submit dialog: you can replace the P4V Submit dialog with one of your own design.
If you have multiple Perforce servers, you can use one of them to serve applets and configure the other servers to refer to the central server. You can tailor applets for specific Perforce users and groups. The following sections describe the Perforce JavaScript API for Visual Tools in detail.
Architecture
P4V and P4Admin use the Qt toolkit as the basis of their cross-platform user interface. Qt-based applications incorporate the open source WebKit HTML rendering engine. The Perforce JavaScript API for Visual Tools uses WebKit to enable you to create applets that extend P4V and P4Admin. Applets are implemented using the following components:
Application files: HTML and JavaScript files that contain the logic for your applets. These files can reside in a Perforce server, on local machines, or in any location that is Web-accessible.
P4V and P4Admin call the central settings file according to internal application logic, providing a key to be processed. In the central settings file, you configure extensions by detecting the keys of interest and specifying the JavaScript or HTML files to be executed or rendered.
Here is how P4Admin and P4V execute applets:
1.
When P4V or P4Admin first connects to a Perforce server, it scans the permissions table for a centralsettings entry that is configured for the current user, according to the standard logic Perforce uses to parse the protections table. The user must have read access to this file.
2.
At startup, P4V and P4Admin execute the JavaScript in the central settings file with various keys, according to proprietary application logic.
3.
If the central settings file contains logic for a key, that logic is executed. Typically, for alerts, JavaScript files are executed; for tabs, HTML files are rendered.
Enabling Applets
By default, applet support is disabled in P4Admin and P4V, and unconfigured in the Perforce Server. To implement applets, you perform the following steps:
1.
Perforce administrators: Create a central settings file, which dispatches alerts and tabs. For details, see The Central Settings File.
2.
Perforce administrators: In your Perforce server, create an entry for the central settings file in the permissions table (using the p4 protect command or the Administration Tool).
3.
Perforce administrators: Create the HTML and JavaScript files containing the logic for your applets.
4.
All users: In P4V or P4Admin Preferences, enable the Accept applets... preference on the Applets tab, add the Perforce server to the list of allowed servers, then exit and relaunch the application.
The following sections describe applet development and administration in detail.
The Permissions Table Entry
To add the required centralsettings entry to the permissions table:
1.
1.
Click the Permissions tab. The permissions table is displayed in a grid at the bottom of the pane.
2.
3.
In the resulting empty lines, specify the location of the central settings file. In the host column, use the keyword centralsettings. Then add a second line to the protections table to ensure that the file is readable by all users.
The following entries specify a single central settings file to be used for all users:
list user * centralsettings //depot/jsapi/centralsettings.js
read user * *               //depot/jsapi/centralsettings.js
4.
Click Save Edits to save your entry.
You can configure different central settings files for specific users or groups. For example, the following entries configure a central settings file solely for the user named jon.
list user jon centralsettings //depot/dev/jon/jonsettings.js
read user jon *               //depot/dev/jon/jonsettings.js
The Central Settings File
The central settings file is a JavaScript file that processes keys and returns the location of the HTML or JavaScript files to be executed for tabs and alerts. It can contain per-group and per-user logic and can point to files in other Perforce servers. To configure the central settings files for a Perforce server, you make one or more entries in the Perforce permissions table. To receive applets, users must have read access to the central settings file.
The following sections provide details about configuring, coding, and administering the central settings file. For details about administering permissions, refer to the Perforce System Administrator's Guide.
To configure the central settings file, you must, at a minimum:
Create two entries in the permissions table. The first entry specifies the location of the central settings file (in the local filesystem or a Perforce server), and the second entry grants users read permission to that file.
Coding the Central Settings File
To add applets, you create a function that returns the applet that is to be executed, branching according to the key that P4V or P4Admin passed to the central settings file. For alerts, return JavaScript files. For tabs, return HTML files. The files can reside on the local filesystem, in a Perforce server, or on the Web, as follows:
p4://[user@][server[:port]]/files/depot-file-path
Finally, you include a line of code that executes the function to process the key passed by the Administration Tool. Important: This function call must be the last line of the central settings file.
The JavaScript API includes a set of methods for coding the logic in the central settings file. For details, see Method and Command Reference.
Following is an extremely basic central settings file that adds a tab to the Perforce Administration tool. The contents of the tab are determined by the specified HTML file.
function settings(key)
{
  if (key == "p4admin_mainTabs")
    return ["p4://[email protected]:1667/files/depot/jsapi/mytab.html"];
Configuring Applets for Specific Users and Groups
You can configure different central settings files on a per-user or per-group basis, for example, to provide different tabs for different users. One approach is to make separate entries in the permissions table for each group or user. For example, the following entries configure different tabs for developers and for artists.
list group dev centralsettings //depot/jsapi/centralsettings-dev.js
read group dev *               //depot/jsapi/centralsettings-dev.js
list group art centralsettings //depot/jsapi/centralsettings-art.js
read group art *               //depot/jsapi/centralsettings-art.js
Another approach is to specify the per-user or per-group logic in the central settings file itself. For example, the following logic ensures that users Tony and Herman each see their own tab, while other users see the default tab:.
function settings(key, user)
{
  if (key == 'p4admin_mainTabs')
  {
    if (user == "tonyz")
      return ["p4:///files/depot/jsapi/tony.html"];
    else if (user == "herman")
      return ["p4:///files/depot/jsapi/herm.html"];
    else
      return ["p4:///files/depot/jsapi/default-tab.html"];
  }
}
Central Settings Keys
The return value is a string or an array of strings, depending on the key that you specify. The following keys are supported:
Use this key to add alerts to the P4Admin home page's alerts widget. Return an array of strings specifying the JavaScript files to be executed. In alert applets, you cannot execute any p4 commands that alter the state of the workspace or server.
Use this key to add custom tabs to the main P4Admin or P4V window. Return an array of strings specifying the HTML files to be rendered.
Use this key to override P4V's defaults for performance-related settings with settings stored in an XML file that resides in a Perforce server. Return a string specifying a path to the XML file. For details, see Administering P4V Settings Centrally.
Use this key to replace the standard changelist submission dialog with your own custom dialog. Return a string specifying a path to the HTML file to be rendered in place of the Submit dialog.
Programming Applets
Issuing Perforce Commands
To issue Perforce commands, use the p4() method. For example, to obtain a list of open jobs:
var cmdOutput = P4JsApi.p4("jobs -e status=open");
The following example creates a new pending changelist.
var changeNumber = 'new';
var changeClient = 'bruno_ws';
var changeUser = 'bruno';
var changeStatus = 'new';
var changeDescription = 'Sample P4JsApi change.';
var changeSpec = 'Change: ' + changeNumber + '\n\n' +
                 'Client: ' + changeClient + '\n\n' +
                 'User: ' + changeUser + '\n\n'
                 'Status: ' + changeStatus + '\n\n' +
                 'Description:\n' +
                 ' ' + changeDescription + '\n';
P4JsApi.p4('-u bruno -c bruno_ws change -i "' + changeSpec + '"');
For long-running commands that you want to run asynchronously (to enable users to continue working instead of waiting for the results to be returned), specify a callback function to process the results. If you run the command asynchronously, an empty object is returned by the p4 method and results are returned to the callback function when the command completes. For example:.
/* Run the "p4 info" command with a callback function.
 * When the command is received by the p4 server,
 * its data will be returned to the 'myInfoCallback' function.
 */
function myInfoCommand()
{
    P4JsApi.p4("info", myInfoCallback);
}
/* Callback function for myInfoCommand().
 * Access the returned data contained in the 'arguments' array.
 * The data array contains the information that is returned by the
 * command that is run by the calling function.
 */
function myInfoCallback()
{
    // The 'data' array might have more than one element.
    var info = arguments[0].data[0];
    alert( info.userName );
To pass form data to a command, you can specify the form as a string or a JSON data object.
Example: Passing a form using a string
var jobstr = 'Job: new\nStatus: open\nUser: randy\nDate: 2010/08/10\nDescription:\n\tNew feature request: add 3D rendering\n';
Example: Passing a form using a JSON data object
var jobJSON = {
    'Job' : 'new',
    'Status' : 'open',
    'User' : 'randy',
    'Date' : '2010/08/10',
    'Description' : '\n\tNew feature request: add 3D rendering\n' };
The JavaScript API controls the p4 commands that can be executed, to maximize security. For a complete list of supported commands, see Method and Command Reference.
Processing Command Results and Handling Server Errors
Command results are returned as JavaScript objects composed of the following properties:
data: an array of objects composed of name/value pairs.
size: the number of objects in the data array.
error: Client error message, if any (for example if an invalid command is given)
info: Client info message, if any
text: Results of command, returned for the diff2 and print commands
binary: Returned if file is binary (also returned for text files if the file's line endings do not conform to the line ending setting for the workspace)
Server errors and messages are returned as data rows on the return object data array. For some commands (such as fstat), the server might return rows of data interspersed with rows of error, warning, and info messages. For example, the fstat command might return a "No such file" error message. These server error messages are returned as a row in the data array with a special property of p4ERROR, p4WARNING, or p4INFO. The value of these properties is an object with the following properties, providing more classification information about the message:
message: (text) Description of the problem
generic: (integer) Server numeric error code
severity: Severity level associated with the message
args: An object contain the arguments specified when the command was issued
The following example illustrates the basics of processing command results.
function processP4Result(p4out) {
  // Errors, warnings and infos coming from server appear in data[], 
  // interspersed with real data
  // Try to find them and promote to errors.
  // Of the statuses that do not represent a valid data returned, map
  // the possible properties from P4JsApi output to them 
  var msgTypeMapNonData = {
      'p4ERROR': 'ERROR'
      ,'p4WARNING': 'WARN'
      ,'p4INFO': 'INFO'
    }
    ,statuses = []
    ,data = []
    ,msgType
    ,nonDataProperty
    ,returnObj = {
      data: null
      ,statuses: null
      ,msgMap: {
        errors: []
        ,warnings: []
        ,infos: []
        ,valids: []
      }
      ,hasErrors: function() {return this.msgMap.errors.length>0;}
      ,hasWarnings: function() {return this.msgMap.warnings.length>0;}
      ,hasInfos: function() {return this.msgMap.infos.length>0;}
      ,hasValids: function() {return this.msgMap.valids.length>0;}
    };
  if (!!p4out.data && p4out.data.length > 0) {
    for (var i=0, len=p4out.data.length;i<len; i++) {
      var datum = p4out.data[i];
      // assume data is good
      msgType = 'VALID';
      // check if any of the non-data types exist on 
      // the data row
      for (var mProp in msgTypeMapNonData) {
        if (datum.hasOwnProperty(mProp)) {
          // row is not data, but some other msg from server.
          // save the property in nonDataProperty, and 
          // save the msgType 
          msgType = msgTypeMapNonData[mProp];
          nonDataProperty = mProp;
          break;
        }
      }
      statuses.push(
        {
          type: msgType
          ,subType: msgType == 'VALID' ? '' : datum[nonDataProperty].subType
          ,severity: msgType == 'VALID' ? 0 : datum[nonDataProperty].severity
          ,generic: msgType == 'VALID' ? 0 : datum[nonDataProperty].generic
          ,message:  msgType == 'VALID' ? '' : datum[nonDataProperty].message
        }
      );
      if (msgType == 'VALID') {
        data.push(datum);
      }
    }
  }
  
  // set the status array and data array on the resultObj
  returnObj.statuses = statuses
  returnObj.data = data;
  
  // from all the statuses collected, put them 
  // in mapped buckets by type on the returnObj so type-based getters 
  // can get them 
  statuses.forEach(function(stat){
    switch (stat.type) {
      case 'ERROR':
        returnObj.msgMap.errors.push(stat);
        break;
      case 'WARN':
        returnObj.msgMap.warnings.push(stat);
        break;
      case 'INFO':
        returnObj.msgMap.infos.push(stat);
        break;
      case 'VALID':
        returnObj.msgMap.valids.push(stat);
        break;
    }
  }, this);
  
  return returnObj;
}

// execute the command
var p4out = P4JsApi.p4('fstat //depot/NotARealFile.cc //depot/... -m500');

// process the results, looking for non-data messages
var processedResults = processP4Result(p4out);

// the processedResults object can contain any combination of data, and 
// error/warning/info messages.  Some error messages are not necessarily
// fatal.  Application must determine how to handle interspersed data and
// messages.  This simple example logs them to the console. 
if (processedResults.hasErrors()) {
  console.error('ERRORS: ' + processedResults.msgMap.errors.join('\n'));
}
if (processedResults.hasWarnings()) {
  console.log('WARNINGS: ' + processedResults.msgMap.warningss.join('\n'));
}
if (processedResults.hasInfos()) {
  console.log('INFO: ' + processedResults.msgMap.infos.join('\n'));
}

// use the data
if (processedResults.data.length>0) {
  // handle data response
  console.log('got data');
  // ...
}
Using P4Web URLs
If you have an instance of P4Web running, you can take advantage of its display logic by embedding a P4Web URL in your HTML file. To construct the P4Web URL, use a browser to display the desired data. When you have the data displayed as desired, copy the URL to your HTML file. For details about P4Web URLs, consult the discussion of action codes in the P4Web documentation on the Perforce web site:
http://www.perforce.com/perforce/doc.current/manuals/p4web/help/actioncodes.html
Extending P4Admin and P4V
Raising Alerts in P4Admin
Alerts are messages that are displayed on the Administration Tool home page, typically indicating an event or condition that requires attention. For example:
Alerts are visible only to superusers (because users with lower levels of privilege see only the Users and Groups tab). By default, alerts are run when the Administration Tool is refreshed (either automatically by the Administration Tool or manually when you click). To specify how often the alert is to be run, independent of refreshes, call startAlertRefreshTimer(). To display an alert, call addAlert(). To update its text, call updateAlert() (specifying the alert ID that was returned when you added the alert), and to remove an alert, call deleteAlert().
To display an image to the left of the alert text, specify the optional image parameter in the call to addAlert() or updateAlert(). To specify the image, use the HTML img tag. To display one of the P4V images provided in the JavaScript API, use the getImage() method as follows:
P4JsApi.addAlert('Alert text goes here', '<img src="' + P4JsApi.getImage('Image name goes here') + '" />');
To obtain a list of images provided by the JavaScript API, call the getImageNames() method.
By default, the image is displayed as 18 pixels square. To override this default, specify height and width attributes in the img tag.
Example: A Basic Alert
The following example creates an alert that tells you whether a Perforce counter is set to an even number, an odd number, or is not set.
In the central settings file, the following code specifies the JavaScript file to be executed when it is called with the alerts key:
function settings(key)
{
  if (key == "p4admin_alerts")
  {
    return ["p4:///files/depot/jsapi/alert.js"];
  }
}
The alert.js file displays alert text:
function testalert()
{
  var counter = P4JsApi.p4('counter alerttest');
  if (typeof alertID == 'undefined')
  {
    // add a new alert that will be updated below
    alertID = P4JsApi.addAlert("Checking counter...");
  }
  if (counter.data[0].value=='0')
  {
    P4JsApi.updateAlert(alertID,"Counter is unset");
  }
  else
  {
    if ((counter.data[0].value % 2) == 0)
    {
      P4JsApi.updateAlert(alertID,"Counter is even");
    }
    else
    {
      P4JsApi.updateAlert(alertID,"Counter is odd");
    }
  }
}
testalert();
Example: Check the Security Level
This alert calls the getServerSecurityLevel() method to check the security level of the Perforce server, and displays an alert if the level is lower than level two.
function securityAlert(slevel)
{
  if (slevel == 0)
    P4JsApi.addAlert ('Security level set to the lowest level: ' + slevel);
  if (slevel == 1)
    P4JsApi.addAlert ('Security level too low: ' + slevel);
}
securityAlert(P4JsApi.getServerSecurityLevel());
Adding Main Tabs to P4Admin and P4V
The Perforce JavaScript API for Visual Tools enables you to add up to 25 tabs to the P4Admin and P4V main windows. After you add custom tabs, uses can display them by choosing them in the View menu.
The following examples illustrate some approaches to coding tabs. Because the tab is essentially a WebKit HTML browser, you can code almost anything that a standard browser can render.
For example, to display an intranet page in a tab:
if (key == "p4v_mainTabs")
{
  return ["http://example.com/intranet/index.html"];
}
To add multiple tabs, specify them in an array. For example:
if (key == "p4admin_mainTabs")
{
  return ["C:\\jsapi\\Tab1.html","C:\\tmp\\Tab2.html","C:\\tmp\\Tab3.html"];
}
Display Connection Settings
The following code displays your connections settings (and other related settings). The contents of the title tag is displayed as the name of the tab.
<html>
<head>
  <title>Current Settings</title>
</head>
<body>
<script type="text/javascript">
  content = '<H1>Current Settings</H1>' +
    '<p><b>Port:</b> ' +
    P4JsApi.encodeForHTML(P4JsApi.getPort()) +
    '<p><b>Client workspace:</b> ' +
    P4JsApi.encodeForHTML(P4JsApi.getClient()) +
    '<p><b>User:</b> ' +
    P4JsApi.encodeForHTML(P4JsApi.getUser()) +
    '<p><b>Charset:</b> ' +
    P4JsApi.encodeForHTML(P4JsApi.getCharset()) +
    '<p><b>Server version:</b> ' +
    P4JsApi.encodeForHTML(P4JsApi.getServerVersion()) +
    '<p><b>Unicode?:</b> ' +
    (P4JsApi.isServerUnicode()=="true" ? 'Yes' : 'No') +
    '<p><b>Case sensitive?:</b> ' +
    (P4JsApi.isServerCaseSensitive()=="true" ? 'Yes' : 'No') +
    '<p><b>Security level:</b> ' +
    P4JsApi.encodeForHTML(P4JsApi.getServerSecurityLevel());
  document.write(content);
</script>
</body>
</html>
Display the Five Most Recent Submitted Changelists
The following example displays the five most recent submitted changelists in a simple table. The code in the central settings file specifies the HTML file to be invoked when the settings method is invoked with the p4admin_mainTabs key as an argument. The latest_changes.html file, which resides in the specified path in the depot, queries the server for the five most recent changelists, builds the table that contains the data, then displays the table.
(Note: The HTML example mixes JavaScript and HTML for the purposes of brevity. In a production environment, they are typically separate.)
Use the following central settings code:
function settings(key) {
  if (key == "p4admin_mainTabs")
  {
    return ["p4:///files/depot/jsapi/latest_changes.html"];
  }
}
And the following HTML code:
<html>
<head>
<title>Five Latest Changes</title>
<script type="text/javascript">
function getLast5Changes() {
  // get latest changes.
  changes = P4JsApi.p4("changes -l -m5");
  // add changes to table
  table = document.getElementById('changes');
  for (var i=0; i<changes.size; i++)
  {
    var change = changes.data[i];
    var row = document.createElement("tr");
    row.innerHTML =
      "<td>" +
      P4JsApi.encodeForHTML(change.Change) +
      "</td>" +
      "<td>" +
      P4JsApi.encodeForHTML(change.User) +
      "</td>" +
      "<td>" +
      P4JsApi.encodeForHTML(change.Description) +
      "</td>";
    table.appendChild(row);
  }
}
</script>
</head>
<body onLoad="getLast5Changes();">
<table id=changes border=1 width="50%">
<tr>
  <th>Change</th>
  <th>User</th>
  <th>Description</th>
</tr>
</table>
</body>
</html>
Detect and set selection
To detect user-selected files and folders or to select files and folders from a script, use the getSelection and setSelection methods. The following example creates a simple tab that exercises both. Note the use of the p4selection event, which is raised by P4V when the user selects a file or folder in the depot pane..
<html> 
<head> 
<link rel="stylesheet" type="text/css" href="table.css" /> 
<script type="text/javascript">P4JsApi.setWebKitDeveloperExtrasEnabled(true);</script> 
<script type="text/javascript"> 
    function reportSelection(v) { 
        var sel = ""; 
        for (var idx = 0; idx < v.length; ++idx) 
            sel += v[idx] + "<br>"; 
        document.getElementById("currentSel").innerHTML = sel; 
    } 
document.addEventListener('p4selection', function(e) { 
        reportSelection(e.customData); 
    }); 
function selectPaths() { 
        var pathlist = document.getElementById("paths").value; 
        var paths = pathlist.split(','); 
        var selList = []; 
        for (var idx = 0; idx < paths.length; ++idx) { 
            var path = paths[idx].trim(); 
            selList.push("p4:///files" + path); 
        } 
       P4JsApi.setSelection(selList, function(v) { console.log('completed: ' + v); });
    } 
</script> 
<title>Selection test</title> 
</head> 
<body onLoad="reportSelection(P4JsApi.getSelection());"> 
<div>Paths to select (comma separated): <input type="text" id="paths"><a href="javascript:selectPaths();">GO</a></div> 
<div><a href="javascript:reportSelection(P4JsApi.getSelection());">Current selection:</a><div id="currentSel"></div></div> 
</body> 
Implementing a Custom Submit Dialog in P4V
To replace the standard P4V Submit dialog with your own customized version, add logic to the central settings file specifying the HTML file that defines the custom dialog. Your custom Submit dialog can include logic, for example, to include information from a defect tracker. To submit the changelist, use the P4JsApi.p4 method to issue the submit command.
For more information on custom Submit dialogs (and for more examples), see the //public/perforce/p4jsapi/examples directory in the Perforce Public depot. Additional information about P4JsAPI can be found in the Perforce Knowledge Base:
http://kb.perforce.com/article/1209
Administering P4V Settings Centrally
By default, P4V users configure performance-related settings (such as the number of minutes between refresh requests) individually. You can use the JavaScript API to override individual settings, which is useful if you have a large number of P4V users connected to the same server and server performance is being affected by the volume of requests.
To override P4V's defaults, you define a variable called P4CentralSettingsJSON in the central settings file, and initialize the variable to the desired settings, using JSON format. For example:
  "P4VOverrides" : {
    "Connection/RefreshRate" : 3,
    "Connection/MaxChangelistFileCount" : 1000,
    "Connection/MaxFilePreviewSize" : 100,
    "Connection/MaxSpecListFetchCount" : 100
  }
You can override the following settings:
RefreshRate: How frequently P4V or P4Admin polls the Perforce server for changes to displayed information.
MaxChangelistFileCount: Maximum number of files displayed in changelists and the Resolve dialog.
MaxFilePreviewSize: Maximum size of file preview in kilobytes
MaxSpecListFetchCount: For changelist, branch mappings, jobs and labels, configures the number of entries to fetch, specified as a multiple of 100.
DisableJobsColumn: Suppresses display of Perforce jobs in the Submitted Changelist tab. If you do not use Perforce jobs, enabling this option can improve P4V performance by reducing job-related queries that are sent to the Perforce server. The following example shows you how to enable the option.
var P4CentralSettingsJSON =
{
    "P4VOverrides" : {
        "DisableJobsColumn": true
    }
Note the following:
Security
Creating Perforce applets using the Perforce JavaScript API for the Visual Tools is programming using Web technologies. The Perforce JavaScript API offers a variety of features for running applets securely:
Per-user and group administration: Using the Perforce protections table, you have complete control of who can run applets and which applets can be run from a particular server
Safe subset of Perforce commands: The API controls the subset of commands that applets are permitted to run. No destructive commands can be issued.
The JavaScript API disables the following WebKit features:
Developer extras (to enable use P4JsApi.setWebKitDeveloperExtrasEnabled method)
Owing to its architecture, the Perforce JavaScript API is immune to several types of attacks, as described in the following table.
Injecting SQL code to exploit database vulnerability
Uploading malicious code files to the Web server
No Web server. (However, the central settings file can be configured to point to untrusted Web servers.)
Using another user's cookies to impersonate that user when authenticating
Using UTF-7 to bypass Web browser delimiter checking and inject malicious code
Not possible in the WebKit engine used in the Perforce JavaScript API.
In general, to ensure security when configuring Perforce applets, observe the following best practices:
The following sections describe these best practices in more detail.
Secure Your Applet Source Code
Write-protect all HTML and JavaScript files that you use to implement Perforce applets. Your Perforce administrator can assign the minimum required write access to the central settings file and to every entry point (JavaScript or HTML file) referenced by the central settings file. When appropriate, grant write access to developers for specific applet source code files. To ensure that alterations to that source code do not affect other users, you can configure the central settings file so that only the developer can execute them.
Restrict Access to the Central Settings File
Ensure that only superusers (and a minimum number of them) have write access to the central settings file. The central settings file can reside in a Perforce server, in which case access to it is governed by the same permissions as all other depot files.
Use Only Trusted Perforce Servers
One possible means of attack is a "hostile" Perforce server: a server that has a central settings file designed to deliver applets that attack the local computer or connected computers. Applets can perform write operations (using the p4() method) only on primary connections (servers to which P4Admin or P4V is currently connected), so trusted servers are safe. However, an applet might be able to run a long- running read-only command on another server or otherwise disrupt the session.
To prevent this, your users can specify the settings for trusted servers and refuse applets from any other server. To configure trusted server settings, use the Applets preferences tab, or connect to the trusted server and, when prompted, choose Always accept applets. Never connect to an untrusted server with the applet feature turned on, and never accept applets from an untrusted server if prompted to do so.
Monitor Your Perforce Server Activity
Monitor your Perforce server to detect unauthorized access. (For details, refer to the discussion of the audit log in the Perforce System Administrators Guide.) If a malicious user gains superuser access to your server, they can modify the central settings file and protections table, and can install applets. (A malicious superuser can cause harm in many other ways, such as obliterating, replaying a journal, creating new superusers, or manipulating protections to exclude current superusers.)
Configure Only Trusted Web Servers
When configuring an entry in the central settings file that points to a Web server (http: protocol), ensure that the Web server is a secure one. If the Web server is not secure, the risk is that a malicious user can change the content on the server to compromise your installation.
Preventing Cross-Site Scripting (XSS) Attacks
Such attacks (also referred to as injection attacks) occur when malicious data or code is embedded in a seemingly safe Web page. To defend your installation against XSS attacks, never put dynamic data (that is, data returned by a Perforce Server command, entered by users, or originating from any uncontrolled source) in a context where it can be executed. Because the Perforce superuser controls access to the applets that are configured and how those applets transmit data, XSS vulnerabilities can be effectively minimized.
Some JavaScript frameworks provide utility methods for escaping dynamic data. However, do not assume that a framework automatically handles XSS issues. Applet developers might need to implement the logic required to prevent XSS attacks manually.
Types of XSS Issues
There are two main forms of this security issue:
Example: a user pastes some code into a changelist description, submits the changelist, then creates an applet that displays changelists. If the dynamic data is not escaped effectively, the code might execute and cause unintended results. This eventuality is unlikely, because data returned from the Perforce Server to the applet is encoded into a JavaScript object and the contents can be handled as data.
To prevent malicious injection, escape dynamic data (as described in the following sections). Likewise, escape the HTML and attribute header data according to established Web security practices.
Escaping Dynamic Data
The Perforce JavaScript API provides three methods for escaping dynamic data:
encodeForHTML(): Use for general HTML, for example, the JavaScript innerHTML method (which sets or retrieves the HTML between the start and end tags of an element).
encodeForHTMLAttribute(): Use in HTML attribute tag, for example setAttribute()
encodeForJavaScript(): Use for JavaScript, for example onLoadAttr.setAttribute()
Properties such as innerHTML are not safe, because the text being added is executed. Best practice is to add the HTML as an object. For example:
var textNode = document.createTextNode(text_to_be_displayed);
document.getElementById("htmlElement").appendChild(textNode);
If you must use innerHTML, escape the data that you are adding. For example:
function escapeHTML(html) {
    var div = document.createElement('div');
    var text = document.createTextNode(html);
    div.appendChild(text);
    return div.innerHTML;
}
The following example illustrates how you escape methods.
<div id="displayText">
</div>
<img id="errorImg" src=""
     style="display: none;"
     onError="alert('no image found');"/>
<script type="text/javascript">
  var result = P4JsApi.p4("some_p4_command");
  var data = result.data[0]; // data is a string
  // Example data that can cause attacks
  var data1 = '<img src="" style="display: none;"
               onError="alert(\'victim file obliterated\');"/>';
  var data2 = "test data here" ;
  var data3 = "alert('victim file obliterated')";
  // Rule: 1
  var displayTextDiv=document.getElementById("display text");
  displayTextDiv.innerHTML = data1; // insecure
  displayTextDiv.innerHTML = P4JsApi.encodeForHtml(data1); // secure
  // Rule: 2
  var inputTag = document.getElementById("textBox");
  inputTag.setAttribute("value", data2); // insecure
  inputTag.setAttribute("value",
      P4JsApi.encodeForHTMLAttribute(data2)); // secure
  // Rule: 3
  var errorImgTag = document.getElementById("errorImg");
  errorImgTag.setAttribute("onError", data3); // insecure
  errorImgTag.setAttribute("onError",
      P4JsApi.encodeForJavaScript(data3)); // secure
Troubleshooting
The following pointers can help you solve applet-related problems.
If you or your users cannot see a new customization and you are sure that the customization is correctly coded and configured, exit and relaunch P4V or P4Admin. Some settings are read only once, when the application is launched.
To view a list of applets and any errors that occur when the central settings file is loaded and executed, display the System Info dialog from the Help menu.
<script type='text/javascript'
src='https://getfirebug.com/firebug-lite.js'>
</script>
p4 commands issued by applets are indicated in the server log (flagged as "jsapi" commands).
 


Previous Table of Contents Index Next

Perforce 2013.1: Javascript API for Visual Tools
Copyright 2010-2013 Perforce Software.