全面深度解析iOS端URL编码和解码过程

一、URL含义

1、URL定义

URL 是Uniform Resource Locator 的缩写,统一资源定位符,对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。基本URL包含模式(或称协议)、服务器名称(或IP地址)、路径和文件名、参数,如“协议://授权/路径查询?参数”。

URL 与 URI
很多人会混淆这两个名词。
URL:(Uniform/Universal Resource Locator 的缩写,统一资源定位符)。
URI:(Uniform Resource Identifier 的缩写,统一资源标识符)。
对于URI, 具体的结构如下:

 foo://example.com:8042/over/there?name=ferret#nose

   \_/ \______________/ \________/\_________/ \__/

    |         |              |         |        |

  scheme     authority      path      query   fragment

URI 属于 URL 更低层次的抽象,一种字符串文本标准。URL 是 URI 的一个子集。
URI 表示请求服务器的路径,定义这么一个资源。而 URL 同时说明要如何访问这个资源(http://)。

URL 百度百科

2、URL字符编码表

1、URL 编码 - 从 %00 到 %ff
2、HTML特殊字符编码对照表

二、URL 编码

1、为什么要编码转义

推荐阅读:字符编码:ASCII、Unicode 和 UTF-8 的区别

世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号,不同的编码方式,解码出来就是乱码,造成数据传输和阅读的极大障碍。互联网的来临,必须要统一字符编码,Unicode(统一码、万国码、单一码)作为计算机科学领域里的一项业界标准应运而生。Unicode 是互联网统一的符号集,只规定了符号唯一的二进制代码值,却没有规定这个二进制代码应该如何存储。UTF-8是一种针对Unicode的可变长度字符编码,UTF-8用1到4个字节编码Unicode字符,在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。
注意:UTF-8 是 Unicode 的实现方式之一。
如 中 字:
Unicode码值: \u4e2d
URL编码(UTF-8): %e4%b8%ad

2、URL编码规则

Url编码通常也被称为百分号编码,编码方式非常简单,使用%百分号加上两位的字符——0123456789ABCDEF——代表一个字节的十六进制形式。Url编码默认使用的字符集是US-ASCII。例如a在US-ASCII码中对应的字节是0x61,那么Url编码之后得到的就是%61,我们在地址栏上输入 http://g.cn/search?q=%61%62%63 ,实际上就等同于在google上搜索abc了。又如@符号在ASCII字符集中对应的字节为0x40,经过Url编码之后得到的是%40。

对于非ASCII字符,需要使用ASCII字符集的超集进行编码得到相应的字节,然后对每个字节执行百分号编码。对于Unicode字符,RFC文档建议使用utf-8对其进行编码得到相应的字节,然后对每个字节执行百分号编码。如"中文"使用UTF-8字符集得到的字节为0xE4 0xB8 0xAD 0xE6 0x96 0x87,经过Url编码之后得到"%E4%B8%AD%E6%96%87"。

3、URL不需要编码的字符

HTTP URL 使用的RFC3986编码规范,RFC3986文档规定,URL中只允许包含以下四种:
1、英文字母(a-z A-Z)
2、数字(0-9)
3、-_.~ 4个特殊字符
4、所有保留字符,RFC3986中指定了以下字符为保留字符(英文字符):
! * ' ( ) ; : @ & = + $ , / ? # [ ]
5、编码标记符号 %

URL 编码使用 "%" 其后跟随两位的十六进制数来替换非 ASCII 的字符,中文是三个编码组合。十六进制格式用于在浏览器和插件中显示非标准的字母和字符。

4、URL需要编码的字符

Url编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。

4.1、非URL定义的字符

不能在 URL 中包含任何非 ASCII 字符,如中文字符、希腊文字符,拉丁文字符等。如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成乱码问题。

4.2、会引起歧义的保留字符

URL 拼接参数或路径设置时,拼接的普通字符串中含有保留字符,会引起歧义的情况。URL 参数字符串中使用 key=value 这样的键值对形式来传参,键值对之间以 & 符号分隔,如宝洁公司的简称为P&G,假设需要当做参数去传递,name=P&G&t=1450591802326,因为参数中多了一个&势必会造成接收 URL 的服务器解析错误,因此必须将引起歧义的 & 符号进行转义编码。

部分保留字符及其URL编码

字符 用法描述 编码
+ 表示空格(在URL中不能使用空格) %2B
空格 URL中的空格可以用+号或者编码 %20
/ 分隔目录和子目录 %2F
? 分隔实际的URL和参数 %3F
# 表示书签或锚点 %23
& URL中指定的参数间的分隔符 %26
= URL中指定的参数的值 %3D
% 百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码 %25

如果需要在URL中用到特殊字符或中文字符,需要将这些特殊字符换成相应的十六进制的值。

三、iOS端URL具体编码处理

1、URL编码和解码是成对

URL编码和解码是一个可逆的过程,编码和解码的逻辑是翻转对应的。
成对有两层含义:
1、两个方法的逻辑对应。一个固定的编码方式,也对一个固定的逆向解码方式,反之亦然。
2、编码和解码的次数也要一一对应。

这四种种字符后,URL编码后的值还是它本身:
1、英文字母(a-z A-Z)
2、数字(0-9)
3、特殊字符( -_.)
4、部分保留字符(英文字符):
! * ' ( ) ; : @ & = + $ , / ?

说明~ # [] 这四个字符是否被转码成百分号编码,因系统不同会有不同。

URL字符编码使用%百分号加上两位的字符——0123456789ABCDEF——代表一个字节的十六进制形式。因编码后的值含有 % 保留字符。再次编译% 会编译成 %25

例如: &
第一次URL编码后:%26
第二次URL编码后:%2526
第三次URL编码后:%252526

正常解码逻辑:
第一次URL解码后:%2526
第二次URL解码后:%26
第三次URL解码后:&

因此,URL编码和解码必须是成对出现的。

初始字符为 & 连续编码三次,连续解码两次,则得到 %26 。
初始字符为 &%26 编码一次:%26%2526 连续解码两次则得到 && 。

2、URL是怎么拆解的

我们看一个常见的接口请求示例:

image

一般会根据 ://:/?&= 等拆分出请求的协议、服务器名称(或IP地址)、端口号、路径和文件名、参数名、参数值等。

3、在组装URL的什么阶段进行URL编码

我们看一个常见的接口请求示例:

image
字符串 说明
:// 协议符号
/ 分隔目录和子目录
测试 代表需要编译处理了的路径
分隔实际的URL和参数
& URL中指定的参数间的分隔符
= URL中指定的参数的值
搜&索 搜索词含有中文,含有保留字段,需要编译
&times 是key的一部分,不应该被编译,若多一次编译,会编译为 x

绿色字体是保留字符,都有特殊的含义,是不应该是被编码的。
红色字体必须要要编译的部分。
黄色背景的字符串,不应该被编译。
若以上操作不正确,会影响整个URL的解析。

常见的拼接过程
1、先拼接实际的请求地址 https://www.baidu.com/s/测@试?
2、再拼接参数字符串 wd=搜&索&timestamp=32424242423
3、将1、2合并凭借成一个网址字符串。
4、将网址字符串转为NSURL 实例。

分析

1、因 测@试 含有中文和保留字符@,需要在步骤1之前,先将 测@试 编码为 %e6%b5%8b%40%e8%af%95 ,再拼接到https://www.baidu.com/s/%e6%b5%8b%40%e8%af%95?
2、因 搜&索 含有中文和保留字符& ,& 会影响参数解析。需要先搜&索 编码为 %e6%90%9c%26%e7%b4%a2 ,再拼接到wd=%e6%90%9c%26%e7%b4%a2&timestamp=32424242423
3、因请求地址和参数列表已经编码过,拼接后的完整请求不应该再次编译。若再次编译 则会因含有 &times 编译为 x 。

小结

上面我们分别编码特殊字符后,最后拼接到一起。也有部分写法是拼接后再统一编码处理的。但因请求路径、请求参数中都可能含有保留字符&、=或中文等特殊字符,造成请求地址解析错误。建议在路径和参数拼接前对路径、参数名、参数值等先行统一编码处理,再行拼接。拼接好后不要再行编码,转为NSURL实例,发送请求。

4、可用的编码和解码API

URL编码是互联网的通用规范,各系统或平台都会提供封装好的API方法供开发者调用。
iOS端在生成NSURL实例

NSURL *url = [NSURL URLWithString:urlString];

特别要注意的是 urlString 中含有超出中文字符等非定URL限定字符时,创建的NSURL对象会失败,url返回为nil。

4.1、iOS 7之前的编码解码处理

4.1.1、方式一:stringByAddingPercentEscapesUsingEncoding

字符串URL编码实现

NSString *urlStr = @"你好0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?#[] %25";
NSString *encodingString = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url编码 = %@",encodingString);

注意:最后一个是空格符号,下面用黄色背景标记。
打印结果是

image

字符串URL解码实现

请求返回的数据格式是%E4%BD%A0%E5%A5%BD,需要进行UTF-8解码,对应方法是:

NSString *encodingString = @"%E4%BD%A0%E5%A5%BD0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?%23%5B%5D%25%20";
NSString *decodedStr = [encodingString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url解码 = %@",decodedStr);

打印结果是

image

通过上面的编码解码过程我们可以知道用

[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

绿色字体的 0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/? 这些字符不会被编译成%百分号编码,只有特别的 #[]%(空格)和中文字符会被编译。

小结

可通过第二章的4.2小节了解到,-_.~&!*'();:@&=+$,/?#[]%(最后一个是空格)这些特殊字符可能存在于网址请求的路径或参数表中,若不进行转义编码,容易在URL解析时,引起歧义,造成解析错误,找不到指定的资源,造成网络请求失败或错误。此方式只能用于处理URL编码规定字符集之外的字符且不含有以上特殊字符的编码处理。
该方式不适合处理URL的整体编译处理,可以局部编译不含特殊字符的URL部分,局限性太强,不建议在URL编码和解码时使用此方式,另外在iOS9之后苹果也废弃该方式。
另外,还有encodeBase64、decodeBase64方式,也是不能编译-_.~&!*'();:@&=+$,/?#[]%特殊字符,处理结果和方式一结果基本一致,就不再展开说明了。

4.1.2、方式二:CFURLCreateStringByAddingPercentEscapes

字符串URL编码实现

NSString *urlStr = @"你好0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?#[]% ";
//方式一编码对比
NSString *encodingStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url编码1-1 = %@",encodingStr);
//方式二编码定义空字符集
NSString *encodeStr = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)urlStr, NULL, (CFStringRef)@"", kCFStringEncodingUTF8));
NSLog(@"url编码2-1 = %@",encodeStr);
//方式二编码定义 ABC-_~.!*'();:@&=+ $,/?%#[] 字符集
NSString *encodeStr2 = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)urlStr, NULL, (CFStringRef)@"ABC-_~.!*'();:@&=+ $,/?%#[]", kCFStringEncodingUTF8));
NSLog(@"url编码2-2 = %@",encodeStr2);

