Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
taosdata
TDengine
提交
e0acd602
T
TDengine
项目概览
taosdata
/
TDengine
大约 1 年 前同步成功
通知
1185
Star
22015
Fork
4786
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
1
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
T
TDengine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
1
Issue
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
e0acd602
编写于
7月 01, 2020
作者:
S
Shengliang Guan
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[TD-814] invalid read while close http connect
上级
0cfb36b5
变更
6
隐藏空白更改
内联
并排
Showing
6 changed file
with
48 addition
and
40 deletion
+48
-40
src/plugins/http/src/httpContext.c
src/plugins/http/src/httpContext.c
+7
-2
src/plugins/http/src/httpHandle.c
src/plugins/http/src/httpHandle.c
+12
-12
src/plugins/http/src/httpJson.c
src/plugins/http/src/httpJson.c
+8
-8
src/plugins/http/src/httpServer.c
src/plugins/http/src/httpServer.c
+0
-2
src/plugins/http/src/httpSystem.c
src/plugins/http/src/httpSystem.c
+4
-2
src/util/src/tcache.c
src/util/src/tcache.c
+17
-14
未找到文件。
src/plugins/http/src/httpContext.c
浏览文件 @
e0acd602
...
...
@@ -141,10 +141,15 @@ HttpContext *httpGetContext(void *ptr) {
void
httpReleaseContext
(
HttpContext
*
pContext
)
{
int32_t
refCount
=
atomic_sub_fetch_32
(
&
pContext
->
refCount
,
1
);
assert
(
refCount
>=
0
);
httpDebug
(
"context:%p,
fd:%d, is releasd, refCount:%d"
,
pContext
,
pContext
->
fd
,
refCount
);
httpDebug
(
"context:%p,
is releasd, refCount:%d"
,
pContext
,
refCount
);
HttpContext
**
ppContext
=
pContext
->
ppContext
;
taosCacheRelease
(
tsHttpServer
.
contextCache
,
(
void
**
)(
&
ppContext
),
false
);
if
(
tsHttpServer
.
contextCache
!=
NULL
)
{
taosCacheRelease
(
tsHttpServer
.
contextCache
,
(
void
**
)(
&
ppContext
),
false
);
}
else
{
httpDebug
(
"context:%p, won't be destroyed for cache is already released"
,
pContext
);
// httpDestroyContext((void **)(&ppContext));
}
}
bool
httpInitContext
(
HttpContext
*
pContext
)
{
...
...
src/plugins/http/src/httpHandle.c
浏览文件 @
e0acd602
...
...
@@ -157,7 +157,7 @@ bool httpGetHttpMethod(HttpContext* pContext) {
pParser
->
method
.
pos
[
pParser
->
method
.
len
]
=
0
;
pParser
->
pLast
=
pSeek
+
1
;
http
Debug
(
"context:%p, fd:%d, ip:%s, httpMethod:%s"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
pParser
->
method
.
pos
);
http
Trace
(
"context:%p, fd:%d, ip:%s, httpMethod:%s"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
pParser
->
method
.
pos
);
return
true
;
}
...
...
@@ -186,23 +186,23 @@ bool httpParseHead(HttpContext* pContext) {
HttpParser
*
pParser
=
&
pContext
->
parser
;
if
(
strncasecmp
(
pParser
->
pLast
,
"Content-Length: "
,
16
)
==
0
)
{
pParser
->
data
.
len
=
(
int32_t
)
atoi
(
pParser
->
pLast
+
16
);
http
Debug
(
"context:%p, fd:%d, ip:%s, Content-Length:%d"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
http
Trace
(
"context:%p, fd:%d, ip:%s, Content-Length:%d"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
pParser
->
data
.
len
);
}
else
if
(
strncasecmp
(
pParser
->
pLast
,
"Accept-Encoding: "
,
17
)
==
0
)
{
if
(
tsHttpEnableCompress
&&
strstr
(
pParser
->
pLast
+
17
,
"gzip"
)
!=
NULL
)
{
pContext
->
acceptEncoding
=
HTTP_COMPRESS_GZIP
;
http
Debug
(
"context:%p, fd:%d, ip:%s, Accept-Encoding:gzip"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, Accept-Encoding:gzip"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
}
else
{
pContext
->
acceptEncoding
=
HTTP_COMPRESS_IDENTITY
;
http
Debug
(
"context:%p, fd:%d, ip:%s, Accept-Encoding:identity"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, Accept-Encoding:identity"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
}
}
else
if
(
strncasecmp
(
pParser
->
pLast
,
"Content-Encoding: "
,
18
)
==
0
)
{
if
(
strstr
(
pParser
->
pLast
+
18
,
"gzip"
)
!=
NULL
)
{
pContext
->
contentEncoding
=
HTTP_COMPRESS_GZIP
;
http
Debug
(
"context:%p, fd:%d, ip:%s, Content-Encoding:gzip"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, Content-Encoding:gzip"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
}
else
{
pContext
->
contentEncoding
=
HTTP_COMPRESS_IDENTITY
;
http
Debug
(
"context:%p, fd:%d, ip:%s, Content-Encoding:identity"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, Content-Encoding:identity"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
}
}
else
if
(
strncasecmp
(
pParser
->
pLast
,
"Connection: "
,
12
)
==
0
)
{
if
(
strncasecmp
(
pParser
->
pLast
+
12
,
"Keep-Alive"
,
10
)
==
0
)
{
...
...
@@ -210,7 +210,7 @@ bool httpParseHead(HttpContext* pContext) {
}
else
{
pContext
->
httpKeepAlive
=
HTTP_KEEPALIVE_DISABLE
;
}
http
Debug
(
"context:%p, fd:%d, ip:%s, keepAlive:%d"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
http
Trace
(
"context:%p, fd:%d, ip:%s, keepAlive:%d"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
pContext
->
httpKeepAlive
);
}
else
if
(
strncasecmp
(
pParser
->
pLast
,
"Transfer-Encoding: "
,
19
)
==
0
)
{
if
(
strncasecmp
(
pParser
->
pLast
+
19
,
"chunked"
,
7
)
==
0
)
{
...
...
@@ -281,7 +281,7 @@ bool httpReadChunkedBody(HttpContext* pContext, HttpParser* pParser) {
httpParseChunkedBody
(
pContext
,
pParser
,
false
);
return
HTTP_CHECK_BODY_SUCCESS
;
}
else
{
http
Debug
(
"context:%p, fd:%d, ip:%s, chunked body not finished, continue read"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, chunked body not finished, continue read"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
if
(
!
httpReadDataImp
(
pContext
))
{
httpError
(
"context:%p, fd:%d, ip:%s, read chunked request error"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
);
return
HTTP_CHECK_BODY_ERROR
;
...
...
@@ -299,7 +299,7 @@ int httpReadUnChunkedBody(HttpContext* pContext, HttpParser* pParser) {
httpSendErrorResp
(
pContext
,
HTTP_PARSE_BODY_ERROR
);
return
HTTP_CHECK_BODY_ERROR
;
}
else
if
(
dataReadLen
<
pParser
->
data
.
len
)
{
http
Debug
(
"context:%p, fd:%d, ip:%s, un-chunked body not finished, read size:%d dataReadLen:%d < pContext->data.len:%d, continue read"
,
http
Trace
(
"context:%p, fd:%d, ip:%s, un-chunked body not finished, read size:%d dataReadLen:%d < pContext->data.len:%d, continue read"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
pContext
->
parser
.
bufsize
,
dataReadLen
,
pParser
->
data
.
len
);
return
HTTP_CHECK_BODY_CONTINUE
;
}
else
{
...
...
@@ -313,9 +313,9 @@ bool httpParseRequest(HttpContext* pContext) {
return
true
;
}
http
Debug
(
"context:%p, fd:%d, ip:%s, thread:%s, numOfFds:%d, read size:%d, raw data:
\n
%s"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
pContext
->
pThread
->
label
,
pContext
->
pThread
->
numOfFds
,
pContext
->
parser
.
bufsize
,
pContext
->
parser
.
buffer
);
http
TraceDump
(
"context:%p, fd:%d, ip:%s, thread:%s, numOfFds:%d, read size:%d, raw data:
\n
%s"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
pContext
->
pThread
->
label
,
pContext
->
pThread
->
numOfFds
,
pContext
->
parser
.
bufsize
,
pContext
->
parser
.
buffer
);
if
(
!
httpGetHttpMethod
(
pContext
))
{
return
false
;
...
...
src/plugins/http/src/httpJson.c
浏览文件 @
e0acd602
...
...
@@ -76,8 +76,8 @@ int httpWriteBuf(struct HttpContext *pContext, const char *buf, int sz) {
httpError
(
"context:%p, fd:%d, ip:%s, dataSize:%d, writeSize:%d, failed to send response:
\n
%s"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
sz
,
writeSz
,
buf
);
}
else
{
http
Debug
(
"context:%p, fd:%d, ip:%s, dataSize:%d, writeSize:%d, response:
\n
%s"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
sz
,
writeSz
,
buf
);
http
Trace
(
"context:%p, fd:%d, ip:%s, dataSize:%d, writeSize:%d, response:
\n
%s"
,
pContext
,
pContext
->
fd
,
pContext
->
ipstr
,
sz
,
writeSz
,
buf
);
}
return
writeSz
;
...
...
@@ -99,7 +99,7 @@ int httpWriteJsonBufBody(JsonBuf* buf, bool isTheLast) {
uint64_t
srcLen
=
(
uint64_t
)
(
buf
->
lst
-
buf
->
buf
);
if
(
buf
->
pContext
->
fd
<=
0
)
{
http
Debug
(
"context:%p, fd:%d, ip:%s, write json body error"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, write json body error"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
);
buf
->
pContext
->
fd
=
-
1
;
}
...
...
@@ -113,11 +113,11 @@ int httpWriteJsonBufBody(JsonBuf* buf, bool isTheLast) {
if
(
buf
->
pContext
->
acceptEncoding
==
HTTP_COMPRESS_IDENTITY
)
{
if
(
buf
->
lst
==
buf
->
buf
)
{
http
Debug
(
"context:%p, fd:%d, ip:%s, no data need dump"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, no data need dump"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
);
return
0
;
// there is no data to dump.
}
else
{
int
len
=
sprintf
(
sLen
,
"%lx
\r\n
"
,
srcLen
);
http
Debug
(
"context:%p, fd:%d, ip:%s, write body, chunkSize:%"
PRIu64
", response:
\n
%s"
,
http
Trace
(
"context:%p, fd:%d, ip:%s, write body, chunkSize:%"
PRIu64
", response:
\n
%s"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
,
srcLen
,
buf
->
buf
);
httpWriteBufNoTrace
(
buf
->
pContext
,
sLen
,
len
);
remain
=
httpWriteBufNoTrace
(
buf
->
pContext
,
buf
->
buf
,
(
int
)
srcLen
);
...
...
@@ -129,12 +129,12 @@ int httpWriteJsonBufBody(JsonBuf* buf, bool isTheLast) {
if
(
ret
==
0
)
{
if
(
compressBufLen
>
0
)
{
int
len
=
sprintf
(
sLen
,
"%x
\r\n
"
,
compressBufLen
);
http
Debug
(
"context:%p, fd:%d, ip:%s, write body, chunkSize:%"
PRIu64
", compressSize:%d, last:%d, response:
\n
%s"
,
http
Trace
(
"context:%p, fd:%d, ip:%s, write body, chunkSize:%"
PRIu64
", compressSize:%d, last:%d, response:
\n
%s"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
,
srcLen
,
compressBufLen
,
isTheLast
,
buf
->
buf
);
httpWriteBufNoTrace
(
buf
->
pContext
,
sLen
,
len
);
remain
=
httpWriteBufNoTrace
(
buf
->
pContext
,
(
const
char
*
)
compressBuf
,
(
int
)
compressBufLen
);
}
else
{
http
Debug
(
"context:%p, fd:%d, ip:%s, last:%d, compress already dumped, response:
\n
%s"
,
http
Trace
(
"context:%p, fd:%d, ip:%s, last:%d, compress already dumped, response:
\n
%s"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
,
isTheLast
,
buf
->
buf
);
return
0
;
// there is no data to dump.
}
...
...
@@ -173,7 +173,7 @@ void httpWriteJsonBufHead(JsonBuf* buf) {
void
httpWriteJsonBufEnd
(
JsonBuf
*
buf
)
{
if
(
buf
->
pContext
->
fd
<=
0
)
{
http
Debug
(
"context:%p, fd:%d, ip:%s, json buf fd is 0"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
);
http
Trace
(
"context:%p, fd:%d, ip:%s, json buf fd is 0"
,
buf
->
pContext
,
buf
->
pContext
->
fd
,
buf
->
pContext
->
ipstr
);
buf
->
pContext
->
fd
=
-
1
;
}
...
...
src/plugins/http/src/httpServer.c
浏览文件 @
e0acd602
...
...
@@ -66,8 +66,6 @@ void httpCleanUpConnect() {
}
}
tfree
(
pServer
->
pThreads
);
pServer
->
pThreads
=
NULL
;
httpDebug
(
"http server:%s is cleaned up"
,
pServer
->
label
);
}
...
...
src/plugins/http/src/httpSystem.c
浏览文件 @
e0acd602
...
...
@@ -95,11 +95,13 @@ void httpCleanUpSystem() {
httpInfo
(
"http server cleanup"
);
httpStopSystem
();
httpCleanUpConnect
();
httpCleanupContexts
();
httpCleanUpSessions
();
httpCleanUpConnect
();
pthread_mutex_destroy
(
&
tsHttpServer
.
serverMutex
);
tfree
(
tsHttpServer
.
pThreads
);
tsHttpServer
.
pThreads
=
NULL
;
tsHttpServer
.
status
=
HTTP_SERVER_CLOSED
;
}
...
...
src/util/src/tcache.c
浏览文件 @
e0acd602
...
...
@@ -119,7 +119,7 @@ static FORCE_INLINE void taosCacheReleaseNode(SCacheObj *pCacheObj, SCacheDataNo
int32_t
size
=
pNode
->
size
;
taosHashRemove
(
pCacheObj
->
pHashTable
,
pNode
->
key
,
pNode
->
keySize
);
uDebug
(
"key:%s
is removed from cache,total:%"
PRId64
",size:%d
bytes"
,
pNode
->
key
,
pCacheObj
->
totalSize
,
size
);
uDebug
(
"key:%s
, is removed from cache, total:%"
PRId64
" size:%d
bytes"
,
pNode
->
key
,
pCacheObj
->
totalSize
,
size
);
if
(
pCacheObj
->
freeFp
)
pCacheObj
->
freeFp
(
pNode
->
data
);
free
(
pNode
);
}
...
...
@@ -288,14 +288,14 @@ void *taosCachePut(SCacheObj *pCacheObj, const char *key, const void *pData, siz
if
(
NULL
!=
pNode
)
{
pCacheObj
->
totalSize
+=
pNode
->
size
;
uDebug
(
"key:%s %p added into cache, added:%"
PRIu64
", expire:%"
PRIu64
", total:%"
PRId64
", size:%"
PRId64
" bytes"
,
uDebug
(
"key:%s
,
%p added into cache, added:%"
PRIu64
", expire:%"
PRIu64
", total:%"
PRId64
", size:%"
PRId64
" bytes"
,
key
,
pNode
,
pNode
->
addedTime
,
pNode
->
expiredTime
,
pCacheObj
->
totalSize
,
dataSize
);
}
else
{
uError
(
"key:%s failed to added into cache, out of memory"
,
key
);
uError
(
"key:%s
,
failed to added into cache, out of memory"
,
key
);
}
}
else
{
// old data exists, update the node
pNode
=
taosUpdateCacheImpl
(
pCacheObj
,
pOld
,
key
,
keyLen
,
pData
,
dataSize
,
duration
*
1000L
);
uDebug
(
"key:%s %p exist in cache, updated"
,
key
,
pNode
);
uDebug
(
"key:%s
,
%p exist in cache, updated"
,
key
,
pNode
);
}
__cache_unlock
(
pCacheObj
);
...
...
@@ -321,10 +321,10 @@ void *taosCacheAcquireByName(SCacheObj *pCacheObj, const char *key) {
if
(
ptNode
!=
NULL
)
{
atomic_add_fetch_32
(
&
pCacheObj
->
statistics
.
hitCount
,
1
);
uDebug
(
"key:%s is retrieved from cache, %p refcnt:%d"
,
key
,
(
*
ptNode
),
T_REF_VAL_GET
(
*
ptNode
));
uDebug
(
"key:%s
,
is retrieved from cache, %p refcnt:%d"
,
key
,
(
*
ptNode
),
T_REF_VAL_GET
(
*
ptNode
));
}
else
{
atomic_add_fetch_32
(
&
pCacheObj
->
statistics
.
missCount
,
1
);
uDebug
(
"key:%s not in cache, retrieved failed"
,
key
);
uDebug
(
"key:%s
,
not in cache, retrieved failed"
,
key
);
}
atomic_add_fetch_32
(
&
pCacheObj
->
statistics
.
totalAccess
,
1
);
...
...
@@ -350,10 +350,10 @@ void* taosCacheUpdateExpireTimeByName(SCacheObj *pCacheObj, const char *key, uin
if
(
ptNode
!=
NULL
)
{
atomic_add_fetch_32
(
&
pCacheObj
->
statistics
.
hitCount
,
1
);
uDebug
(
"key:%s expireTime is updated in cache, %p refcnt:%d"
,
key
,
(
*
ptNode
),
T_REF_VAL_GET
(
*
ptNode
));
uDebug
(
"key:%s
,
expireTime is updated in cache, %p refcnt:%d"
,
key
,
(
*
ptNode
),
T_REF_VAL_GET
(
*
ptNode
));
}
else
{
atomic_add_fetch_32
(
&
pCacheObj
->
statistics
.
missCount
,
1
);
uDebug
(
"key:%s not in cache, retrieved failed"
,
key
);
uDebug
(
"key:%s
,
not in cache, retrieved failed"
,
key
);
}
atomic_add_fetch_32
(
&
pCacheObj
->
statistics
.
totalAccess
,
1
);
...
...
@@ -410,13 +410,13 @@ void taosCacheRelease(SCacheObj *pCacheObj, void **data, bool _remove) {
SCacheDataNode
*
pNode
=
(
SCacheDataNode
*
)((
char
*
)(
*
data
)
-
offset
);
if
(
pNode
->
signature
!=
(
uint64_t
)
pNode
)
{
uError
(
"
key:
%p release invalid cache data"
,
pNode
);
uError
(
"%p release invalid cache data"
,
pNode
);
return
;
}
*
data
=
NULL
;
int32_t
ref
=
T_REF_DEC
(
pNode
);
uDebug
(
"
%p data released, refcnt:%d"
,
pNode
,
ref
);
uDebug
(
"
key:%s, is released, %p refcnt:%d"
,
pNode
->
key
,
pNode
,
ref
);
if
(
_remove
)
{
__cache_wr_lock
(
pCacheObj
);
...
...
@@ -501,7 +501,7 @@ void taosAddToTrash(SCacheObj *pCacheObj, SCacheDataNode *pNode) {
pNode
->
inTrashCan
=
true
;
pCacheObj
->
numOfElemsInTrash
++
;
uDebug
(
"key:%s %p move to trash, numOfElem in trash:%d"
,
pNode
->
key
,
pNode
,
pCacheObj
->
numOfElemsInTrash
);
uDebug
(
"key:%s
,
%p move to trash, numOfElem in trash:%d"
,
pNode
->
key
,
pNode
,
pCacheObj
->
numOfElemsInTrash
);
}
void
taosRemoveFromTrashCan
(
SCacheObj
*
pCacheObj
,
STrashElem
*
pElem
)
{
...
...
@@ -549,7 +549,7 @@ void taosTrashCanEmpty(SCacheObj *pCacheObj, bool force) {
}
if
(
force
||
(
T_REF_VAL_GET
(
pElem
->
pData
)
==
0
))
{
uDebug
(
"key:%s %p removed from trash. numOfElem in trash:%d"
,
pElem
->
pData
->
key
,
pElem
->
pData
,
uDebug
(
"key:%s
,
%p removed from trash. numOfElem in trash:%d"
,
pElem
->
pData
->
key
,
pElem
->
pData
,
pCacheObj
->
numOfElemsInTrash
-
1
);
STrashElem
*
p
=
pElem
;
...
...
@@ -570,8 +570,11 @@ void doCleanupDataCache(SCacheObj *pCacheObj) {
while
(
taosHashIterNext
(
pIter
))
{
SCacheDataNode
*
pNode
=
*
(
SCacheDataNode
**
)
taosHashIterGet
(
pIter
);
// if (pNode->expiredTime <= expiredTime && T_REF_VAL_GET(pNode) <= 0) {
taosCacheReleaseNode
(
pCacheObj
,
pNode
);
//}
if
(
T_REF_VAL_GET
(
pNode
)
<=
0
)
{
taosCacheReleaseNode
(
pCacheObj
,
pNode
);
}
else
{
uDebug
(
"key:%s, will not remove from cache, refcnt:%d"
,
pNode
->
key
,
T_REF_VAL_GET
(
pNode
));
}
}
taosHashDestroyIter
(
pIter
);
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录