View Javadoc
1   /*
2    * Copyright 2012 Brian Matthews
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.btmatthews.maven.plugins.crx;
18  
19  import java.io.*;
20  
21  import org.codehaus.plexus.component.annotations.Component;
22  
23  /**
24   * Implementation of {@link ArchiveHelper} that outputs the CRX archive.
25   *
26   * @author <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
27   * @since 1.1.0
28   */
29  @Component(role = ArchiveHelper.class, hint = "crx")
30  public class CRXArchiveHelper implements ArchiveHelper {
31      /**
32       * Used to as a mask when extracting 8 least significant bits of a integer.
33       */
34      private static final int BYTE_MASK = 0xFF;
35  
36      /**
37       * The amounts in order to move bits 15 thru 8 into the 8 least significant bits.
38       */
39      private static final int SHIFT_8 = 8;
40  
41      /**
42       * The amounts in order to move bits 23 thru 16 into the 8 least significant bits.
43       */
44      private static final int SHIFT_16 = 16;
45  
46      /**
47       * The amounts in order to move bits 31 thru 24 into the 8 least significant bits.
48       */
49      private static final int SHIFT_24 = 24;
50  
51      /**
52       * The magic number for CRX files.
53       */
54      private static final byte[] CRX_MAGIC = { 0x43, 0x72, 0x32, 0x34 };
55  
56      /**
57       * The CRX header version number in little endian format.
58       */
59      private static final byte[] CRX_VERSION = { 0x02, 0x00, 0x00, 0x00 };
60  
61      /**
62       * Generate the CRX file writing the header, public key, signature and data.
63       *
64       * @param crxFile    The target CRX file.
65       * @param crxArchive The CRX archive.
66       * @throws IOException If there was an error writing the CRX file.
67       */
68      public void writeArchive(final File crxFile, final CRXArchive crxArchive) throws IOException {
69          if (crxFile.exists()) {
70              crxFile.delete();
71          } else {
72              crxFile.getParentFile().mkdirs();
73          }
74          final FileOutputStream crx = new FileOutputStream(crxFile);
75          try {
76              crx.write(CRX_MAGIC);
77              crx.write(CRX_VERSION);
78              writeLength(crx, crxArchive.getPublicKey().length);
79              writeLength(crx, crxArchive.getSignature().length);
80              crx.write(crxArchive.getPublicKey());
81              crx.write(crxArchive.getSignature());
82              crx.write(crxArchive.getData());
83          } finally {
84              crx.close();
85          }
86      }
87  
88      /**
89       * Read the CRX archive from a file loading the header, public key, signature and data.
90       *
91       * @param crxFile The source CRX file.
92       * @return The CRX archive.
93       * @throws IOException If there was an error reading the CRX file.
94       */
95      public CRXArchive readArchive(final File crxFile) throws IOException {
96          final byte[] buffer = new byte[4];
97          final InputStream crxIn = new FileInputStream(crxFile);
98          try {
99              crxIn.read(buffer);
100             crxIn.read(buffer);
101             final int publicKeyLength = readLength(crxIn);
102             final int signatureLength = readLength(crxIn);
103             final byte[] publicKey = new byte[publicKeyLength];
104             crxIn.read(publicKey);
105             final byte[] signature = new byte[signatureLength];
106             crxIn.read(signature);
107             final int dataLength = (int)(crxFile.length() - 16 - publicKeyLength - signatureLength);
108             final byte[] data = new byte[dataLength];
109             crxIn.read(data);
110             return new CRXArchive(publicKey, signature, data);
111         } finally {
112             crxIn.close();
113         }
114     }
115 
116     /**
117      * Write a 32-bit integer to the output stream in little endian format.
118      *
119      * @param out The output stream.
120      * @param val The 32-bit integer.
121      * @throws IOException If there was a problem writing to the output stream.
122      */
123     private void writeLength(final OutputStream out, final int val) throws IOException {
124         out.write(val & BYTE_MASK);
125         out.write((val >> SHIFT_8) & BYTE_MASK);
126         out.write((val >> SHIFT_16) & BYTE_MASK);
127         out.write((val >> SHIFT_24) & BYTE_MASK);
128     }
129 
130     /**
131      * Read a 32-bit integer from the output stream in little endian format.
132      *
133      * @param in The input stream.
134      * @return The 32-bit integer.
135      * @throws IOException If there was a problem reading from the input stream.
136      */
137     private int readLength(final InputStream in) throws IOException {
138         final byte[] buffer = new byte[4];
139         in.read(buffer, 0, 4);
140         return (buffer[3] << SHIFT_24) | ((buffer[2] & BYTE_MASK) << SHIFT_16) | ((buffer[1] & BYTE_MASK) << SHIFT_8)
141                 | (buffer[0] & BYTE_MASK);
142     }
143 }