注意:最后一个是空格符号,编码2-2强制编译了自定义字符集 ABC-_~.!*'();:@&=+ $,/?%#[],对结果如下:

image

字符串URL解码实现

NSString *encodedString = @"%E4%BD%A0%E5%A5%BD0123456789abcxyz%41%42%43XYZ%2D%5F%2E%7E&%21%2A%27%28%29%3B%3A@&%3D%2B%24%2C%2F%3F%23%5B%5D%25%20";

NSString *decodedStr = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,(CFStringRef)encodedString,CFSTR(""),kCFStringEncodingUTF8));
NSLog(@"url解码2-1 = %@",decodedStr);
    
NSString *decodedStr2 = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,(CFStringRef)encodedString,CFSTR(";:@&"),kCFStringEncodingUTF8));
NSLog(@"url解码2-2 = %@",decodedStr);

打印结果是

image

小结

通过以上数据可以看到,方式二在方式一编码的基础上,可对自定义的特殊字符集也进行编码处理,解决了方式一存在的问题。
解码2-2强制不解码自定义字符集 ABC-_~.!*'();:@&=+ $,/?%#[],但依然有两个字符@ &比较特殊,排除在解码之外,具体暂不明原因。

iOS7之前,建议采用方式二,要确定自定义字符集能全面覆盖有可能存在歧义的字符。

