aboutsummaryrefslogblamecommitdiffstats
path: root/lib/libbugzilla.js
blob: 990c75970696f6adfc81bbf544912afc65e1cea2 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                     
                               

                                         

                              
                                      

                                

                                                         
                                              

                                           



                                                                                
 
                                                              
                                 
 
                             
                                                            







                                                    
   

                                                                               
  

                                           


                                       

                                                                   
                                                

                                                                                                 
                          


   

                                                                                
  
                                                           
   
                                                      










                                                                              
         




                                                    

 
                                               






























                                                                                        
             
             
         













                                                                  
 

                                                  








                                                                             


   
  

                                                            
  
                                                                          
   
                                                                                          

















                                                                                          

     

                                                          

                                                                                              
     
 

















                                                                              
     

















                                                                       
 






                                                   

  
                                                  
                      

  
                                                     
                               

  
                                                 







                                    


                                                                         

                                                     

  
                                                                                     





                              

  
                                                                               






                          
  
 
                                                                                  











                                                                            

  

                                                                                
                                                                                        

                               


























                                                                              
    

  











                                                                                   
                                                                  




                                     
                                                                 







                                        
                                                            



                                                                        
 

                                              


                            







                                                     
























                                                                              

                                                                                 
                 



                     
 
                               
                                                         



                                                                                     
 













                                                                       
 


                                                                                        
 




                                                                            




                                                              
         



               
 
// Released under the MIT/X11 license
// http://www.opensource.org/licenses/mit-license.php
//
"use strict";
var preferences = require("preferences-service");
var prompts = require("prompts");
var clipboard = require("clipboard");
var tabs = require("tabs");
var logger = require("logger");
var passUtils = require("passwords");
var Request = require("request").Request;
var selfMod = require("self");
var urlMod = require("url");
var dataUtils = require("utils/data");
var xrpc = require("xmlrpc");
var panelMod = require("panel");

var JSONURLDefault = "https://fedorahosted.org/released"+
  "/bugzilla-triage-scripts/Config_data.json";
var BTSPrefNS = "bugzilla-triage.setting.";
var BTSPassRealm = "BTSXMLRPCPass";
var copiedAttributes = [ "queryButton", "upstreamButton", "parseAbrtBacktraces",
  "submitsLogging", "XorgLogAnalysis", "objectStyle", "signature",
  "suspiciousComponents" ];


var passwords = {}; // hash of passwords indexed by a hostname
var config = exports.config = {};

function Message(cmd, data) {
  console.log("Message: cmd = " + cmd + ", data = " + data);
	this.cmd = cmd;
	this.data = data;
}

function log(msg) {
	postMessage(new Message("LogMessage", msg));
}

/**
 * parse XML object out of string working around various bugs in Gecko
 * implementation see https://developer.mozilla.org/en/E4X for more information
 *
 * @param inStr
 *          String with unparsed XML string
 * @return XML object
 */
function parseXMLfromString (inStuff) {
  // if (typeof inStuff !== 'string') In future we should recognize
  // this.response
  // and get just .text property out of it. TODO
  var respStr = inStuff.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, ""); // bug
                                                                                        // 336551
  return new XML(respStr);
}

/**
 * In case URL contains alias, not the real bug number, get the real bug no from
 * the XML representation. Sets correct value to this.bugNo.
 *
 * This is a slow variant for bugs other than actual window
 */
function getRealBugNoSlow(bugNo, location, callback) {
  console.log("We have to deal with bug aliased as " + this.bugNo);
  // https://bugzilla.redhat.com/show_bug.cgi?ctype=xml&id=serialWacom
  Request({
    url: location.href+"&ctype=xml",
    onComplete: function(response) {
      if (response.status === 200) {
        var xmlRepr = parseXMLfromString(response.text);
        // TODO this probably wrong, both XPath and .text attribute
        var bugID = parseInt(xmlRepr.bug.bug_id.text, 10);
        if (isNaN(bugID)) {
          throw new Error("Cannot get bug no. even from XML representation!");
        }
        console.log("The real bug no. is " + bugID);
        callback(bugID)
      }
    }
  }).get();
}

function getPassword(login, domain, callback) {
  var passPrompt = "Enter your Bugzilla password for fixing MIME attachment types";
  var switchPrompt = "Do you want to switch off features requiring password completely";
  var prefName = BTSPrefNS+"withoutPassowrd";
  var retObject = {
    password:  null, // password string or null if no password provided
    withoutPass: false // whether user doesn't want to use password at all
  };

  passUtils.search({
    username: login,
    url: domain,
    realm: BTSPassRealm,
    onComplete: function onComplete([credential]) {
      if (credential) {
        // We found the password, just go ahead and use it
        retObject.password = credential.password;
        callback(retObject);
      }
      else {
        // We don't have a stored password, ask for one
        var passwordText = prompts.promptPassword(passPrompt);
        if (passwordText && passwordText.length > 0) {
          // Right, we've got it … store it and then use it.
          retObject.password = passwordText;
          passUtils.store({
            username: login,
            password: passwordText,
            url: domain,
            realm: BTSPassRealm,
            onComplete: function onComplete() {
              callback(retObject);
            }
          });
        }
        else {
          // We don't have password, and user haven't entered one?
          // Does he want to live passwordless?
          // FIXME should we call the callback at all?
          var switchOff = prompts.promptYesNoCancel(switchPrompt);
          if (switchOff) {
            preferences.set(prefName,true);
          }
          retObject.withoutPass = switchOff;
          callback(retObject);
        }
      }
    }
  });
}

