Thursday, February 8, 2018

Running CURL through Java: Windows and Linux Executions

Normally, the environment variables are set through envp and the command is broken up into separate strings.

However, when on Windows, all the envp and command strings should be concatenated and then processed through Git Bash.  Here is the implementation.


package com.mycompany.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;

public class CurlHelperGeneric {
    
    /**
     *  USERNAME=sizu
        RSA_TOKEN=myRSAToken
        http_proxy=http://myproxy.com:8080/ https_proxy=http://myproxy.com:8080/ curl --proxy-header "Proxy-Authorization: Basic $(echo -n "USERNAME:$RSA_TOKEN" | openssl enc -base64 -e)" "https://myservice.com/registry/v1/service" -k 

        http_proxy=http://myproxy.com:8080/ https_proxy=http://myproxy.com:8080/ curl --proxy-header 'Proxy-Authorization: "+helper.getBasic()+"' 'https://myservice.com/registry/v1/service' -k
     * @param args
     * @throws Exception
     */
    public static void main (String[] args) throws Exception {
         
        CurlHelperGeneric helper = new CurlHelperGeneric();
        String[] cmdArray = new String[] {
                "curl"
                "--proxy-header"
                "Proxy-Authorization: Basic c2lXXXXXXzEyMjAwMjQyMTI2OA==",
                "https://myservice.com/v1/pools",
                "-k"
                };
        String[] envp = new String[]{
            "http_proxy=http://myproxy.com:8080/",
            "https_proxy=http://myproxy.com:8080/"
        };
        
        String output = helper.runCommand(cmdArray, envp);
        
        System.out.println("Output:"+output);
    }
    
    public String runCommand(String[] cmdArray, String[] envp) {
        String os = System.getProperty("os.name");
        if(os.toUpperCase().contains("WINDOWS")) {
//            String[] cmdArray = new String[] {
//                    "C:\\Users\\sizu\\AppData\\Local\\Programs\\Git\\bin\\sh.exe",
//                    "--login",
//                    "-i",
//                    "-c",
//                    "http_proxy=http://myproxy.com:8080/ https_proxy=http://myproxy.com:8080/ curl --proxy-header 'Proxy-Authorization: "+proxyAuthorization+"' '"+httpString+"' -k",
//            };
//            String[] envp = new String[]{};
            
            String curlCommandToRunThroughGit = "";
            for(int i=0; i<envp.length; i++) {
                curlCommandToRunThroughGit = curlCommandToRunThroughGit + envp[i] + " ";
            }
            for(int i=0; i<cmdArray.length; i++) {
                curlCommandToRunThroughGit = curlCommandToRunThroughGit + "'" + cmdArray[i] + "' "// Wrap Authorization and https String with single quotes
            }
            
            cmdArray = new String[] {
                "C:\\Users\\sizu\\AppData\\Local\\Programs\\Git\\bin\\sh.exe",
                "--login",
                "-i",
                "-c",
                curlCommandToRunThroughGit,
            };
            envp = new String[]{};
        }
        
        
        
        try {
            Process process = Runtime.getRuntime().exec(cmdArray, envp);
            StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream()); // Gobble Stream even if not used
            StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream());
            errorGobbler.start();
            outputGobbler.start();
            int processComplete = process.waitFor(); // Wait for process to finish    
            
//            System.out.println("ERR:"+errorGobbler.streamContent.toString());
//            System.out.println("OUT:"+outputGobbler.streamContent.toString());
            
            return outputGobbler.streamContent.toString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
    
    public static class StreamGobbler extends Thread {
        InputStream is;
        
        StreamGobbler(InputStream is) {
            this.is = is;
        }
        
        StringBuilder streamContent = new StringBuilder();
        
        public void run() {
            try {
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                String line=null;
                while ( (line = br.readLine()) != null) {
                    streamContent.append(line);
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();  
            }
        }
    }
}

Wednesday, February 7, 2018

Running CURL Through Java

Sometimes, for whatever reason, curl and Java will not execute the same way.  In some cases, a Java request will work, while in other cases a curl request will work.  Or, you may know how to make one use case work but not the other.


package com.mycompany.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;

public class CurlHelper {
    public String gitExecutable = "C:\\Users\\sizu\\AppData\\Local\\Programs\\Git\\bin\\sh.exe";
    public String fileToRunCurl = "C:\\command.bat";
    public String outputFile = "C:\\command.txt";
    public String curlCommand;