该方式可以编译特殊字符,因此,不适合对URL的整体编译,只能先将各个URL部分编译后,再组装在一起。

4.2、iOS 7之后的编码解码处理

4.2.1、全新的方式:stringByAddingPercentEncodingWithAllowedCharacters

iOS9之后苹果建议 使用新方法
stringByAddingPercentEncodingWithAllowedCharacters,其实该方法iOS7之后都可以调用。

苹果对该方法的注解:将AllowedCharacters集中不包含的所有字符替换为百分比编码字符,返回从接收器生成的新字符串。utf-8编码用于确定正确的编码字符百分比。不能对整个URL字符串进行百分比编码。此方法用于对URL组件或子组件字符串进行百分比编码,而不是对整个URL字符串进行百分比编码。7位ascii范围之外的允许字符中的任何字符都将被忽略。

字符串URL编码实现

NSString *urlStr = @"你好0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?#[]% ";
//方式一编码对比
NSString *encodingString = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url编码1-1 = %@",encodingString);

//方式二自定义字符集 ABC-_~.!*'();:@&=+ $,/?%#[]  编码对比
NSString *encodeStr2 = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)urlStr, NULL, (CFStringRef)@"ABC-_~.!*'();:=+ $,/?%#[]", kCFStringEncodingUTF8));
    NSLog(@"url编码2-2 = %@",encodeStr2);

//系统提供的枚举字符集,这些字符不需要  编译
NSString *encodeStr3 = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSLog(@"url编码3-1 = %@",encodeStr3);
    
//自定义字符不需要编译的字符集,为空字符集,将所有字符用百分号编码
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@""];
NSString *encodeStr4 = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:characterSet];
    NSLog(@"url编码3-2 = %@",encodeStr4);

打印结果是

image

网上常见的字符集枚举说明(供参考):

