|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "Base64.h" |
|
7 |
|
8 #include "nsIInputStream.h" |
|
9 #include "nsString.h" |
|
10 |
|
11 #include "plbase64.h" |
|
12 |
|
13 namespace { |
|
14 |
|
15 // BEGIN base64 encode code copied and modified from NSPR |
|
16 const unsigned char *base = (unsigned char *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
|
17 |
|
18 template <typename T> |
|
19 static void |
|
20 Encode3to4(const unsigned char *src, T *dest) |
|
21 { |
|
22 uint32_t b32 = (uint32_t)0; |
|
23 int i, j = 18; |
|
24 |
|
25 for( i = 0; i < 3; i++ ) |
|
26 { |
|
27 b32 <<= 8; |
|
28 b32 |= (uint32_t)src[i]; |
|
29 } |
|
30 |
|
31 for( i = 0; i < 4; i++ ) |
|
32 { |
|
33 dest[i] = base[ (uint32_t)((b32>>j) & 0x3F) ]; |
|
34 j -= 6; |
|
35 } |
|
36 } |
|
37 |
|
38 template <typename T> |
|
39 static void |
|
40 Encode2to4(const unsigned char *src, T *dest) |
|
41 { |
|
42 dest[0] = base[ (uint32_t)((src[0]>>2) & 0x3F) ]; |
|
43 dest[1] = base[ (uint32_t)(((src[0] & 0x03) << 4) | ((src[1] >> 4) & 0x0F)) ]; |
|
44 dest[2] = base[ (uint32_t)((src[1] & 0x0F) << 2) ]; |
|
45 dest[3] = (unsigned char)'='; |
|
46 } |
|
47 |
|
48 template <typename T> |
|
49 static void |
|
50 Encode1to4(const unsigned char *src, T *dest) |
|
51 { |
|
52 dest[0] = base[ (uint32_t)((src[0]>>2) & 0x3F) ]; |
|
53 dest[1] = base[ (uint32_t)((src[0] & 0x03) << 4) ]; |
|
54 dest[2] = (unsigned char)'='; |
|
55 dest[3] = (unsigned char)'='; |
|
56 } |
|
57 |
|
58 template <typename T> |
|
59 static void |
|
60 Encode(const unsigned char *src, uint32_t srclen, T *dest) |
|
61 { |
|
62 while( srclen >= 3 ) |
|
63 { |
|
64 Encode3to4(src, dest); |
|
65 src += 3; |
|
66 dest += 4; |
|
67 srclen -= 3; |
|
68 } |
|
69 |
|
70 switch( srclen ) |
|
71 { |
|
72 case 2: |
|
73 Encode2to4(src, dest); |
|
74 break; |
|
75 case 1: |
|
76 Encode1to4(src, dest); |
|
77 break; |
|
78 case 0: |
|
79 break; |
|
80 default: |
|
81 NS_NOTREACHED("coding error"); |
|
82 } |
|
83 } |
|
84 |
|
85 // END base64 encode code copied and modified from NSPR. |
|
86 |
|
87 template <typename T> |
|
88 struct EncodeInputStream_State { |
|
89 unsigned char c[3]; |
|
90 uint8_t charsOnStack; |
|
91 typename T::char_type* buffer; |
|
92 }; |
|
93 |
|
94 template <typename T> |
|
95 NS_METHOD |
|
96 EncodeInputStream_Encoder(nsIInputStream *aStream, |
|
97 void *aClosure, |
|
98 const char *aFromSegment, |
|
99 uint32_t aToOffset, |
|
100 uint32_t aCount, |
|
101 uint32_t *aWriteCount) |
|
102 { |
|
103 NS_ASSERTION(aCount > 0, "Er, what?"); |
|
104 |
|
105 EncodeInputStream_State<T>* state = |
|
106 static_cast<EncodeInputStream_State<T>*>(aClosure); |
|
107 |
|
108 // If we have any data left from last time, encode it now. |
|
109 uint32_t countRemaining = aCount; |
|
110 const unsigned char *src = (const unsigned char*)aFromSegment; |
|
111 if (state->charsOnStack) { |
|
112 unsigned char firstSet[4]; |
|
113 if (state->charsOnStack == 1) { |
|
114 firstSet[0] = state->c[0]; |
|
115 firstSet[1] = src[0]; |
|
116 firstSet[2] = (countRemaining > 1) ? src[1] : '\0'; |
|
117 firstSet[3] = '\0'; |
|
118 } else /* state->charsOnStack == 2 */ { |
|
119 firstSet[0] = state->c[0]; |
|
120 firstSet[1] = state->c[1]; |
|
121 firstSet[2] = src[0]; |
|
122 firstSet[3] = '\0'; |
|
123 } |
|
124 Encode(firstSet, 3, state->buffer); |
|
125 state->buffer += 4; |
|
126 countRemaining -= (3 - state->charsOnStack); |
|
127 src += (3 - state->charsOnStack); |
|
128 state->charsOnStack = 0; |
|
129 } |
|
130 |
|
131 // Encode the bulk of the |
|
132 uint32_t encodeLength = countRemaining - countRemaining % 3; |
|
133 NS_ABORT_IF_FALSE(encodeLength % 3 == 0, |
|
134 "Should have an exact number of triplets!"); |
|
135 Encode(src, encodeLength, state->buffer); |
|
136 state->buffer += (encodeLength / 3) * 4; |
|
137 src += encodeLength; |
|
138 countRemaining -= encodeLength; |
|
139 |
|
140 // We must consume all data, so if there's some data left stash it |
|
141 *aWriteCount = aCount; |
|
142 |
|
143 if (countRemaining) { |
|
144 // We should never have a full triplet left at this point. |
|
145 NS_ABORT_IF_FALSE(countRemaining < 3, "We should have encoded more!"); |
|
146 state->c[0] = src[0]; |
|
147 state->c[1] = (countRemaining == 2) ? src[1] : '\0'; |
|
148 state->charsOnStack = countRemaining; |
|
149 } |
|
150 |
|
151 return NS_OK; |
|
152 } |
|
153 |
|
154 template <typename T> |
|
155 nsresult |
|
156 EncodeInputStream(nsIInputStream *aInputStream, |
|
157 T &aDest, |
|
158 uint32_t aCount, |
|
159 uint32_t aOffset) |
|
160 { |
|
161 nsresult rv; |
|
162 uint64_t count64 = aCount; |
|
163 |
|
164 if (!aCount) { |
|
165 rv = aInputStream->Available(&count64); |
|
166 if (NS_WARN_IF(NS_FAILED(rv))) |
|
167 return rv; |
|
168 // if count64 is over 4GB, it will be failed at the below condition, |
|
169 // then will return NS_ERROR_OUT_OF_MEMORY |
|
170 aCount = (uint32_t)count64; |
|
171 } |
|
172 |
|
173 uint64_t countlong = |
|
174 (count64 + 2) / 3 * 4; // +2 due to integer math. |
|
175 if (countlong + aOffset > UINT32_MAX) |
|
176 return NS_ERROR_OUT_OF_MEMORY; |
|
177 |
|
178 uint32_t count = uint32_t(countlong); |
|
179 |
|
180 aDest.SetLength(count + aOffset); |
|
181 if (aDest.Length() != count + aOffset) |
|
182 return NS_ERROR_OUT_OF_MEMORY; |
|
183 |
|
184 EncodeInputStream_State<T> state; |
|
185 state.charsOnStack = 0; |
|
186 state.c[2] = '\0'; |
|
187 state.buffer = aOffset + aDest.BeginWriting(); |
|
188 |
|
189 while (1) { |
|
190 uint32_t read = 0; |
|
191 |
|
192 rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>, |
|
193 (void*)&state, |
|
194 aCount, |
|
195 &read); |
|
196 if (NS_FAILED(rv)) { |
|
197 if (rv == NS_BASE_STREAM_WOULD_BLOCK) |
|
198 NS_RUNTIMEABORT("Not implemented for async streams!"); |
|
199 if (rv == NS_ERROR_NOT_IMPLEMENTED) |
|
200 NS_RUNTIMEABORT("Requires a stream that implements ReadSegments!"); |
|
201 return rv; |
|
202 } |
|
203 |
|
204 if (!read) |
|
205 break; |
|
206 } |
|
207 |
|
208 // Finish encoding if anything is left |
|
209 if (state.charsOnStack) |
|
210 Encode(state.c, state.charsOnStack, state.buffer); |
|
211 |
|
212 if (aDest.Length()) |
|
213 // May belong to an nsCString with an unallocated buffer, so only null |
|
214 // terminate if there is a need to. |
|
215 *aDest.EndWriting() = '\0'; |
|
216 |
|
217 return NS_OK; |
|
218 } |
|
219 |
|
220 } // namespace (anonymous) |
|
221 |
|
222 namespace mozilla { |
|
223 |
|
224 nsresult |
|
225 Base64EncodeInputStream(nsIInputStream *aInputStream, |
|
226 nsACString &aDest, |
|
227 uint32_t aCount, |
|
228 uint32_t aOffset) |
|
229 { |
|
230 return EncodeInputStream<nsACString>(aInputStream, aDest, aCount, aOffset); |
|
231 } |
|
232 |
|
233 nsresult |
|
234 Base64EncodeInputStream(nsIInputStream *aInputStream, |
|
235 nsAString &aDest, |
|
236 uint32_t aCount, |
|
237 uint32_t aOffset) |
|
238 { |
|
239 return EncodeInputStream<nsAString>(aInputStream, aDest, aCount, aOffset); |
|
240 } |
|
241 |
|
242 nsresult |
|
243 Base64Encode(const nsACString &aBinaryData, nsACString &aString) |
|
244 { |
|
245 // Check for overflow. |
|
246 if (aBinaryData.Length() > (UINT32_MAX / 4) * 3) { |
|
247 return NS_ERROR_FAILURE; |
|
248 } |
|
249 |
|
250 // Don't ask PR_Base64Encode to encode empty strings |
|
251 if (aBinaryData.IsEmpty()) { |
|
252 aString.Truncate(); |
|
253 return NS_OK; |
|
254 } |
|
255 |
|
256 uint32_t stringLen = ((aBinaryData.Length() + 2) / 3) * 4; |
|
257 |
|
258 char *buffer; |
|
259 |
|
260 // Add one byte for null termination. |
|
261 if (aString.SetCapacity(stringLen + 1, fallible_t()) && |
|
262 (buffer = aString.BeginWriting()) && |
|
263 PL_Base64Encode(aBinaryData.BeginReading(), aBinaryData.Length(), buffer)) { |
|
264 // PL_Base64Encode doesn't null terminate the buffer for us when we pass |
|
265 // the buffer in. Do that manually. |
|
266 buffer[stringLen] = '\0'; |
|
267 |
|
268 aString.SetLength(stringLen); |
|
269 return NS_OK; |
|
270 } |
|
271 |
|
272 aString.Truncate(); |
|
273 return NS_ERROR_INVALID_ARG; |
|
274 } |
|
275 |
|
276 nsresult |
|
277 Base64Encode(const nsAString &aString, nsAString &aBinaryData) |
|
278 { |
|
279 NS_LossyConvertUTF16toASCII string(aString); |
|
280 nsAutoCString binaryData; |
|
281 |
|
282 nsresult rv = Base64Encode(string, binaryData); |
|
283 if (NS_SUCCEEDED(rv)) { |
|
284 CopyASCIItoUTF16(binaryData, aBinaryData); |
|
285 } else { |
|
286 aBinaryData.Truncate(); |
|
287 } |
|
288 |
|
289 return rv; |
|
290 } |
|
291 |
|
292 nsresult |
|
293 Base64Decode(const nsACString &aString, nsACString &aBinaryData) |
|
294 { |
|
295 // Check for overflow. |
|
296 if (aString.Length() > UINT32_MAX / 3) { |
|
297 return NS_ERROR_FAILURE; |
|
298 } |
|
299 |
|
300 // Don't ask PR_Base64Decode to decode the empty string |
|
301 if (aString.IsEmpty()) { |
|
302 aBinaryData.Truncate(); |
|
303 return NS_OK; |
|
304 } |
|
305 |
|
306 uint32_t binaryDataLen = ((aString.Length() * 3) / 4); |
|
307 |
|
308 char *buffer; |
|
309 |
|
310 // Add one byte for null termination. |
|
311 if (aBinaryData.SetCapacity(binaryDataLen + 1, fallible_t()) && |
|
312 (buffer = aBinaryData.BeginWriting()) && |
|
313 PL_Base64Decode(aString.BeginReading(), aString.Length(), buffer)) { |
|
314 // PL_Base64Decode doesn't null terminate the buffer for us when we pass |
|
315 // the buffer in. Do that manually, taking into account the number of '=' |
|
316 // characters we were passed. |
|
317 if (!aString.IsEmpty() && aString[aString.Length() - 1] == '=') { |
|
318 if (aString.Length() > 1 && aString[aString.Length() - 2] == '=') { |
|
319 binaryDataLen -= 2; |
|
320 } else { |
|
321 binaryDataLen -= 1; |
|
322 } |
|
323 } |
|
324 buffer[binaryDataLen] = '\0'; |
|
325 |
|
326 aBinaryData.SetLength(binaryDataLen); |
|
327 return NS_OK; |
|
328 } |
|
329 |
|
330 aBinaryData.Truncate(); |
|
331 return NS_ERROR_INVALID_ARG; |
|
332 } |
|
333 |
|
334 nsresult |
|
335 Base64Decode(const nsAString &aBinaryData, nsAString &aString) |
|
336 { |
|
337 NS_LossyConvertUTF16toASCII binaryData(aBinaryData); |
|
338 nsAutoCString string; |
|
339 |
|
340 nsresult rv = Base64Decode(binaryData, string); |
|
341 if (NS_SUCCEEDED(rv)) { |
|
342 CopyASCIItoUTF16(string, aString); |
|
343 } else { |
|
344 aString.Truncate(); |
|
345 } |
|
346 |
|
347 return rv; |
|
348 } |
|
349 |
|
350 } // namespace mozilla |