1 // Copyright 2015 The Bazel Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 // ijar.cpp -- .jar -> _interface.jar tool.
16 //
17 
18 #include <errno.h>
19 #include <limits.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <memory>
24 
25 #include "third_party/ijar/zip.h"
26 
27 namespace devtools_ijar {
28 
29 bool verbose = false;
30 
31 // Reads a JVM class from classdata_in (of the specified length), and
32 // writes out a simplified class to classdata_out, advancing the
33 // pointer. Returns true if the class should be kept.
34 bool StripClass(u1 *&classdata_out, const u1 *classdata_in, size_t in_length);
35 
36 const char *CLASS_EXTENSION = ".class";
37 const size_t CLASS_EXTENSION_LENGTH = strlen(CLASS_EXTENSION);
38 const char *KOTLIN_MODULE_EXTENSION = ".kotlin_module";
39 const size_t KOTLIN_MODULE_EXTENSION_LENGTH = strlen(KOTLIN_MODULE_EXTENSION);
40 
41 const char *MANIFEST_DIR_PATH = "META-INF/";
42 const size_t MANIFEST_DIR_PATH_LENGTH = strlen(MANIFEST_DIR_PATH);
43 const char *MANIFEST_PATH = "META-INF/MANIFEST.MF";
44 const size_t MANIFEST_PATH_LENGTH = strlen(MANIFEST_PATH);
45 const char *MANIFEST_HEADER =
46     "Manifest-Version: 1.0\r\n"
47     "Created-By: bazel\r\n";
48 const size_t MANIFEST_HEADER_LENGTH = strlen(MANIFEST_HEADER);
49 // These attributes are used by JavaBuilder, Turbine, and ijar.
50 // They must all be kept in sync.
51 const char *TARGET_LABEL_KEY = "Target-Label: ";
52 const size_t TARGET_LABEL_KEY_LENGTH = strlen(TARGET_LABEL_KEY);
53 const char *INJECTING_RULE_KIND_KEY = "Injecting-Rule-Kind: ";
54 const size_t INJECTING_RULE_KIND_KEY_LENGTH = strlen(INJECTING_RULE_KIND_KEY);
55 
56 class JarExtractorProcessor : public ZipExtractorProcessor {
57  public:
58   // Set the ZipBuilder to add the ijar class to the output zip file.
59   // This pointer should not be deleted while this class is still in use and
60   // it should be set before any call to the Process() method.
SetZipBuilder(ZipBuilder * builder)61   void SetZipBuilder(ZipBuilder *builder) { this->builder_ = builder; }
62   virtual void WriteManifest(const char *target_label,
63                              const char *injecting_rule_kind) = 0;
64 
65  protected:
66   // Not owned by JarStripperProcessor, see SetZipBuilder().
67   ZipBuilder *builder_;
68 };
69 
70 // ZipExtractorProcessor that select only .class file and use
71 // StripClass to generate an interface class, storing as a new file
72 // in the specified ZipBuilder.
73 class JarStripperProcessor : public JarExtractorProcessor {
74  public:
JarStripperProcessor()75   JarStripperProcessor() {}
~JarStripperProcessor()76   virtual ~JarStripperProcessor() {}
77 
78   virtual void Process(const char *filename, const u4 attr, const u1 *data,
79                        const size_t size);
80   virtual bool Accept(const char *filename, const u4 attr);
81 
82   virtual void WriteManifest(const char *target_label,
83                              const char *injecting_rule_kind);
84 };
85 
StartsWith(const char * str,const size_t str_len,const char * prefix,const size_t prefix_len)86 static bool StartsWith(const char *str, const size_t str_len,
87                        const char *prefix, const size_t prefix_len) {
88   return str_len >= prefix_len && strncmp(str, prefix, prefix_len) == 0;
89 }
90 
EndsWith(const char * str,const size_t str_len,const char * suffix,const size_t suffix_len)91 static bool EndsWith(const char *str, const size_t str_len, const char *suffix,
92                      const size_t suffix_len) {
93   return str_len >= suffix_len &&
94          strcmp(str + str_len - suffix_len, suffix) == 0;
95 }
96 
IsKotlinModule(const char * filename,const size_t filename_len)97 static bool IsKotlinModule(const char *filename, const size_t filename_len) {
98   return StartsWith(filename, filename_len, MANIFEST_DIR_PATH,
99                     MANIFEST_DIR_PATH_LENGTH) &&
100          EndsWith(filename, filename_len, KOTLIN_MODULE_EXTENSION,
101                   KOTLIN_MODULE_EXTENSION_LENGTH);
102 }
103 
Accept(const char * filename,const u4)104 bool JarStripperProcessor::Accept(const char *filename, const u4 /*attr*/) {
105   const size_t filename_len = strlen(filename);
106   if (IsKotlinModule(filename, filename_len)) {
107     return true;
108   }
109   if (filename_len < CLASS_EXTENSION_LENGTH ||
110       strcmp(filename + filename_len - CLASS_EXTENSION_LENGTH,
111              CLASS_EXTENSION) != 0) {
112     return false;
113   }
114   return true;
115 }
116 
IsModuleInfo(const char * filename)117 static bool IsModuleInfo(const char *filename) {
118   const char *slash = strrchr(filename, '/');
119   if (slash == NULL) {
120     slash = filename;
121   } else {
122     slash++;
123   }
124   return strcmp(slash, "module-info.class") == 0;
125 }
126 
Process(const char * filename,const u4,const u1 * data,const size_t size)127 void JarStripperProcessor::Process(const char *filename, const u4 /*attr*/,
128                                    const u1 *data, const size_t size) {
129   if (verbose) {
130     fprintf(stderr, "INFO: StripClass: %s\n", filename);
131   }
132   if (IsModuleInfo(filename) || IsKotlinModule(filename, strlen(filename))) {
133     u1 *q = builder_->NewFile(filename, 0);
134     memcpy(q, data, size);
135     builder_->FinishFile(size, /* compress: */ false, /* compute_crc: */ true);
136   } else {
137     u1 *buf = reinterpret_cast<u1 *>(malloc(size));
138     u1 *classdata_out = buf;
139     if (!StripClass(buf, data, size)) {
140       free(classdata_out);
141       return;
142     }
143     u1 *q = builder_->NewFile(filename, 0);
144     size_t out_length = buf - classdata_out;
145     memcpy(q, classdata_out, out_length);
146     builder_->FinishFile(out_length, /* compress: */ false,
147                          /* compute_crc: */ true);
148     free(classdata_out);
149   }
150 }
151 
152 // Copies the string into the buffer without the null terminator, returns
153 // updated buffer pointer
WriteStr(u1 * buf,const char * str)154 static u1 *WriteStr(u1 *buf, const char *str) {
155   size_t len = strlen(str);
156   memcpy(buf, str, len);
157   return buf + len;
158 }
159 
160 // Writes a manifest attribute including a "\r\n" line break, returns updated
161 // buffer pointer.
WriteManifestAttr(u1 * buf,const char * key,const char * val)162 static u1 *WriteManifestAttr(u1 *buf, const char *key, const char *val) {
163   buf = WriteStr(buf, key);
164   buf = WriteStr(buf, val);
165   *buf++ = '\r';
166   *buf++ = '\n';
167   return buf;
168 }
169 
WriteManifest(const char * target_label,const char * injecting_rule_kind)170 void JarStripperProcessor::WriteManifest(const char *target_label,
171                                          const char *injecting_rule_kind) {
172   if (target_label == nullptr) {
173     return;
174   }
175   builder_->WriteEmptyFile(MANIFEST_DIR_PATH);
176   u1 *start = builder_->NewFile(MANIFEST_PATH, 0);
177   u1 *buf = start;
178   buf = WriteStr(buf, MANIFEST_HEADER);
179   buf = WriteManifestAttr(buf, TARGET_LABEL_KEY, target_label);
180   if (injecting_rule_kind) {
181     buf = WriteManifestAttr(buf, INJECTING_RULE_KIND_KEY, injecting_rule_kind);
182   }
183   size_t total_len = buf - start;
184   builder_->FinishFile(total_len, /* compress: */ false,
185                        /* compute_crc: */ true);
186 }
187 
188 class JarCopierProcessor : public JarExtractorProcessor {
189  public:
JarCopierProcessor(const char * jar)190   JarCopierProcessor(const char *jar) : jar_(jar) {}
~JarCopierProcessor()191   virtual ~JarCopierProcessor() {}
192 
193   virtual void Process(const char *filename, const u4 /*attr*/, const u1 *data,
194                        const size_t size);
195   virtual bool Accept(const char *filename, const u4 /*attr*/);
196 
197   virtual void WriteManifest(const char *target_label,
198                              const char *injecting_rule_kind);
199 
200  private:
201   class ManifestLocator : public ZipExtractorProcessor {
202    public:
ManifestLocator()203     ManifestLocator() : manifest_buf_(nullptr), manifest_size_(0) {}
~ManifestLocator()204     virtual ~ManifestLocator() { free(manifest_buf_); }
205 
206     u1 *manifest_buf_;
207     size_t manifest_size_;
208 
Accept(const char * filename,const u4)209     virtual bool Accept(const char *filename, const u4 /*attr*/) {
210       return strcmp(filename, MANIFEST_PATH) == 0;
211     }
212 
Process(const char *,const u4,const u1 * data,const size_t size)213     virtual void Process(const char * /*filename*/, const u4 /*attr*/,
214                          const u1 *data, const size_t size) {
215       manifest_buf_ = (u1 *)malloc(size);
216       memmove(manifest_buf_, data, size);
217       manifest_size_ = size;
218     }
219   };
220 
221   const char *jar_;
222 
223   u1 *AppendTargetLabelToManifest(u1 *buf, const u1 *manifest_data,
224                                   const size_t size, const char *target_label,
225                                   const char *injecting_rule_kind);
226 };
227 
Process(const char * filename,const u4,const u1 * data,const size_t size)228 void JarCopierProcessor::Process(const char *filename, const u4 /*attr*/,
229                                  const u1 *data, const size_t size) {
230   if (verbose) {
231     fprintf(stderr, "INFO: CopyFile: %s\n", filename);
232   }
233   // We already handled the manifest in WriteManifest
234   if (strcmp(filename, MANIFEST_DIR_PATH) == 0 ||
235       strcmp(filename, MANIFEST_PATH) == 0) {
236     return;
237   }
238   u1 *q = builder_->NewFile(filename, 0);
239   memcpy(q, data, size);
240   builder_->FinishFile(size, /* compress: */ false, /* compute_crc: */ true);
241 }
242 
Accept(const char *,const u4)243 bool JarCopierProcessor::Accept(const char * /*filename*/, const u4 /*attr*/) {
244   return true;
245 }
246 
WriteManifest(const char * target_label,const char * injecting_rule_kind)247 void JarCopierProcessor::WriteManifest(const char *target_label,
248                                        const char *injecting_rule_kind) {
249   ManifestLocator manifest_locator;
250   std::unique_ptr<ZipExtractor> in(
251       ZipExtractor::Create(jar_, &manifest_locator));
252   in->ProcessAll();
253 
254   bool wants_manifest =
255       manifest_locator.manifest_buf_ != nullptr || target_label != nullptr;
256   if (wants_manifest) {
257     builder_->WriteEmptyFile(MANIFEST_DIR_PATH);
258     u1 *start = builder_->NewFile(MANIFEST_PATH, 0);
259     u1 *buf = start;
260     // Three cases:
261     // 1. We need to merge the target label into a pre-existing manifest
262     // 2. Write a manifest from scratch with a target label
263     // 3. Copy existing manifest without adding target label
264     if (manifest_locator.manifest_buf_ != nullptr && target_label != nullptr) {
265       buf = AppendTargetLabelToManifest(buf, manifest_locator.manifest_buf_,
266                                         manifest_locator.manifest_size_,
267                                         target_label, injecting_rule_kind);
268     } else if (target_label != nullptr) {
269       buf = WriteStr(buf, MANIFEST_HEADER);
270       buf = WriteManifestAttr(buf, TARGET_LABEL_KEY, target_label);
271       if (injecting_rule_kind) {
272         buf = WriteManifestAttr(buf, INJECTING_RULE_KIND_KEY,
273                                 injecting_rule_kind);
274       }
275     } else {
276       memcpy(buf, manifest_locator.manifest_buf_,
277              manifest_locator.manifest_size_);
278       buf += manifest_locator.manifest_size_;
279     }
280 
281     size_t total_len = buf - start;
282     builder_->FinishFile(total_len, /* compress: */ false,
283                          /* compute_crc: */ true);
284   }
285 }
286 
AppendTargetLabelToManifest(u1 * buf,const u1 * manifest_data,const size_t size,const char * target_label,const char * injecting_rule_kind)287 u1 *JarCopierProcessor::AppendTargetLabelToManifest(
288     u1 *buf, const u1 *manifest_data, const size_t size,
289     const char *target_label, const char *injecting_rule_kind) {
290   const char *line_start = (const char *)manifest_data;
291   const char *data_end = (const char *)manifest_data + size;
292   while (line_start < data_end) {
293     const char *line_end = strchr(line_start, '\n');
294     // Go past return char to point to next line, or to end of data buffer
295     line_end = line_end != nullptr ? line_end + 1 : data_end;
296 
297     // Copy line unless it's Target-Label/Injecting-Rule-Kind and we're writing
298     // that ourselves
299     if (strncmp(line_start, TARGET_LABEL_KEY, TARGET_LABEL_KEY_LENGTH) != 0 &&
300         strncmp(line_start, INJECTING_RULE_KIND_KEY,
301                 INJECTING_RULE_KIND_KEY_LENGTH) != 0) {
302       size_t len = line_end - line_start;
303       // Skip empty lines
304       if (len > 0 && line_start[0] != '\r' && line_start[0] != '\n') {
305         memcpy(buf, line_start, len);
306         buf += len;
307       }
308     }
309     line_start = line_end;
310   }
311   buf = WriteManifestAttr(buf, TARGET_LABEL_KEY, target_label);
312   if (injecting_rule_kind != nullptr) {
313     buf = WriteManifestAttr(buf, INJECTING_RULE_KIND_KEY, injecting_rule_kind);
314   }
315   return buf;
316 }
317 
318 // WriteManifest, including zip file format overhead.
EstimateManifestOutputSize(const char * target_label,const char * injecting_rule_kind)319 static size_t EstimateManifestOutputSize(const char *target_label,
320                                          const char *injecting_rule_kind) {
321   if (target_label == nullptr) {
322     return 0;
323   }
324   // local headers
325   size_t length = 30 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
326   // central directory
327   length += 46 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
328   // zip64 EOCD entries
329   length += 56 * 2;
330 
331   // manifest content
332   length += MANIFEST_HEADER_LENGTH;
333   // target label manifest entry, including newline
334   length += TARGET_LABEL_KEY_LENGTH + strlen(target_label) + 2;
335   if (injecting_rule_kind) {
336     // injecting rule kind manifest entry, including newline
337     length += INJECTING_RULE_KIND_KEY_LENGTH + strlen(injecting_rule_kind) + 2;
338   }
339   return length;
340 }
341 
342 // Opens "file_in" (a .jar file) for reading, and writes an interface
343 // .jar to "file_out".
OpenFilesAndProcessJar(const char * file_out,const char * file_in,bool strip_jar,const char * target_label,const char * injecting_rule_kind)344 static void OpenFilesAndProcessJar(const char *file_out, const char *file_in,
345                                    bool strip_jar, const char *target_label,
346                                    const char *injecting_rule_kind) {
347   std::unique_ptr<JarExtractorProcessor> processor;
348   if (strip_jar) {
349     processor =
350         std::unique_ptr<JarExtractorProcessor>(new JarStripperProcessor());
351   } else {
352     processor =
353         std::unique_ptr<JarExtractorProcessor>(new JarCopierProcessor(file_in));
354   }
355   std::unique_ptr<ZipExtractor> in(
356       ZipExtractor::Create(file_in, processor.get()));
357   if (in == NULL) {
358     fprintf(stderr, "Unable to open Zip file %s: %s\n", file_in,
359             strerror(errno));
360     abort();
361   }
362   u8 output_length =
363       in->CalculateOutputLength() +
364       EstimateManifestOutputSize(target_label, injecting_rule_kind);
365   std::unique_ptr<ZipBuilder> out(ZipBuilder::Create(file_out, output_length));
366   if (out == NULL) {
367     fprintf(stderr, "Unable to open output file %s: %s\n", file_out,
368             strerror(errno));
369     abort();
370   }
371   processor->SetZipBuilder(out.get());
372   processor->WriteManifest(target_label, injecting_rule_kind);
373 
374   // Process all files in the zip
375   if (in->ProcessAll() < 0) {
376     fprintf(stderr, "%s\n", in->GetError());
377     abort();
378   }
379 
380   // Add dummy file, since javac doesn't like truly empty jars.
381   if (out->GetNumberFiles() == 0) {
382     out->WriteEmptyFile("dummy");
383   }
384   // Finish writing the output file
385   if (out->Finish() < 0) {
386     fprintf(stderr, "%s\n", out->GetError());
387     abort();
388   }
389   // Get all file size
390   size_t in_length = in->GetSize();
391   size_t out_length = out->GetSize();
392   if (verbose) {
393     fprintf(stderr, "INFO: produced interface jar: %s -> %s (%d%%).\n", file_in,
394             file_out, static_cast<int>(100.0 * out_length / in_length));
395   }
396 }
397 }  // namespace devtools_ijar
398 
399 //
400 // main method
401 //
usage()402 static void usage() {
403   fprintf(stderr,
404           "Usage: ijar "
405           "[-v] [--[no]strip_jar] "
406           "[--target label label] [--injecting_rule_kind kind] "
407           "x.jar [x_interface.jar>]\n");
408   fprintf(stderr, "Creates an interface jar from the specified jar file.\n");
409   exit(1);
410 }
411 
main(int argc,char ** argv)412 int main(int argc, char **argv) {
413   bool strip_jar = true;
414   const char *target_label = NULL;
415   const char *injecting_rule_kind = NULL;
416   const char *filename_in = NULL;
417   const char *filename_out = NULL;
418 
419   for (int ii = 1; ii < argc; ++ii) {
420     if (strcmp(argv[ii], "-v") == 0) {
421       devtools_ijar::verbose = true;
422     } else if (strcmp(argv[ii], "--strip_jar") == 0) {
423       strip_jar = true;
424     } else if (strcmp(argv[ii], "--nostrip_jar") == 0) {
425       strip_jar = false;
426     } else if (strcmp(argv[ii], "--target_label") == 0) {
427       if (++ii >= argc) {
428         usage();
429       }
430       target_label = argv[ii];
431     } else if (strcmp(argv[ii], "--injecting_rule_kind") == 0) {
432       if (++ii >= argc) {
433         usage();
434       }
435       injecting_rule_kind = argv[ii];
436     } else if (filename_in == NULL) {
437       filename_in = argv[ii];
438     } else if (filename_out == NULL) {
439       filename_out = argv[ii];
440     } else {
441       usage();
442     }
443   }
444 
445   if (filename_in == NULL) {
446     usage();
447   }
448 
449   // Guess output filename from input:
450   char filename_out_buf[PATH_MAX];
451   if (filename_out == NULL) {
452     size_t len = strlen(filename_in);
453     if (len > 4 && strncmp(filename_in + len - 4, ".jar", 4) == 0) {
454       strcpy(filename_out_buf, filename_in);
455       strcpy(filename_out_buf + len - 4, "-interface.jar");
456       filename_out = filename_out_buf;
457     } else {
458       fprintf(stderr,
459               "Can't determine output filename since input filename "
460               "doesn't end with '.jar'.\n");
461       return 1;
462     }
463   }
464 
465   if (devtools_ijar::verbose) {
466     fprintf(stderr, "INFO: writing to '%s'.\n", filename_out);
467   }
468 
469   devtools_ijar::OpenFilesAndProcessJar(filename_out, filename_in, strip_jar,
470                                         target_label, injecting_rule_kind);
471   return 0;
472 }
473