URLFragmentAllowedCharacterSet  "#%<>[\]^`{|}
URLHostAllowedCharacterSet      "#%/<>?@\^`{|}
URLPasswordAllowedCharacterSet  "#%/:<>?@[\]^`{|}
URLPathAllowedCharacterSet      "#%;<>?[\]^`{|}
URLQueryAllowedCharacterSet     "#%<>[\]^`{|}
URLUserAllowedCharacterSet      "#%/:<>?@[\]^`

字符串URL解码实现

//上段代码的结果为encodeStr3入参
NSString *decodedStr3 = [encodeStr3 stringByRemovingPercentEncoding];
NSLog(@"url编码3-1 = %@",decodedStr3);
//上段代码的结果为encodeStr4入参
NSString *decodedStr4 = [encodeStr4 stringByRemovingPercentEncoding];
    NSLog(@"url编码3-2 = %@",decodedStr4);

打印结果是


image

解码接口统一,不需要入参等。

小结
我们知道url编码1-1和编码3-1,系统提供给我们的URL特定的编码方式,并不能满足我们正确编码解码下面这样的常见请求示例:

image

因此需要根据业务自定义字符集,来定制化URL编码和解码。
编码3-1用的是系统 URLFragmentAllowedCharacterSet 字符集,系统并未提供打印字符集中具体字符的任何入口,我们并不能保障所有可能有歧义的特殊字符都转义编码过。建议我们采用URL编码3-2的写法,自定义特殊字符甚至定义空字符集,来编译局部所有的字符,最后再拼接成一个整体URL。

4.3、最优方案和封装处理

iOS7以下的版本可用4.1.2的方式微调即可。目前绝大部分APP都是适配在iOS7及以上的,我们以iOS7之后的方案为主。
创建一个 NSString+UTF_8 分类,定义两个方法实现如下:

/**
 对字符串的每个字符进行UTF-8编码
 
 @return 百分号编码后的字符串
 */
- (NSString *)URLUTF8EncodingString
{
    if (self.length == 0) {
        return self;
    }
    NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@""];
    NSString *encodeStr = [self stringByAddingPercentEncodingWithAllowedCharacters:characterSet];
    return encodeStr;
}

/**
 对字符串的每个字符进行彻底的 UTF-8 解码
 连续编码2次,需要连续解码2次,第三次继续解码时,则返回为空
 @return 百分号编码解码后的字符串
 */
- (NSString *)URLUTF8DecodingString
{
    if (self.length == 0) {
        return self;
    }
    if ([self stringByRemovingPercentEncoding] == nil
        || [self isEqualToString:[self stringByRemovingPercentEncoding]]) {
        return self;
    }
    NSString *decodedStr = [self stringByRemovingPercentEncoding];
    while ([decodedStr stringByRemovingPercentEncoding] != nil) {
        decodedStr = [decodedStr stringByRemovingPercentEncoding];
    }
    return decodedStr;
}

注意

URLUTF8EncodingString UTF-8编码可以无限制调用多次,stringByRemovingPercentEncoding方法的特殊性是字符串不是UTF-8编码格式,调用时返回为nil,因此解码时只需调用一次URLUTF8DecodingString即可将所有字符彻底UTF-8解码。

5、其他处理方法

可以将需要编码的参数表整体封装为NSDdata类型,使用post请求发送也是可以的。

四、总结

1、在URL组装拼接前对各个部分的可能会引起歧义的字符串进行全量UTF-8编码。
2、在需要解码的地方,需要先分拆字符串,再分段解码使用。
3、在需要将已组装的数据,进行重组时,需要先拆解,分别解码后再编码,最后再重组。
4、服务端会对请求进行UTF-8解码一次,请确保请求中的字符只进行一次UTF-8编码。

参考资料

1、HTML URL 编码参考手册
2、HTML URL 编码
3、在线url网址编码、解码
4、站长工具 之 URL编码解码
5、URL原理、URL编码、URL特殊字符

原创不易,转载请注明作者:择势勤

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 听着舒缓的轻音乐,神经在这气氛中慢慢放松,一天天过的是如此之快,昨天的忙碌与振奋已俨然逝去,不知不觉我们已经20了...
    魅惑初心阅读 118评论 0 1
  • 过了25+,花在脸上的钱要远比穿在身上的更重要,可皱纹一旦开始显现,似乎几千大洋的贵妇面霜也消不了法令纹and眼角...
    粉红的三天阅读 462评论 0 0
  • 整整三天没进食,自从我无情的把她搬到陌生的家,可能她就要恨死我了…… 暂时叫他小绿好了,虽...
    熊熊小姑娘阅读 279评论 0 0
  • 奶奶从集上回来,说集上有一些店开门了,我高兴极了,赶快叫着妈妈去集上,我们来到了集上,妈妈来集上是买染发剂的,妈妈...
    王皓煜阅读 250评论 0 1