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