    private String fullCommand;
    private String output = null;
 
    public static void main (String[] args) throws Exception {
         
        CurlHelper helper = new CurlHelper ();
        helper.curlCommand = "http_proxy=http://myproxy.mycompany.com:8080/ https_proxy=http://myproxy.mycompany.com:8080/ curl --proxy-header 'Proxy-Authorization: Basic c2XXXXXwMzEyMjAwMjIyMDkzOA==' 'https://myapiendpoint.mycompany.com:8080/solr/prod/select?rows=10000&format=json' -k";
        
        System.out.println("Output:"+helper.runCurlCommandViaGitBash());
    }
    
    public String runCurlCommandViaGitBash() {
        fullCommand = "\""+gitExecutable+"\" --login -i -c \""+curlCommand+" > '"+outputFile+"'\"";
        createCommandFile();
        executeCommand();
        readOutputFile();
        
        return output;
    }
    
    private void createCommandFile() {
        // Step 1: Write command in command.bat
        File fileToCreate = new File(fileToRunCurl);
        try {
            if(fileToCreate.getParentFile() != null && !fileToCreate.exists()) {
                fileToCreate.getParentFile().mkdirs();
            }
            FileWriter fw = new FileWriter(fileToCreate);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(fullCommand);
            bw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void executeCommand() {
        System.out.println("executeCommand START");
        // Step 2: Execute command.bat
        try {
            String[] cmdArray = new String[1];
            cmdArray[0] = fileToRunCurl;
    
            Process process = Runtime.getRuntime().exec(cmdArray, nullnew File("C:\\"));
            System.out.println("executeCommand B");
            
            // WHEN RUNNING RUNTIME EXEC MULTIPLE TIMES (Over 1000 Runs) WOULD FREEZE - CLEAR ERROR AND OUTPUT STREAMS: https://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
            // any error message?
            StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERR");            
            
            // any output?
            StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUT");
                
            // kick them off
            errorGobbler.start();
            outputGobbler.start();
            // WHEN RUNNING RUNTIME EXEC MULTIPLE TIMES (Over 1000 Runs) WOULD FREEZE - CLEAR ERROR AND OUTPUT STREAMS: https://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
            
            int processComplete = process.waitFor(); // Sometimes the process would never complete.  // Could add logic to monitor ERR stream and call process.destroy() if necessary.
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("executeCommand FINISH");
    }
    
    private void readOutputFile() {
        output = "";
        try {
            FileReader fr = new FileReader(new File(outputFile));
            BufferedReader br = new BufferedReader(fr);
            String sCurrentLine;
            while ((sCurrentLine = br.readLine()) != null) {
                output = output + sCurrentLine + '\n';
                //System.out.println(sCurrentLine);
            }
            br.close();
            fr.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static class StreamGobbler extends Thread
    {
        InputStream is;
        String type;
        OutputStream os;
        
        StreamGobbler(InputStream is, String type)
        {
            this(is, type, null);
        }
        StreamGobbler(InputStream is, String type, OutputStream redirect)
        {
            this.is = is;
            this.type = type;
            this.os = redirect;
        }
        
        public void run()
        {
            try
            {
                PrintWriter pw = null;
                if (os != null)
                    pw = new PrintWriter(os);
                    
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                String line=null;
                while ( (line = br.readLine()) != null)
                {
                    if (pw != null)
                        pw.println(line);
                    System.out.println(type + ">" + line);    
                }
                if (pw != null)
                    pw.flush();
            } catch (IOException ioe)
                {
                ioe.printStackTrace();  
                }
        }
    }
}


This code give credit to www.javaworld.com for creating the Stream Gobbler, without which the process would block for for loops, running this curl helper thousands of times. The Stream Gobbler, makes sure the error and output streams do not get clogged up.

Sometimes using http proxies might be easier in curl than java.  Also, you might have rsa tokens that need to be Base 64 encoded to access the proxy.  Below will only work if USERNAME and RSA_TOKEN are system variables.

http_proxy=http://myproxy.mycompany.com:8080/ https_proxy=http://myproxy.mycompany.com:8080/ curl --proxy-header "Proxy-Authorization: Basic $(echo -n "$USERNAME:$RSA_TOKEN" | openssl enc -base64 -e)" "https://myservice.com/registry/v1/service" -k

Or you might encode the Authorization in Java, prior to using the CurlHelper above.

public static String getBasicFromUsernameAndRSAToken() {
        String basic_auth = new String(org.apache.commons.codec.binary.Base64.encodeBase64(("USERNAME:RSA_TOKEN").getBytes()));
        return "Basic " + basic_auth;
    }
In some cases, you will have proxy authentication and authentication.
"curl 'https://myservice.mycompany.com/api/query/Applications' -H 'Authorization: ll/V1nJzd3kl4q9Y/ln7XXXXXX2sInDmAKnSzp6HMSon0YlfDMXNbJrUw9OWDmL'"

Friday, September 15, 2017

Pagination, Search and Filter

Often times developers will implement a pagination to reduce the number of results.  However, it is always important to remember that pagination must be coupled with a search feature.  Otherwise, people lose one of the best capabilities that is available when all the results are available.

Today, I will add code based on https://www.w3schools.com/howto/howto_js_filter_table.asp and https://ebay.gitbooks.io/mindpatterns/content/navigation/pagination.html .


This design includes the following:

  • A function is provided to filter given the row from the table and the filter string
  • A label is added after the pagination to show which results are being displayed
  • Query parameters allowed for a filter string, page number to display and results per page
  • Disabled or hidden pagination elements when not applicable
  • The current page is always in the middle of the pagination module
  • The table holds the source of truth for the page and resultsPerPage attributes
  • The input holds the source of truth for the filter string
  • 5 numbers and 2 arrows are allowed in the pagination module

Saturday, July 23, 2016

Find longest sublist of distinct words

package test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Assert;

public class LongestDistinctWords {
    public static void main(String[] args) {

        List<String> resultA = new LongestDistinctWords().occurences(new ArrayList<String>());
        System.err.println("resultA:"+resultA);
        Assert.assertEquals(resultA, Arrays.asList());
        
        List<String> resultB = new LongestDistinctWords().occurences(Arrays.asList("My","My","House","House","Time","Time","Time","Jeep","Jeep","Jeep","Jeep","Jeep","Jeep","Jeep","Jeep","Cat"));
        System.err.println("resultB:"+resultB);
        Assert.assertEquals(resultB, Arrays.asList("My","House"));
        
        List<String> resultC = new LongestDistinctWords().occurences(Arrays.asList("My","Test","My","My","Test","My","Hello","Dog","Hello"));
        System.err.println("resultC:"+resultC);
        Assert.assertEquals(resultC, Arrays.asList("Test""My""Hello""Dog"));
        
        List<String> resultD = new LongestDistinctWords().occurences(Arrays.asList("My","Test","My","Test","Hello","My","Test","Hello","Dog"));
        System.err.println("resultD:"+resultD);
        Assert.assertEquals(resultD, Arrays.asList("My","Test","Hello","Dog"));

        List<String> resultE = new LongestDistinctWords().occurences(Arrays.asList("My","My","And","My","And","My","Hi","How","Are","Hi","Yes","We"));
        System.err.println("resultE:"+resultE);
        Assert.assertEquals(resultE, Arrays.asList("And","My","Hi","How","Are"));

        List<String> resultF = new LongestDistinctWords().occurences(Arrays.asList("My","My","And","My","And","My","Hi","How","Are","Hi","My","We","Can","See"));
        System.err.println("resultF:"+resultF);
        Assert.assertEquals(resultF, Arrays.asList("How","Are","Hi","My","We","Can","See"));
    }
    
    /**
     * Find longest sublist of distinct words
     * @param words
     * @return
     */

    public List<String> occurences(List<String> words) {
        Integer longestStart = 0;
        Integer longestEnd = 0;
        Map<String, Integer> indexAfterWordLastSeenMap = new HashMap<String, Integer>();
        Integer currentStart = 0;
        Integer currentEnd = 0;
        for(String word: words) {
            Integer lastSeen = indexAfterWordLastSeenMap.get(word);
            if(lastSeen != null && lastSeen > currentStart) {
                if(currentEnd - currentStart > longestEnd - longestStart) {
                    longestStart = currentStart;
                    longestEnd = currentEnd;
                }
                currentStart = lastSeen;
            } 
            currentEnd++;
            indexAfterWordLastSeenMap.put(word, currentEnd);
        }
        if(currentEnd - currentStart > longestEnd - longestStart) {
            longestStart = currentStart;
            longestEnd = currentEnd;
        }
        return words.subList(longestStart, longestEnd);
    }
}

Parsing a list of words and searching for most common occurences

package test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.junit.Assert;

public class KthOccurence {
    public static void main(String[] args) {

        List<String> resultA = new KthOccurence().occurences(4, new ArrayList<String>());
        System.err.println("resultA:"+resultA);
        Assert.assertEquals(resultA, Arrays.asList(nullnullnullnull));
        
        List<String> resultB = new KthOccurence().occurences(4, Arrays.asList("My","My","House","House","Time","Time","Time","Jeep","Jeep","Jeep","Jeep","Jeep","Jeep","Jeep","Jeep","Cat"));
        System.err.println("resultB:"+resultB);
        Assert.assertEquals(resultB, Arrays.asList("My","House","Time","Jeep"));
        
        List<String> resultC = new KthOccurence().occurences(1, Arrays.asList("My","Test","My","My","Test","My","Hello","Dog","Hello"));
        System.err.println("resultC:"+resultC);
        Assert.assertEquals(resultC, Arrays.asList("My"));
        
        List<String> resultD = new KthOccurence().occurences(2, Arrays.asList("My","Test","My","My","Test","My","Hello","Dog","Hello"));
        System.err.println("resultD:"+resultD);
        Assert.assertEquals(resultD, Arrays.asList("Hello","My"));

        List<String> resultE = new KthOccurence().occurences(3, Arrays.asList("My","Test","My","My","Test","My","Hello","Dog","Hello"));
        System.err.println("resultE:"+resultE);
        Assert.assertEquals(resultE, Arrays.asList("Test","Hello","My"));
    }
    
    /**
     * Example: Find the 50 most common words from a book
     * @param K - Number of distinct words to return with highest redundancy
     * @param words - List of words with repeated words
     * @return - Ordered list of highest repeated words
     */

    public List<String> occurences(Integer K, List<String> words) {
        Map<String, Integer> countMap = new HashMap<String, Integer>();
        
        LinkedList<String> trackedList = new LinkedList<String>(); // smallest to largest count in list
        for(int i=0; i<K; i++) {
            trackedList.add(null);
        }
        countMap.put(null, 0);

        for(String currentWord: words) {
            Integer currentCount = countMap.get(currentWord);
            if(currentCount == null) {
                currentCount = 1;
            } else {
                currentCount++;
            }
            countMap.put(currentWord, currentCount);
            
            ListIterator<String> iterator = trackedList.listIterator();
            while(iterator.hasNext()) { // O(K) to walk through linked list
                String nextIteration = iterator.next();
                
                if(currentCount < countMap.get(nextIteration)) { // If 
                    iterator.previous();
                    break;
                }
                
                if(currentWord.equals(nextIteration)) { // if come across word, remove it
                    iterator.remove(); // O(1) to remove from linked list
                }
            }
            // location of iterator is insertion point
            iterator.add(currentWord); // O(1) to add to linked list
            if(trackedList.size() > K) { // An insertion occurred without a remove
                trackedList.removeFirst(); // O(1) to remove first from linked list
            }
        }
        
        return trackedList;
    }
}

Tuesday, April 19, 2016

Hello World Javascript Extension for Chrome and Firefox

Developing a Chrome Extension

The folder structure will be as follows:
  • src
    • content
      • HelloWorld.js
      • rightClickMenuForGoogleChrome.js
    • manifest.json

HelloWorld.js

// Main function
HelloWorld=function() {
  alert('Hello World');
};

rightClickMenuForGoogleChrome.js

HelloWorldImportJS = "content/HelloWorld.js";

// To get all these menus to work, there are a few differences between Firefox and Chrome Extensions
// gContextMenu is defined when using Firefox extension and in this case, DocumentFunction should return gContextMenu.gContextMenu.target.ownerDocument instead of document

chrome.contextMenus.create({
  id: 'helloWorldTool',
  title: "Hello World Tool",
  contexts:["all"]
});
  chrome.contextMenus.create({
    id: 'helloWorld',
    parentId: 'helloWorldTool',
    title: "Run Hello World",
    contexts:["all"],
    onclick: function(info, tab){
      chrome.tabs.executeScript(tab.id, {file:HelloWorldImportJS}, function() {
        chrome.tabs.executeScript(tab.id, {code:"HelloWorld();"});
      });
    }
  });

manifest.json

{
  "manifest_version": 2,
  "name""Hello World Tool",
  "short_name""HWT",
  "description""Hello World Tool Description.",
  "version""1.0",
  "minimum_chrome_version""38",
  "permissions": [
    "contextMenus"
    "tabs",
    "http://*/*", 
    "https://*/*" ],

  "background": {
    "scripts": [
      "content/rightClickMenuForGoogleChrome.js"
    ]
  }
}

Chrome Web Store Publishing

Try It Out

  • Go to the link to the Published Extension and click "Add to Chrome"
  • Add to Chrome
  • Right click pages and test "Run Hello World"

Developing a Firefox Extension

The folder structure will be as follows:
  • src
    • content
      • HelloWorld.js
      • rightClickMenuForFirefox.xul
    • chrome.manifest
    • install.rdf

HelloWorld.js

See above.

rightClickMenuForFirefox.xul

<?xml version="1.0"?>
<overlay id="helloWorldToolOverlay"
    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <statusbar id="status-bar">
        <statusbarpanel id="mypanel" label=""/>
    </statusbar>
    <script type="application/x-javascript" src="chrome://helloWorld/content/HelloWorld.js"/>
    // This is for the right click menu.
    <popup id="contentAreaContextMenu">
        <menuseparator id="helloWorldSeparator" insertafter="context-stop"/>  
        <menu id="helloWorldTool" label="Hello World Tool" insertafter="context-stop">            
            <menupopup> 
                <menuitem id="helloWorld" label="Run Hello World" oncommand="HelloWorld();"/> 
            </menupopup> 
        </menu>
    </popup>
</overlay>

chrome.manifest

content    helloWorld content/ contentaccessible=yes
overlay    chrome://browser/content/browser.xul    chrome://helloWorld/content/rightClickMenuForFirefox.xul

install.rdf

<?xml version="1.0"?>
<RDF:RDF xmlns:em="http://www.mozilla.org/2004/em-rdf#"
         xmlns:NC="http://home.netscape.com/NC-rdf#"
         xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <RDF:Description RDF:about="urn:mozilla:install-manifest"
                   em:id="helloWorldTool@helloWorld.com"
                   em:version="1.0"
                   em:name="Hello World Tool"
                   em:type="2"
                   em:unpack="true"
                   em:description="Hello World Tool Description."
                   em:creator="scottizu@gmail.com"
                   em:homepageURL="http://www.ebay.com"
                   >
    <em:targetApplication RDF:resource="rdf:#$RI.6J3"/>
  </RDF:Description>
  <RDF:Description RDF:about="rdf:#$RI.6J3"
                   em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
                   em:minVersion="1.5"
                   em:maxVersion="45.*" />
</RDF:RDF>

Firefox Web Store Publishing

  • Zip content, chrome.manifest and install.rdf
  • Rename as "HelloWorldTool.Firefox.pre.xpi"
  • Login to https://addons.mozilla.org/en-US/developers/
  • Submit an Add-on
  • Choose File, HelloWorldTool.Firefox.pre.xpi, Upload
  • Category: Web Development, Mozilla Public License, version 2.0, Preliminary Review
If you want to distribute by yourself, you can install the extension.  Then go to the installation folder: (C:\Users\sizu\AppData\Roaming\Mozilla\Firefox\Profiles\2xrz9a7g.default-1451441818907\extensions\helloWorldTool@helloWorld.com ).  If it has a META-INF folder, this contains the digital signature for the Firefox Extension.  Zip the content, META-INF, chrome.manifest, install.rdf file and rename "HelloWorldTool.Firefox.xpi".  You can go to 'about:addons' in Firefox, click the settings wheel, Install Add on and select this file.  The extension will then show in 'about:addons'.

Try It Out

  • Go to the link to the Published Extension in Firefox and click Add to Firefox
    • https://addons.mozilla.org/en-US/developers/addon/hello-world-tool/versions/1850966
    • https://addons.mozilla.org/en-US/firefox/addon/hello-world-tool/
  • Add to Firefox
  • Right click pages and test "Run Hello World"

Thursday, January 14, 2016

Firefox and Chrome Extensions and Automatic Updates

https://developer.mozilla.org/en-US/docs/Extension_Versioning,_Update_and_Compatibility
https://developer.mozilla.org/en-US/Add-ons/Distribution
https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm
C:\Users\sizu\AppData\Roaming\npm\node_modules\jpm\bin\jpm
https://addons.mozilla.org/en-US/developers/addon/api/key/

TOPIC 1 - XPI SIGNATURE CREATION

To digitally sign the Firefox extension, you will need to get some Firefox credentials: JWT issuer, JWT secret.  These can be received after signing up by...

https://addons.mozilla.org/en-US/firefox/users/login?to=/en-US/developers/addon/api/key/ Tools->Manage API Keys->Generate New Credentials (JWT issuer, JWT secret)

 To create the .xpi file (extension file), you can do so with the jpm tool.  You should first download NPM, which you can use in Git Bash (needs to be downloaded too).

STEP 1: Then, you can run 'npm install jpm --global' in Git Bash.

STEP 2: After, you may create a package.json file using 'jpm init'.  After this is created, you should add additional fields beyond title, name, etc.  I added an id (@my_izu_extension), homepage, icon and main (picked a random js file in my content folder).

ERROR 1: I had an error with "Using existing install.rdf", since I had manually created this.  I just moved this into a backup folder after copying its contents to the package.json file, in the appropriate format.

ERROR 2: I had an Invalid addon ID.  You can use a GUID or any string with '@'.  I chose the later and used the ID '@my_izu_extension'.

STEP 3: Run 'jpm sign --api-key ${JWTissuer} --api-secret ${JWT secret}'.

Note: Here replace the ${xxx} with the credentials from the Firefox site (https://addons.mozilla.org/en-US/developers/addon/api/key/)

TOPIC 2 - AUTOMATIC UPDATES FOR FIREFOX EXTENSION

STEP 1: Firefox Updates over http.  Download McCoy from https://developer.mozilla.org/en-US/docs/Mozilla/Projects/McCoy.  Keys->Create New Key.  Right Click New Key->Copy Public Key.

STEP 2: This goes into the Firefox install.rdf file as updateKey. (put this in package JSON as updateKey, right along with id, title, name, etc).

Note: If using an install.rdf file, this can be done automatically with Extension->Add Key to Install Manifest and finding install.rdf file.

STEP 3:

To create an FirefoxUpdates.rdf file by copying it from here:
https://developer.mozilla.org/en-US/docs/Extension_Versioning,_Update_and_Compatibility

Delete the first <RDF:li> tag since you will use the second one which since the updateLink will be over http.  Change the id to match yours (mine was '@my_izu_extension').

Add an updateLink and updateInfoURL (these will need to be hosted somewhere).

You can generate the updateHash with openssl as follows 'openssl dgst -sha256 @my_izu_extension.xpi > updateHash.txt'

STEP 4:

This will run openssl and create a hash in the file updateHash.txt.  Something like this...

SHA256(my_izu_extension.xpi)= 2aefcd04c56f060bcfc53acfe96a87af120ed05933ab7969c91642847aa445df

Copy the hash and add it to the updateHash field but add the prefix 'sha256:'.  Something like this...

<em:updateHash>sha256:2aefcd04c56f060bcfc53acfe96a87af120ed05933ab7969c91642847aa445df</em:updateHash>

STEP 5: Then, add the updateHash into the FirefoxUpdates.rdf file by Update->Sign Update Manifest.

STEP 6: Upload the new @my_izu_extension.xpi file and FirefoxUpdates.rdf file appropriately.

RECAP.  Here's my setup.  I have a package.json with a my updateURL (pointing to where I will store the FirefoxUpdates.rdf file) and updateKey (generated by McCoy).  After adding these, I use jpm to create the @my_izu_extension.xpi file.

I create/modify the update.rdf file to include updateLink (pointing to where I will store the .xpi file).  I create a hash of the @my_izu_extension.xpi file (using openssl), then modify the updateHash field in FirefoxUpdates.rdf.  Finally, I digitally sign the FirefoxUpdates.rdf file using McCoy which adds the digital signature.

Last, I upload both the @my_izu_extension.xpi file and the FirefoxUpdates.rdf file to their appropriate locations.  I host them both in the same folder, both hosted by a Tomcat Server.

TOPIC 3 - AUTOMATIC UPDATES FOR GOOGLE CHROME EXTENSION

STEP 1: Modify the manifest.json file to add an update_url field right along side the name, description, etc.

STEP 2: Create an update.xml file like the one shown here: https://developer.chrome.com/extensions/autoupdate

I get the app id by using the chrome://extensions page, with Developer mode checked and use 'Load unpacked extension' which loads the extension and gives me the ID (aka appID).

STEP 3: Upload the update.xml file to where the update_url points.