exports.changeJSONURL = function changeJSONURL() {
  var prfNm = BTSPrefNS+"JSONURL";
  var url = preferences.get(prfNm,"");

  var reply = prompts.prompt("New location of JSON configuration file", url);
  if (reply && (reply != url)) {
    preferences.set(prfNm, reply.trim());
    // TODO Restartless add-on needs to resolve this.
    prompts.alert("For now, you should really restart Firefox!");
  }
};

/**
 *
 * libbz.getInstalledPackages(msg.data, function (pkgsMsg) {
 * worker.postMessage(pkgsMsg);
 *
 * locationLoginObj: { location: window.location.href, login: getLogin() }
 */
exports.getInstalledPackages = function getInstalledPackages(locationLoginObj, callback) {
  var installedPackages = {};
  var enabledPackages = [];
  var location = locationLoginObj.location;

  if (typeof location == "string") {
    location = new urlMod.URL(location);
  }

  // Collect enabled packages per hostname (plus default ones)
  if (config.gJSONData && ("commentPackages" in config.gJSONData)) {
    if ("enabledPackages" in config.gJSONData.configData) {
      var epObject = config.gJSONData.configData.enabledPackages;
      if (location.host in epObject) {
        enabledPackages = enabledPackages.concat(epObject[location.host].split(/[,\s]+/));
      }
      if ("any" in epObject) {
        enabledPackages = enabledPackages.concat(epObject.any.split(/[,\s]+/));
      }
    }

    var allIdx = null;
    if ((allIdx = enabledPackages.indexOf("all")) != -1) {
      enabledPackages.splice(allIdx, 1);
      enabledPackages = enabledPackages.concat(Object.keys(config.gJSONData.commentPackages));
    }

    // TODO To be decided, whether we cannot just eliminate packages in
    // installedPackages and having it just as a plain list of all cmdObjects.
    enabledPackages.forEach(function (pkg, idx, arr) {
      if (pkg in config.gJSONData.commentPackages) {
        installedPackages[pkg] = config.gJSONData.commentPackages[pkg];
      }
    });
  }

  // Expand commentIdx properties into full comments
  var cmdObj = {};
  for (var pkgKey in installedPackages) {
    for (var cmdObjKey in installedPackages[pkgKey]) {
      cmdObj = installedPackages[pkgKey][cmdObjKey];
      if ("commentIdx" in cmdObj) {
        cmdObj.comment = config.gJSONData.commentStrings[cmdObj.commentIdx];
        delete cmdObj.commentIdx;
      }
    }
  }

  if (config.gJSONData.commentStrings &&
      "sentUpstreamString" in config.gJSONData.commentStrings) {
    config.constantData.commentStrings = {};
    config.constantData.commentStrings.sentUpstreamString =
      config.gJSONData.commentStrings["sentUpstreamString"];
  }

  var locURL = new urlMod.URL(locationLoginObj.location);
  var passDomain = locURL.scheme + "://" + locURL.host;
  getPassword(locationLoginObj.login, passDomain, function (passwObj) {
    // In order to avoid sending whole password to the content script,
    // we are sending just these two Booleans.
    config.constantData.passwordState = {
      passAvailable: (passwObj.password !== null),
      withoutPass: passwObj.withoutPass
    };

    callback(new Message("CreateButtons", {
      instPkgs: installedPackages,
      constData: config.constantData,
      config: config.configData,
      kNodes: config.gJSONData.configData.killNodes
    }));
  });
};

exports.getClipboard = function getClipboard(cb) {
  cb(clipboard.get());
};

exports.setClipboard = function setClipboard(stuff) {
  clipboard.set(stuff, "text");
};

exports.getURL = function getURL(url, callback) {
  Request({
    url: url,
    onComplete: function(response) {
      if (response.status == 200) {
        callback(response.text);
      }
    }
  }).get();
};

exports.openStringInNewPanel = function openStringInNewPanel(inHTMLStr) {
  openURLInNewPanel("data:text/html;charset=utf-8," +
    inHTMLStr);
};

var openURLInNewPanel = exports.openURLInNewPanel = function openURLInNewPanel(url) {
  var panel = panelMod.Panel({
    contentURL: url,
    width: 640,
    height: 640
  });
  panel.show();
};

var openURLInNewTab = exports.openURLInNewTab = function openURLInNewTab(url) {
  tabs.open({
    url: url,
    inBackground: true,
    onReady: function(t) {
      t.activate();
    }
  });
};

exports.createUpstreamBug = function createUpstreamBug(urlStr, subject, comment) {
  tabs.open({
    url: urlStr,
    inBackground: true,
    onReady: function (t) {
      var otherElems = t.contentDocument.forms.namedItem("Create").elements;
      // Summary
      otherElems.namedItem("short_desc").value = subject;
      // Comment
      otherElems.namedItem("comment").value = collectComments();
      t.activate();
    }
  });
};

