| // Copyright 2011 The Goma Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| |
| #include "jar_parser.h" |
| |
| #include <limits.h> |
| #include <string.h> |
| |
| #include <memory> |
| |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_split.h" |
| #include "basictypes.h" |
| #include "glog/logging.h" |
| #include "minizip/unzip.h" |
| #include "path.h" |
| |
| #ifdef _WIN32 |
| # include "config_win.h" |
| #endif |
| |
| namespace devtools_goma { |
| |
| JarParser::JarParser() { |
| } |
| |
| static void AddJarFile(const string& jar_file, const string& cwd, |
| std::set<string>* jar_files); |
| |
| static void ReadManifest(char* content, const string& cwd, |
| std::set<string>* jar_files) { |
| // The format of manifest files is similar to HTTP header |
| // (i.e., "key1: value1<CRLF>key2: value2<CRLF>") |
| // We need only the value of Class-Path. |
| char* p = content; |
| static const char kClassPathHeader[] = "Class-Path: "; |
| const size_t kClassPathHeaderSize = strlen(kClassPathHeader); |
| for (;;) { |
| if (!strncmp(p, kClassPathHeader, kClassPathHeaderSize)) { |
| p += kClassPathHeaderSize; |
| break; |
| } |
| |
| p = strchr(p, '\n'); |
| if (!p) { |
| return; |
| } |
| p++; |
| } |
| |
| char* end = strchr(p, '\r'); |
| if (end) { |
| *end = '\0'; |
| } |
| |
| for (auto&& path : absl::StrSplit(p, ' ', absl::SkipEmpty())) { |
| if (absl::EndsWith(path, ".jar")) { |
| AddJarFile(string(path), cwd, jar_files); |
| } |
| } |
| } |
| |
| class ScopedUnzFile { |
| public: |
| explicit ScopedUnzFile(const char* path) |
| : path_(path), |
| unz_file_(unzOpen64(path)), |
| open_current_(false) { |
| } |
| ~ScopedUnzFile() { |
| if (open_current_) { |
| unzCloseCurrentFile(unz_file_); |
| open_current_ = false; |
| } |
| if (IsValid()) { |
| int err = unzClose(unz_file_); |
| LOG_IF(WARNING, err != UNZ_OK) << "unzClose path=" << path_ |
| << "err=" << err; |
| } |
| } |
| |
| bool IsValid() const { |
| return unz_file_ != 0; |
| } |
| |
| int GetGlobalInfo64(unz_global_info64* info) { |
| return unzGetGlobalInfo64(unz_file_, info); |
| } |
| |
| int GetCurrentFileInfo64(unz_file_info64* fileinfo, |
| char* filename, unsigned long filename_bufsize, |
| void* extra, unsigned long extra_bufsize, |
| char* comment, unsigned long comment_bufsize) { |
| return unzGetCurrentFileInfo64(unz_file_, fileinfo, |
| filename, filename_bufsize, |
| extra, extra_bufsize, |
| comment, comment_bufsize); |
| } |
| |
| int OpenCurrentFile() { |
| DCHECK(!open_current_) << path_; |
| int err = unzOpenCurrentFile(unz_file_); |
| open_current_ = (err == UNZ_OK); |
| return err; |
| } |
| |
| int ReadCurrentFile(void* buf, unsigned len) { |
| DCHECK(open_current_) << path_; |
| return unzReadCurrentFile(unz_file_, buf, len); |
| } |
| |
| int CloseCurrentFile() { |
| open_current_ = false; |
| return unzCloseCurrentFile(unz_file_); |
| } |
| |
| int GoToNextFile() { |
| DCHECK(!open_current_) << path_; |
| return unzGoToNextFile(unz_file_); |
| } |
| |
| private: |
| const string path_; |
| unzFile unz_file_; |
| bool open_current_; |
| DISALLOW_COPY_AND_ASSIGN(ScopedUnzFile); |
| }; |
| |
| static void AddJarFile(const string& jar_file, const string& cwd, |
| std::set<string>* jar_files) { |
| const string& jar_path = file::JoinPathRespectAbsolute(cwd, jar_file); |
| if (!jar_files->insert(jar_path).second) { |
| return; |
| } |
| |
| LOG(INFO) << "Reading jar file: " << jar_path; |
| |
| string basedir; |
| #ifndef _WIN32 |
| char SEP = '/'; |
| #else |
| char SEP = '\\'; |
| #endif |
| size_t last_sep_pos = jar_path.rfind(SEP); |
| if (last_sep_pos != string::npos) { |
| basedir = jar_path.substr(0, last_sep_pos); |
| } |
| |
| ScopedUnzFile scoped_jar(jar_path.c_str()); |
| if (!scoped_jar.IsValid()) { |
| LOG(WARNING) << "Not jar archive? (unzOpen64):" << jar_path; |
| return; |
| } |
| |
| int err; |
| unz_global_info64 jar_info; |
| err = scoped_jar.GetGlobalInfo64(&jar_info); |
| if (err) { |
| LOG(WARNING) << "Broken jar archive? (unzGetGlobalInfo64): " << jar_path |
| << " err=" << err; |
| return; |
| } |
| |
| for (ZPOS64_T i = 0; i < jar_info.number_entry; i++) { |
| unz_file_info64 fileinfo; |
| char filename[PATH_MAX]; |
| err = scoped_jar.GetCurrentFileInfo64(&fileinfo, |
| filename, sizeof(filename), |
| nullptr, 0, |
| nullptr, 0); |
| if (err) { |
| LOG(WARNING) << "Broken jar archive? (unzGetCurrentFileInfo64): " |
| << jar_path << " err=" << err; |
| return; |
| } |
| |
| static const char kManifestFileName[] = "META-INF/MANIFEST.MF"; |
| if (!strcmp(filename, kManifestFileName)) { |
| err = scoped_jar.OpenCurrentFile(); |
| if (err) { |
| LOG(WARNING) << "Broken jar archive? (unzOpenCurrentFile): " |
| << jar_path << " err=" << err; |
| return; |
| } |
| |
| size_t sz = static_cast<size_t>(fileinfo.uncompressed_size); |
| std::unique_ptr<char[]> buf(new char[sz + 1]); |
| err = scoped_jar.ReadCurrentFile(buf.get(), sz); |
| if (err < 0) { |
| LOG(WARNING) << "Broken jar archive? (unzReadCurrentFile): " |
| << jar_path << " err=" << err; |
| return; |
| } |
| buf.get()[fileinfo.uncompressed_size] = '\0'; |
| ReadManifest(buf.get(), basedir, jar_files); |
| err = scoped_jar.CloseCurrentFile(); |
| LOG_IF(WARNING, err != UNZ_OK) << "CloseCurrentFile: " << jar_path |
| << " err=" << err; |
| return; |
| } |
| |
| err = scoped_jar.GoToNextFile(); |
| if (err == UNZ_END_OF_LIST_OF_FILE) { |
| break; |
| } |
| if (err) { |
| LOG(WARNING) << "Broken jar archive? (unzGoToNextFile): " << jar_path |
| << " err=" << err; |
| return; |
| } |
| } |
| |
| if (!absl::EndsWith(jar_file, ".zip")) { |
| LOG(WARNING) << jar_file << " doesn't contain manifest"; |
| } |
| } |
| |
| void JarParser::GetJarFiles(const std::vector<string>& input_jar_files, |
| const string& cwd, |
| std::set<string>* jar_files) { |
| for (const auto& input_jar_file : input_jar_files) { |
| AddJarFile(input_jar_file, cwd, jar_files); |
| } |
| } |
| |
| } // namespace devtools_goma |