resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / cmCryptoHash.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmCryptoHash.h"
4
5 #include <cm/memory>
6
7 #include <cm3p/kwiml/int.h>
8 #include <cm3p/rhash.h>
9
10 #include "cmsys/FStream.hxx"
11
12 static unsigned int const cmCryptoHashAlgoToId[] = {
13   /* clang-format needs this comment to break after the opening brace */
14   RHASH_MD5,      //
15   RHASH_SHA1,     //
16   RHASH_SHA224,   //
17   RHASH_SHA256,   //
18   RHASH_SHA384,   //
19   RHASH_SHA512,   //
20   RHASH_SHA3_224, //
21   RHASH_SHA3_256, //
22   RHASH_SHA3_384, //
23   RHASH_SHA3_512
24 };
25
26 static int cmCryptoHash_rhash_library_initialized;
27
28 static rhash cmCryptoHash_rhash_init(unsigned int id)
29 {
30   if (!cmCryptoHash_rhash_library_initialized) {
31     cmCryptoHash_rhash_library_initialized = 1;
32     rhash_library_init();
33   }
34   return rhash_init(id);
35 }
36
37 cmCryptoHash::cmCryptoHash(Algo algo)
38   : Id(cmCryptoHashAlgoToId[algo])
39   , CTX(cmCryptoHash_rhash_init(this->Id))
40 {
41 }
42
43 cmCryptoHash::~cmCryptoHash()
44 {
45   rhash_free(this->CTX);
46 }
47
48 std::unique_ptr<cmCryptoHash> cmCryptoHash::New(cm::string_view algo)
49 {
50   if (algo == "MD5") {
51     return cm::make_unique<cmCryptoHash>(AlgoMD5);
52   }
53   if (algo == "SHA1") {
54     return cm::make_unique<cmCryptoHash>(AlgoSHA1);
55   }
56   if (algo == "SHA224") {
57     return cm::make_unique<cmCryptoHash>(AlgoSHA224);
58   }
59   if (algo == "SHA256") {
60     return cm::make_unique<cmCryptoHash>(AlgoSHA256);
61   }
62   if (algo == "SHA384") {
63     return cm::make_unique<cmCryptoHash>(AlgoSHA384);
64   }
65   if (algo == "SHA512") {
66     return cm::make_unique<cmCryptoHash>(AlgoSHA512);
67   }
68   if (algo == "SHA3_224") {
69     return cm::make_unique<cmCryptoHash>(AlgoSHA3_224);
70   }
71   if (algo == "SHA3_256") {
72     return cm::make_unique<cmCryptoHash>(AlgoSHA3_256);
73   }
74   if (algo == "SHA3_384") {
75     return cm::make_unique<cmCryptoHash>(AlgoSHA3_384);
76   }
77   if (algo == "SHA3_512") {
78     return cm::make_unique<cmCryptoHash>(AlgoSHA3_512);
79   }
80   return std::unique_ptr<cmCryptoHash>(nullptr);
81 }
82
83 bool cmCryptoHash::IntFromHexDigit(char input, char& output)
84 {
85   if (input >= '0' && input <= '9') {
86     output = static_cast<char>(input - '0');
87     return true;
88   }
89   if (input >= 'a' && input <= 'f') {
90     output = static_cast<char>(input - 'a' + 0xA);
91     return true;
92   }
93   if (input >= 'A' && input <= 'F') {
94     output = static_cast<char>(input - 'A' + 0xA);
95     return true;
96   }
97   return false;
98 }
99
100 std::string cmCryptoHash::ByteHashToString(
101   const std::vector<unsigned char>& hash)
102 {
103   // Map from 4-bit index to hexadecimal representation.
104   static char const hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
105                                 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
106
107   std::string res;
108   res.reserve(hash.size() * 2);
109   for (unsigned char v : hash) {
110     res.push_back(hex[v >> 4]);
111     res.push_back(hex[v & 0xF]);
112   }
113   return res;
114 }
115
116 std::vector<unsigned char> cmCryptoHash::ByteHashString(cm::string_view input)
117 {
118   this->Initialize();
119   this->Append(input);
120   return this->Finalize();
121 }
122
123 std::vector<unsigned char> cmCryptoHash::ByteHashFile(const std::string& file)
124 {
125   cmsys::ifstream fin(file.c_str(), std::ios::in | std::ios::binary);
126   if (fin) {
127     this->Initialize();
128     {
129       // Should be efficient enough on most system:
130       KWIML_INT_uint64_t buffer[512];
131       char* buffer_c = reinterpret_cast<char*>(buffer);
132       unsigned char const* buffer_uc =
133         reinterpret_cast<unsigned char const*>(buffer);
134       // This copy loop is very sensitive on certain platforms with
135       // slightly broken stream libraries (like HPUX).  Normally, it is
136       // incorrect to not check the error condition on the fin.read()
137       // before using the data, but the fin.gcount() will be zero if an
138       // error occurred.  Therefore, the loop should be safe everywhere.
139       while (fin) {
140         fin.read(buffer_c, sizeof(buffer));
141         if (int gcount = static_cast<int>(fin.gcount())) {
142           this->Append(buffer_uc, gcount);
143         }
144       }
145     }
146     if (fin.eof()) {
147       // Success
148       return this->Finalize();
149     }
150     // Finalize anyway
151     this->Finalize();
152   }
153   // Return without success
154   return std::vector<unsigned char>();
155 }
156
157 std::string cmCryptoHash::HashString(cm::string_view input)
158 {
159   return ByteHashToString(this->ByteHashString(input));
160 }
161
162 std::string cmCryptoHash::HashFile(const std::string& file)
163 {
164   return ByteHashToString(this->ByteHashFile(file));
165 }
166
167 void cmCryptoHash::Initialize()
168 {
169   rhash_reset(this->CTX);
170 }
171
172 void cmCryptoHash::Append(void const* buf, size_t sz)
173 {
174   rhash_update(this->CTX, buf, sz);
175 }
176
177 void cmCryptoHash::Append(cm::string_view input)
178 {
179   rhash_update(this->CTX, input.data(), input.size());
180 }
181
182 std::vector<unsigned char> cmCryptoHash::Finalize()
183 {
184   std::vector<unsigned char> hash(rhash_get_digest_size(this->Id), 0);
185   rhash_final(this->CTX, hash.data());
186   return hash;
187 }
188
189 std::string cmCryptoHash::FinalizeHex()
190 {
191   return cmCryptoHash::ByteHashToString(this->Finalize());
192 }