// Make a XML-RPC call ... most of the business logic should stay in the content
// script
exports.makeXMLRPCCall = function makeXMLRPCCall(url, login, method, params, callback) {
  var urlObj = urlMod.URL(url);
  getPassword(login,
     urlObj.schema + "://" + urlObj.host,
     function (passwObj) {
       if (!passwObj.password) {
         // TODO this should happen, only when user presses Escape in password
         // prompt
         return null;
       }

       var msg = new xrpc.XMLRPCMessage(method);
       params.forEach(function (par) {
         msg.addParameter(par);
       });
       msg.addParameter(login);
       msg.addParameter(passwObj.password);

       Request({
         url: url,
         onComplete: function(response) {
           if (response.status == 200) {
             var resp = parseXMLfromString(response.text);
             callback(resp.toXMLString());
           }
         },
         content: msg.xml(),
         contentType: "text/xml"
       }).post();
     }
  );
};

// Make a JSONL-RPC call ... most of the business logic should stay in the
// content script
// http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html
exports.makeJSONRPCCall = function makeJSONRPCCall(url, method, params, callback) {

//  {"version":"1.1", "method":"Bug.history", "params":{ "ids":[12345] } }
   var msg = {
       "version": "1.1",
       "method": method,
       "params": params
   };

//   console.log("makeJSONRPCCall: out = " + JSON.stringify(msg));

   Request({
     url: url,
     onComplete: function(response) {
       if (response.status == 200) {
//         console.log("makeJSONRPCCall: in = " + response.text);
         callback(response.json.result);
       }
     },
     content: JSON.stringify(msg),
     contentType: "application/json"
   }).post();
};

exports.initialize = function initialize(config, callback) {
  var prefJSONURLName = BTSPrefNS+"JSONURL";
  var prefDebugName = BTSPrefNS+"debug";
  var urlStr = "",
    debugOption = false; // should we spit out a lot of debugging output

  if (preferences.isSet(prefJSONURLName)) {
    urlStr = preferences.get(prefJSONURLName);
  }
  else {
    urlStr = JSONURLDefault;
    preferences.set(prefJSONURLName, JSONURLDefault);
  }

  if (preferences.isSet(prefDebugName)) {
    debugOption = preferences.get(prefDebugName);
  }
  else {
    preferences.set(prefDebugName, debugOption);
  }

  // Randomize URL to avoid caching
  // TODO see https://fedorahosted.org/bugzilla-triage-scripts/ticket/21
  // for more thorough discussion and possible further improvement
  urlStr += (urlStr.match(/\?/) == null ? "?" : "&") + (new Date()).getTime();

  Request({
    url: urlStr,
    onComplete: function (response) {
      if (response.status == 200) {
        config.gJSONData = response.json;

        // Get additional tables
        if ("downloadJSON" in config.gJSONData.configData) {
          var URLsList = config.gJSONData.configData.downloadJSON;
          var dwnldObj = "";
          URLsList.forEach(function (arr) {
            var title = arr[0];
            var url = arr[1];
            Request({
              url: url,
              onComplete: function(response) {
                if (response.status == 200) {
                  config.constantData[title] = response.json;
                } else {
                  console.error("Cannot download " + title + " from URL " + url);
                }
              }
            }).get();
          });
        }

        config.configData = {};
        config.configData.debuggingVerbose = debugOption;
        config.configData.matches = config.gJSONData.configData.matches;
        config.configData.skipMatches = config.configData.matches.map(function(x) {
          return x.replace("show_bug.cgi.*","((process|post)_bug|attachment)\.cgi$");
        });

        config.constantData = {};
        if ("constantData" in config.gJSONData) {
          config.constantData = config.gJSONData.constantData;
          config.constantData.queryUpstreamBug = JSON.parse(
            selfMod.data.load("queryUpstreamBug.json"));
          config.constantData.XMLRPCData = JSON.parse(
            selfMod.data.load("XMLRPCdata.json"));
          config.constantData.bugzillaLabelNames =
            JSON.parse(selfMod.data.load("bugzillalabelNames.json"));
          config.constantData.newUpstreamBug =
            JSON.parse(selfMod.data.load("newUpstreamBug.json"));
          config.constantData.ProfessionalProducts =
            JSON.parse(selfMod.data.load("professionalProducts.json"));
        }

        if ("CCmaintainer" in config.constantData) {
          config.configData.defBugzillaMaintainerArr = config.constantData.CCmaintainer;
        }

        copiedAttributes.forEach(function (attrib) {
          if (attrib in config.gJSONData.configData) {
            config.configData[attrib] = config.gJSONData.configData[attrib];
          }
        });

        if ("submitsLogging" in config.gJSONData.configData &&
          config.gJSONData.configData.submitsLogging) {
            logger.initialize(JSON.parse(selfMod.data.load(
              "bugzillalabelAbbreviations.json")));
        }
       }
    callback();
    }
  }).get();
}