如何在 iOS 上进行 base64 编码?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/392464/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I do base64 encoding on iOS?
提问by BlueDolphin
I'd like to do base64
encoding and decoding, but I could not find any support from the iPhone SDK
. How can I do base64
encoding and decoding with or without a library?
我想做base64
编码和解码,但我找不到 iPhone 的任何支持SDK
。如何base64
使用或不使用库进行编码和解码?
回答by Alex Reynolds
This is a good use case for Objective C categories.
这是 Objective C类别的一个很好的用例。
For Base64 encoding:
对于 Base64 编码:
#import <Foundation/NSString.h>
@interface NSString (NSStringAdditions)
+ (NSString *) base64StringFromData:(NSData *)data length:(int)length;
@end
-------------------------------------------
#import "NSStringAdditions.h"
static char base64EncodingTable[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
@implementation NSString (NSStringAdditions)
+ (NSString *) base64StringFromData: (NSData *)data length: (int)length {
unsigned long ixtext, lentext;
long ctremaining;
unsigned char input[3], output[4];
short i, charsonline = 0, ctcopy;
const unsigned char *raw;
NSMutableString *result;
lentext = [data length];
if (lentext < 1)
return @"";
result = [NSMutableString stringWithCapacity: lentext];
raw = [data bytes];
ixtext = 0;
while (true) {
ctremaining = lentext - ixtext;
if (ctremaining <= 0)
break;
for (i = 0; i < 3; i++) {
unsigned long ix = ixtext + i;
if (ix < lentext)
input[i] = raw[ix];
else
input[i] = 0;
}
output[0] = (input[0] & 0xFC) >> 2;
output[1] = ((input[0] & 0x03) << 4) | ((input[1] & 0xF0) >> 4);
output[2] = ((input[1] & 0x0F) << 2) | ((input[2] & 0xC0) >> 6);
output[3] = input[2] & 0x3F;
ctcopy = 4;
switch (ctremaining) {
case 1:
ctcopy = 2;
break;
case 2:
ctcopy = 3;
break;
}
for (i = 0; i < ctcopy; i++)
[result appendString: [NSString stringWithFormat: @"%c", base64EncodingTable[output[i]]]];
for (i = ctcopy; i < 4; i++)
[result appendString: @"="];
ixtext += 3;
charsonline += 4;
if ((length > 0) && (charsonline >= length))
charsonline = 0;
}
return result;
}
@end
For Base64 decoding:
对于 Base64 解码:
#import <Foundation/Foundation.h>
@class NSString;
@interface NSData (NSDataAdditions)
+ (NSData *) base64DataFromString:(NSString *)string;
@end
-------------------------------------------
#import "NSDataAdditions.h"
@implementation NSData (NSDataAdditions)
+ (NSData *)base64DataFromString: (NSString *)string
{
unsigned long ixtext, lentext;
unsigned char ch, inbuf[4], outbuf[3];
short i, ixinbuf;
Boolean flignore, flendtext = false;
const unsigned char *tempcstring;
NSMutableData *theData;
if (string == nil)
{
return [NSData data];
}
ixtext = 0;
tempcstring = (const unsigned char *)[string UTF8String];
lentext = [string length];
theData = [NSMutableData dataWithCapacity: lentext];
ixinbuf = 0;
while (true)
{
if (ixtext >= lentext)
{
break;
}
ch = tempcstring [ixtext++];
flignore = false;
if ((ch >= 'A') && (ch <= 'Z'))
{
ch = ch - 'A';
}
else if ((ch >= 'a') && (ch <= 'z'))
{
ch = ch - 'a' + 26;
}
else if ((ch >= '0') && (ch <= '9'))
{
ch = ch - '0' + 52;
}
else if (ch == '+')
{
ch = 62;
}
else if (ch == '=')
{
flendtext = true;
}
else if (ch == '/')
{
ch = 63;
}
else
{
flignore = true;
}
if (!flignore)
{
short ctcharsinbuf = 3;
Boolean flbreak = false;
if (flendtext)
{
if (ixinbuf == 0)
{
break;
}
if ((ixinbuf == 1) || (ixinbuf == 2))
{
ctcharsinbuf = 1;
}
else
{
ctcharsinbuf = 2;
}
ixinbuf = 3;
flbreak = true;
}
inbuf [ixinbuf++] = ch;
if (ixinbuf == 4)
{
ixinbuf = 0;
outbuf[0] = (inbuf[0] << 2) | ((inbuf[1] & 0x30) >> 4);
outbuf[1] = ((inbuf[1] & 0x0F) << 4) | ((inbuf[2] & 0x3C) >> 2);
outbuf[2] = ((inbuf[2] & 0x03) << 6) | (inbuf[3] & 0x3F);
for (i = 0; i < ctcharsinbuf; i++)
{
[theData appendBytes: &outbuf[i] length: 1];
}
}
if (flbreak)
{
break;
}
}
}
return theData;
}
@end
回答by Mike Ho
A really, really fast implementation which was ported (and modified/improved) from the PHP Core library into native Objective-C code is available in the QSStrings Classfrom the QSUtilities Library. I did a quick benchmark: a 5.3MB image (JPEG) file took < 50ms to encode, and about 140ms to decode.
这是从PHP Core库成原生Objective-C代码移植(和改性/改善)一个非常,非常快速的实施是在可用QSStrings类从QSUtilities库。我做了一个快速的基准测试:一个 5.3MB 的图像 (JPEG) 文件需要 < 50 毫秒来编码,大约 140 毫秒来解码。
The code for the entire library (including the Base64 Methods) are available on GitHub.
整个库的代码(包括 Base64 方法)可在GitHub上找到。
Or alternatively, if you want the code to justthe Base64 methods themselves, I've posted it here:
或者,如果您希望代码仅用于 Base64 方法本身,我已将其发布在此处:
First, you need the mapping tables:
首先,您需要映射表:
static const char _base64EncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const short _base64DecodingTable[256] = {
-2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
-2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
-2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
};
To Encode:
编码:
+ (NSString *)encodeBase64WithString:(NSString *)strData {
return [QSStrings encodeBase64WithData:[strData dataUsingEncoding:NSUTF8StringEncoding]];
}
+ (NSString *)encodeBase64WithData:(NSData *)objData {
const unsigned char * objRawData = [objData bytes];
char * objPointer;
char * strResult;
// Get the Raw Data length and ensure we actually have data
int intLength = [objData length];
if (intLength == 0) return nil;
// Setup the String-based Result placeholder and pointer within that placeholder
strResult = (char *)calloc((((intLength + 2) / 3) * 4) + 1, sizeof(char));
objPointer = strResult;
// Iterate through everything
while (intLength > 2) { // keep going until we have less than 24 bits
*objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
*objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
*objPointer++ = _base64EncodingTable[((objRawData[1] & 0x0f) << 2) + (objRawData[2] >> 6)];
*objPointer++ = _base64EncodingTable[objRawData[2] & 0x3f];
// we just handled 3 octets (24 bits) of data
objRawData += 3;
intLength -= 3;
}
// now deal with the tail end of things
if (intLength != 0) {
*objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
if (intLength > 1) {
*objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
*objPointer++ = _base64EncodingTable[(objRawData[1] & 0x0f) << 2];
*objPointer++ = '=';
} else {
*objPointer++ = _base64EncodingTable[(objRawData[0] & 0x03) << 4];
*objPointer++ = '=';
*objPointer++ = '=';
}
}
// Terminate the string-based result
*objPointer = '+ (NSData *)decodeBase64WithString:(NSString *)strBase64 {
const char *objPointer = [strBase64 cStringUsingEncoding:NSASCIIStringEncoding];
size_t intLength = strlen(objPointer);
int intCurrent;
int i = 0, j = 0, k;
unsigned char *objResult = calloc(intLength, sizeof(unsigned char));
// Run through the whole string, converting as we go
while ( ((intCurrent = *objPointer++) != 'NSString *string;
if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
string = [data base64EncodedStringWithOptions:kNilOptions]; // iOS 7+
} else {
string = [data base64Encoding]; // pre iOS7
}
') && (intLength-- > 0) ) {
if (intCurrent == '=') {
if (*objPointer != '=' && ((i % 4) == 1)) {// || (intLength > 0)) {
// the padding character is invalid at this point -- so this entire string is invalid
free(objResult);
return nil;
}
continue;
}
intCurrent = _base64DecodingTable[intCurrent];
if (intCurrent == -1) {
// we're at a whitespace -- simply skip over
continue;
} else if (intCurrent == -2) {
// we're at an invalid character
free(objResult);
return nil;
}
switch (i % 4) {
case 0:
objResult[j] = intCurrent << 2;
break;
case 1:
objResult[j++] |= intCurrent >> 4;
objResult[j] = (intCurrent & 0x0f) << 4;
break;
case 2:
objResult[j++] |= intCurrent >>2;
objResult[j] = (intCurrent & 0x03) << 6;
break;
case 3:
objResult[j++] |= intCurrent;
break;
}
i++;
}
// mop things up if we ended on a boundary
k = j;
if (intCurrent == '=') {
switch (i % 4) {
case 1:
// Invalid state
free(objResult);
return nil;
case 2:
k++;
// flow through
case 3:
objResult[k] = 0;
}
}
// Cleanup and setup the return NSData
NSData * objData = [[[NSData alloc] initWithBytes:objResult length:j] autorelease];
free(objResult);
return objData;
}
';
// Create result NSString object
NSString *base64String = [NSString stringWithCString:strResult encoding:NSASCIIStringEncoding];
// Free memory
free(strResult);
return base64String;
}
To Decode:
解码:
NSData *data;
if ([NSData instancesRespondToSelector:@selector(initWithBase64EncodedString:options:)]) {
data = [[NSData alloc] initWithBase64EncodedString:string options:kNilOptions]; // iOS 7+
} else {
data = [[NSData alloc] initWithBase64Encoding:string]; // pre iOS7
}
回答by Rob
Historically we would have directed you to one of the many third-party base 64 libraries (as discussed in the other answers) for converting from binary data to base 64 string (and back), but iOS 7 now has native base 64 encoding(and exposes the previously private iOS 4 methods, in case you need to support earlier versions of iOS).
从历史上看,我们会引导您使用许多第三方 base 64 库之一(如其他答案中所述)以将二进制数据转换为 base 64 字符串(并返回),但 iOS 7 现在具有原生 base 64 编码(和公开以前私有的 iOS 4 方法,以防您需要支持早期版本的 iOS)。
Thus to convert NSData
to NSString
base 64 representation you can use base64EncodedStringWithOptions
. If you have to support iOS versions prior to 7.0 as well, you can do:
因此,要转换NSData
为NSString
base 64 表示,您可以使用base64EncodedStringWithOptions
. 如果您还必须支持 7.0 之前的 iOS 版本,您可以执行以下操作:
NSString *string = [data base64EncodedStringWithOptions:kNilOptions];
And to convert base 64 NSString
back to NSData
you can use initWithBase64EncodedString
. Likewise, if you need to support iOS versions prior to 7.0, you can do:
并将 base 64 转换NSString
回NSData
您可以使用initWithBase64EncodedString
. 同样,如果您需要支持 7.0 之前的 iOS 版本,您可以执行以下操作:
NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:kNilOptions];
Obviously, if you don't need backward compatibility with iOS versions prior to 7.0, it's even easier, just use base64EncodedStringWithOptions
or initWithBase64EncodedString
, respectively, and don't bother with the run-time check for earlier iOS versions. In fact, if you use the above code when your minimum target is iOS 7 or greater, you'll actually get a compiler warning about the deprecated methods. So, in iOS 7 and greater, you would simply convert to base 64 string with:
显然,如果您不需要与 7.0 之前的 iOS 版本向后兼容,那就更简单了,只需分别使用base64EncodedStringWithOptions
或initWithBase64EncodedString
,并且不要打扰早期iOS 版本的运行时检查。事实上,如果您在最低目标是 iOS 7 或更高版本时使用上述代码,您实际上会收到有关已弃用方法的编译器警告。因此,在 iOS 7 及更高版本中,您只需使用以下命令转换为 base 64 字符串:
+ (NSString *) base64StringFromData: (NSData *)data length: (int)length {
int lentext = [data length];
if (lentext < 1) return @"";
char *outbuf = malloc(lentext*4/3+4); // add 4 to be sure
if ( !outbuf ) return nil;
const unsigned char *raw = [data bytes];
int inp = 0;
int outp = 0;
int do_now = lentext - (lentext%3);
for ( outp = 0, inp = 0; inp < do_now; inp += 3 )
{
outbuf[outp++] = base64EncodingTable[(raw[inp] & 0xFC) >> 2];
outbuf[outp++] = base64EncodingTable[((raw[inp] & 0x03) << 4) | ((raw[inp+1] & 0xF0) >> 4)];
outbuf[outp++] = base64EncodingTable[((raw[inp+1] & 0x0F) << 2) | ((raw[inp+2] & 0xC0) >> 6)];
outbuf[outp++] = base64EncodingTable[raw[inp+2] & 0x3F];
}
if ( do_now < lentext )
{
char tmpbuf[2] = {0,0};
int left = lentext%3;
for ( int i=0; i < left; i++ )
{
tmpbuf[i] = raw[do_now+i];
}
raw = tmpbuf;
outbuf[outp++] = base64EncodingTable[(raw[inp] & 0xFC) >> 2];
outbuf[outp++] = base64EncodingTable[((raw[inp] & 0x03) << 4) | ((raw[inp+1] & 0xF0) >> 4)];
if ( left == 2 ) outbuf[outp++] = base64EncodingTable[((raw[inp+1] & 0x0F) << 2) | ((raw[inp+2] & 0xC0) >> 6)];
}
NSString *ret = [[[NSString alloc] initWithBytes:outbuf length:outp encoding:NSASCIIStringEncoding] autorelease];
free(outbuf);
return ret;
}
and back again with:
并再次返回:
outbuf[outp++] = base64EncodingTable1[(raw[inp] & 0xFC)];
outbuf[outp++] = base64EncodingTable2[(raw[inp] & 0x03) | (raw[inp+1] & 0xF0)];
outbuf[outp++] = base64EncodingTable3[(raw[inp+1] & 0x0F) | (raw[inp+2] & 0xC0)];
outbuf[outp++] = base64EncodingTable4[raw[inp+2] & 0x3F];
回答by quellish
iOS includes built in support for base64 encoding and decoding. If you look at resolv.h
you should see the two functions b64_ntop
and b64_pton
. The Square SocketRocketlibrary provides a reasonable example of how to use these functions from objective-c.
iOS 包括对 base64 编码和解码的内置支持。如果您看一下,resolv.h
您应该会看到两个函数b64_ntop
和b64_pton
。Square SocketRocket库提供了一个合理的示例,说明如何使用 Objective-c 中的这些函数。
These functions are pretty well tested and reliable - unlike many of the implementations you may find in random internet postings.
Don't forget to link against libresolv.dylib
.
这些功能经过了很好的测试和可靠 - 与您可能在随机互联网发布中找到的许多实现不同。不要忘记链接到libresolv.dylib
.
回答by mvds
Since this seems to be the number one google hit on base64 encoding and iphone, I felt like sharing my experience with the code snippet above.
由于这似乎是谷歌在 base64 编码和 iphone 上的排名第一,我想与上面的代码片段分享我的经验。
It works, but it is extremely slow. A benchmark on a random image (0.4 mb) took 37 seconds on native iphone. The main reason is probably all the OOP magic - single char NSStrings etc, which are only autoreleased after the encoding is done.
它有效,但速度非常慢。对随机图像 (0.4 mb) 的基准测试在原生 iphone 上耗时 37 秒。主要原因可能是所有的 OOP 魔法——单字符 NSStrings 等,它们只有在编码完成后才会自动释放。
Another suggestion posted here (ab)uses the openssl library, which feels like overkill as well.
此处发布的另一个建议 (ab) 使用了 openssl 库,这也感觉有点矫枉过正。
The code below takes 70 ms - that's a 500 times speedup. This only does base64 encoding (decoding will follow as soon as I encounter it)
下面的代码需要 70 毫秒 - 这是 500 倍的加速。这只进行base64编码(我一遇到它就会解码)
raw = tmpbuf;
inp = 0;
outbuf[outp++] = base64EncodingTable[(raw[inp] & 0xFC) >> 2];
outbuf[outp++] = base64EncodingTable[((raw[inp] & 0x03) << 4) | ((raw[inp+1] & 0xF0) >> 4)];
if ( left == 2 ) outbuf[outp++] = base64EncodingTable[((raw[inp+1] & 0x0F) << 2) | ((raw[inp+2] & 0xC0) >> 6)];
else outbuf[outp++] = '=';
outbuf[outp++] = '=';
I left out the line-cutting since I didn't need it, but it's trivial to add.
由于我不需要它,所以我省略了线切割,但添加起来很简单。
For those who are interested in optimizing: the goal is to minimize what happens in the main loop. Therefore all logic to deal with the last 3 bytes is treated outside the loop.
对于那些对优化感兴趣的人:目标是最小化主循环中发生的事情。因此,处理最后 3 个字节的所有逻辑都在循环之外处理。
Also, try to work on data in-place, without additional copying to/from buffers. And reduce any arithmetic to the bare minimum.
此外,尝试就地处理数据,而无需向/从缓冲区进行额外的复制。并将任何算术减少到最低限度。
Observe that the bits that are put together to look up an entry in the table, would not overlap when they were to be orred together without shifting. A major improvement could therefore be to use 4 separate 256 byte lookup tables and eliminate the shifts, like this:
观察到放在一起查找表中条目的位在不移位的情况下被组合在一起时不会重叠。因此,一个主要的改进可能是使用 4 个单独的 256 字节查找表并消除移位,如下所示:
[data base64Encoding]; //iOS < 7.0
[data base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength]; //iOS >= 7.0
Of course you could take it a whole lot further, but that's beyond the scope here.
当然,你可以更进一步,但这超出了这里的范围。
回答by user335742
In mvds's excellent improvement, there are two problems. Change code to this:
在mvds的出色改进中,存在两个问题。将代码改成这样:
unsigned char tmpbuf[3] = {0,0,0};
回答by Nagaraj
Better solution :
更好的解决方案:
There is a built in function in NSData
NSData 中有一个内置函数
while ( outp%4 ) outbuf[outp++] = '=';
回答by AlexeyVMP
Under iOS8 and later use - (NSString *)base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)options
of NSData
在 iOS8 及更高版本下使用- (NSString *)base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)options
NSData
回答by mvds
Glad people liked it. The end-game was a little flawed I must admit. Besides rightly setting inp=0 you should either also increase tmpbuf's size to 3, like
很高兴人们喜欢它。我必须承认,最终游戏有点缺陷。除了正确设置 inp=0 之外,您还应该将 tmpbuf 的大小增加到 3,例如
#import "NSDataAdditions.h"
@implementation NSData (NSDataAdditions)
+ (NSData *) base64DataFromString: (NSString *)string {
unsigned long ixtext, lentext;
unsigned char ch, input[4], output[3];
short i, ixinput;
Boolean flignore, flendtext = false;
const char *temporary;
NSMutableData *result;
if (!string)
return [NSData data];
ixtext = 0;
temporary = [string UTF8String];
lentext = [string length];
result = [NSMutableData dataWithCapacity: lentext];
ixinput = 0;
while (true) {
if (ixtext >= lentext)
break;
ch = temporary[ixtext++];
flignore = false;
if ((ch >= 'A') && (ch <= 'Z'))
ch = ch - 'A';
else if ((ch >= 'a') && (ch <= 'z'))
ch = ch - 'a' + 26;
else if ((ch >= '0') && (ch <= '9'))
ch = ch - '0' + 52;
else if (ch == '+')
ch = 62;
else if (ch == '=')
flendtext = true;
else if (ch == '/')
ch = 63;
else
flignore = true;
if (!flignore) {
short ctcharsinput = 3;
Boolean flbreak = false;
if (flendtext) {
if (ixinput == 0)
break;
if ((ixinput == 1) || (ixinput == 2))
ctcharsinput = 1;
else
ctcharsinput = 2;
ixinput = 3;
flbreak = true;
}
input[ixinput++] = ch;
if (ixinput == 4){
ixinput = 0;
output[0] = (input[0] << 2) | ((input[1] & 0x30) >> 4);
output[1] = ((input[1] & 0x0F) << 4) | ((input[2] & 0x3C) >> 2);
output[2] = ((input[2] & 0x03) << 6) | (input[3] & 0x3F);
for (i = 0; i < ctcharsinput; i++)
[result appendBytes: &output[i] length: 1];
}
if (flbreak)
break;
}
}
return result;
}
@end
orleave out the orring of raw[inp+2]; if we would have a raw[inp+2] != 0 for this chunk we would still be in the loop of course...
或省略 raw[inp+2] 的 orring;如果我们有一个 raw[inp+2] != 0 对于这个块,我们当然仍然在循环中......
Either way works, you might consider keeping the final table lookup block identical to the one in the loop for clarity. In the final version I used I did
无论哪种方式都有效,为了清晰起见,您可能会考虑将最终表查找块与循环中的块保持相同。在我使用的最终版本中,我做了
##代码##To add the ==
添加 ==
Sorry I didn't check RFC's and stuff, should have done a better job!
抱歉,我没有检查 RFC 和其他东西,应该做得更好!