优化:优化被测件

wuhaoyang
RenFengJiang 8 months ago
parent 064bb0309e
commit 99060ade51
  1. 2
      sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/checkers/FVNRPassWordChecker.java
  2. 4
      sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/checkers/FVNRShaChecker.java
  3. 4
      sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/checkers/HighEncryptDesChecker.java
  4. 4
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/BufferDataCheckerTest.java
  5. 4
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/DLLVerifyCheckerTest.java
  6. 2
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/ErrorMessageCheckerTest.java
  7. 10
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheckTest.java
  8. 4
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/PRNGVerifyCheckerTest.java
  9. 2
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/PathVerifyCheckerTest.java
  10. 2
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/ReallocMainCheckerTest.java
  11. 2
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/SQLVerifyCheckerTest.java
  12. 2
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/VerificationPathCheckerTest.java
  13. 70
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/BufferDataChecker.cc
  14. 57
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/DLLVerifyChecker.cc
  15. 16
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/ErrorMessageChecker.cc
  16. 18
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.cc
  17. 4
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/NumericalCopyChecker.cc
  18. 18
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/PRNGVerifyChecker.cc
  19. 7
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/PathVerifyChecker.cc
  20. 30
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/ReallocMainChecker.cc
  21. 48
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/SQLVerifyChecker.cc
  22. 32
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/VerificationPathChecker.cc

@ -43,7 +43,7 @@ public class FVNRPassWordChecker extends SquidCheck<Grammar> {
List<String> inputFileLines = getContext().getInputFileLines();
for (String str:inputFileLines) {
if(str.startsWith("#include")){
if(str.contains("<openssl/aes.h>") || str.contains("<openssl/des.h>")){
if(str.contains("openssl/aes.h") || str.contains("openssl/des.h")){
getContext().createFileViolation(this, "应使用单向不可逆算法对密码进行加密");
break;
}

@ -43,8 +43,8 @@ public class FVNRShaChecker extends SquidCheck<Grammar> {
List<String> inputFileLines = getContext().getInputFileLines();
for (String str:inputFileLines) {
if(str.startsWith("#include")){
if(str.contains("<openssl/") || str.contains("<cryptopp/") ){
if(!str.contains("sha.h>") && !str.contains("blake2.h>") && !str.contains("md5.h>")){
if(str.contains("openssl/") || str.contains("cryptopp/") ){
if(!str.contains("sha.h") && !str.contains("blake2.h") && !str.contains("md5.h")){
getContext().createFileViolation(this, "应使用不可逆标准散列算法");
break;
}

@ -43,8 +43,8 @@ public class HighEncryptDesChecker extends SquidCheck<Grammar> {
List<String> inputFileLines = getContext().getInputFileLines();
for (String str:inputFileLines) {
if(str.startsWith("#include")){
if(str.contains("<openssl/") || str.contains("<cryptopp/") ){
if(!str.contains("aes.h>") && !str.contains("des.h>") ){
if(str.contains("openssl/") || str.contains("cryptopp/") ){
if(!str.contains("aes.h") && !str.contains("des.h") ){
getContext().createFileViolation(this, "应采用加密强度较高的标准加密算法");
break;
}

@ -27,7 +27,9 @@ public class BufferDataCheckerTest {
var tester = CxxFileTesterHelper.create("BufferDataChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(12).withMessage("应对读写缓冲区的数据长度进行检查")
.next().atLine(17).withMessage("应对读写缓冲区的数据长度进行检查")
.next().atLine(30).withMessage("应对读写缓冲区的数据长度进行检查")
.next().atLine(43).withMessage("应对读写缓冲区的数据长度进行检查")
.noMore();
}
}

@ -27,8 +27,8 @@ public class DLLVerifyCheckerTest {
var tester = CxxFileTesterHelper.create("DLLVerifyChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(22).withMessage("在动态加载库前对输入数据进行验证")
.next().atLine(44).withMessage("在动态加载库前对输入数据进行验证")
.next().atLine(17).withMessage("在动态加载库前对输入数据进行验证")
.next().atLine(43).withMessage("在动态加载库前对输入数据进行验证")
.noMore();
}
}

@ -28,7 +28,7 @@ public class ErrorMessageCheckerTest {
var tester = CxxFileTesterHelper.create("ErrorMessageChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(8).withMessage("抛出异常消息不得包含敏感信息")
.next().atLine(20).withMessage("抛出异常消息不得包含敏感信息")
.noMore();
}
}

@ -30,11 +30,11 @@ public class FormatFunctionCheckTest {
var tester = CxxFileTesterHelper.create("FormatFunctionCheck.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(6).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(9).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(12).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(16).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(20).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(8).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(11).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(14).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(18).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.next().atLine(22).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数")
.noMore();
}
}

@ -28,9 +28,9 @@ public class PRNGVerifyCheckerTest {
var tester = CxxFileTesterHelper.create("PRNGVerifyChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(4).withMessage("应使用目前被业界专家认为较强的经过良好审核的加密PRNG算法")
.next().atLine(6).withMessage("应使用目前被业界专家认为较强的经过良好审核的加密PRNG算法")
.next().atLine(8).withMessage("应使用目前被业界专家认为较强的经过良好审核的加密PRNG算法")
.next().atLine(11).withMessage("应使用目前被业界专家认为较强的经过良好审核的加密PRNG算法")
.next().atLine(14).withMessage("应使用目前被业界专家认为较强的经过良好审核的加密PRNG算法")
.noMore();
}
}

@ -27,8 +27,8 @@ public class PathVerifyCheckerTest {
var tester = CxxFileTesterHelper.create("PathVerifyChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(3).withMessage("使用关键资源时指定资源所在的路径")
.next().atLine(4).withMessage("使用关键资源时指定资源所在的路径")
.next().atLine(5).withMessage("使用关键资源时指定资源所在的路径")
.noMore();
}
}

@ -27,7 +27,7 @@ public class ReallocMainCheckerTest {
var tester = CxxFileTesterHelper.create("ReallocMainChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(14).withMessage("使用realloc函数前应先清楚敏感信息")
.next().atLine(27).withMessage("使用realloc函数前应先清楚敏感信息")
.noMore();
}

@ -28,7 +28,7 @@ public class SQLVerifyCheckerTest {
var tester = CxxFileTesterHelper.create("SQLVerifyChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(7).withMessage("在使用SQL语句前对输入数据进行验证")
.next().atLine(26).withMessage("在使用SQL语句前对输入数据进行验证")
.noMore();
}
}

@ -21,7 +21,7 @@ public class VerificationPathCheckerTest {
var tester = CxxFileTesterHelper.create("VerificationPathChecker.cc");
SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker);
CheckMessagesVerifier.verify(file.getCheckMessages())
.next().atLine(18).withMessage("在构建路径名前对数据进行校验")
.next().atLine(12).withMessage("在构建路径名前对数据进行校验")
.noMore();
}
}

@ -1,17 +1,63 @@
void writeIntoBuffer(const char* data, size_t dataSize) {
#include <cstring>
#include <iostream>
// 假设这是全局或类成员变量
char buffer[1024];
size_t bufferSize = sizeof(buffer);
size_t usedBufferSize = 0;
void testOne(const char* data, size_t dataSize) {
// 检查要写入的数据是否超过缓冲区的剩余空间
if (dataSize > bufferSize) {
std::cerr << "Error: Data size exceeds buffer capacity." << std::endl;
return;
}
if(as(length)){
}
// if (dataSize > bufferSize - usedBufferSize) {
// std::cerr << "Error: Data size exceeds buffer remaining capacity." << std::endl;
// return;
// }
// 安全地复制数据到缓冲区的剩余空间
memcpy(buffer , data, dataSize);//error
// 更新缓冲区已使用的空间
usedBufferSize += dataSize;
}
void testTwo(const char* data, size_t dataSize) {
// 检查要写入的数据是否超过缓冲区的剩余空间
// if (dataSize > bufferSize - usedBufferSize) {
// std::cerr << "Error: Data size exceeds buffer remaining capacity." << std::endl;
// return;
// }
// 安全地复制数据到缓冲区
// 安全地复制数据到缓冲区的剩余空间
strncpy(buffer, data, dataSize);//error
// 更新缓冲区已使用的空间
usedBufferSize += dataSize;
}
memcpy(buffer, data, 1);
strncpy(buffer, data, length);
memset(buffer, data, length);
void testThree(const char* data, size_t dataSize) {
// 检查要写入的数据是否超过缓冲区的剩余空间
// if (dataSize > bufferSize - usedBufferSize) {
// std::cerr << "Error: Data size exceeds buffer remaining capacity." << std::endl;
// return;
// }
// 安全地复制数据到缓冲区的剩余空间
memset(buffer, 0, dataSize);//error
// 更新缓冲区已使用的空间
bufferSize -= dataSize;
usedBufferSize += dataSize;
}
int main() {
const char* testData = "Hello, World!";
size_t testDataSize = strlen(testData) + 1; // 包含结束符'\0'
testOne(testData, testDataSize);
testTwo(testData, testDataSize);
testThree(testData, testDataSize);
// 输出缓冲区内容以验证
for (size_t i = 0; i < usedBufferSize; ++i) {
if (buffer[i] == '\0') break;
std::cout << buffer[i];
}
std::cout << std::endl;
return 0;
}

@ -1,24 +1,19 @@
#include <iostream>
#ifdef _WIN32
#include <windows.h>
#include <dlfcn.h>
#else
#include <dlfcn.h>
#endif
int main()
int test1()
{
std::string a = "your_dll.dll";
// if (a != "a") {
// // 这个条件语句块目前为空,如果需要可以添加相关逻辑
// }
//#ifdef _WIN32
std::wstring wideDLLName(a.begin(), a.end()); // C++11及以后版本可以直接转换
// std::string a = "aa";
// std::wstring wideA(a.begin(), a.end());
// if(wideDLLName == wideA){
// }
std::wstring wideDLLName(a.begin(), a.end());
// std::string v = "aa";
// std::wstring wideA(v.begin(),v.end());
// if (wideDLLName == wideA) {
// // 当 wideDLLName 等于 ANSI 字符串 "aa" 转换后的宽字符串时,这里的代码将被执行
// }
HINSTANCE hInst = LoadLibrary(wideDLLName.c_str());//error
if (hInst == NULL) {
std::cout << "无法加载库" << std::endl;
@ -26,40 +21,58 @@ int main()
}
typedef void (*FuncType)();
FuncType func = (FuncType)GetProcAddress(hInst, "函数名");
FuncType func = (FuncType)GetProcAddress(hInst, "实际函数名");
if (func == NULL) {
std::cout << "无法获取函数" << std::endl;
FreeLibrary(hInst);
std::cout << "无法获取函数" << std::endl;
return -1;
}
func();
FreeLibrary(hInst);
//#else
std::string b = "c";
// if (b != "a") {
// // 这个条件语句块目前为空,如果需要可以添加相关逻辑
// }
return 0;
}
#if !defined(_WIN32)
int test2()
{
std::string b = "your_so_file.so";
// if(b == ""){
// }
void *handle = dlopen(b.c_str(), RTLD_LAZY);//error
if (!handle) {
std::cerr << "无法打开库:" << dlerror() << '\n';
std::cerr << "无法打开库: " << dlerror() << '\n';
return 1;
}
dlerror(); // 清除上一次调用产生的错误信息
typedef void (*FuncType)();
FuncType func = (FuncType)dlsym(handle, "函数名称");
FuncType func = (FuncType)dlsym(handle, "实际函数名");
const char *dlsym_error = dlerror();
if (dlsym_error) {
std::cerr << "无法加载符号 '函数名称':" << dlsym_error << '\n';
dlclose(handle);
std::cerr << "无法加载符号 '实际函数名':" << dlsym_error << '\n';
return 1;
}
func();
dlclose(handle);
//#endif
return 0;
}
#endif
int main()
{
// 调用对应平台的函数
#ifdef _WIN32
test1();
#else
test2();
#endif
return 0;
}

@ -1,11 +1,23 @@
#include <iostream>
#include <stdexcept>
using namespace std;
class MyException : public std::exception {
public:
explicit MyException(const std::string& msg) : message(msg) {}
virtual const char* what() const noexcept override {
return message.c_str();
}
private:
std::string message;
};
int main() {
try {
std::String weapon = "手枪";
std::string weapon = "手枪";
// 抛出一个异常
// throw "C++ Exception" + an;
throw MyException(weapon );
throw MyException(weapon);
}
catch (const char* e) {
// 捕获异常并处理

@ -3,21 +3,23 @@
int main()
{
const char* name = "World";
printf("Hello, %s!\n", name,name1,name2);
string name1 = "a";
string name2 = "C";
printf("Hello, %s!\n", name, name1.c_str(), name2.c_str());
char buffer[50];
sprintf(buffer,"Hello, %s!\n",name,name1,name2);
char buffer1[50];
sprintf(buffer1,"Hello, %s!\n",name,name1,name2);
char buffer[50];
snprintf(buffer, sizeof(buffer), "Hello, %s!\n",name, name1, name2);
char buffer2[50];
snprintf(buffer2, sizeof(buffer2), "Hello, %s!\n", name, name1.c_str(), name2.c_str());
FILE* fp = fopen("file.txt", "w");
if(fp) {
fprintf(fp, "Hel1o, %s!\n", name, name1, name2);
if (fp) {
fprintf(fp, "Hello, %s !\n", name, name1.c_str(), name2.c_str());
fclose(fp);
}
string str = format("Hello, {}!\n",name, name1, name2);
string formatted = format("Hello, {} !\n", name, name1, name2);
return 0;
}

@ -1,10 +1,10 @@
#include <array>
class MyClass {
private:
std::array<int, 10> privateArray;
public:
// 假设初始化已经在构造函数或其他地方完成
// 返回的是私有数组的副本
// const std::array<int, 10> getPrivateArrayRef() const {
// return privateArray;

@ -1,10 +1,18 @@
#include <iostream>
#include <random>
#include <ctime>
int main(){
std::mt19937 generator(time(0)); // mt19937是32比特的
std::cout << generator() << std::endl;
std::ranlux24_base generator(time(0)); // ranlux24_base是24比特的
std::cout << generator() << std::endl;
std::knuth_b generator(time(0));
std::random_device rd; // 用于获取非确定性随机种子
std::mt19937 generator(rd()); // 使用更好的随机种子初始化mt19937
std::cout << generator() << std::endl;
std::ranlux24_base rlbGenerator(rd()); // 使用更好的随机种子初始化ranlux24_base
std::cout << rlbGenerator() << std::endl;
std::knuth_b knuthGenerator(rd()); // 使用更好的随机种子初始化knuth_b
std::cout << knuthGenerator() << std::endl;
return 0;
}

@ -1,7 +1,8 @@
#include <array>
int main() {
//std::String a = "/path/to/your/file.txt";
std::String a = "path/to/your/file.txt";
std::String testString1 = "User\\Documents";
//std::string a = "/path/to/your/file.txt";
std::string a = "path/to/your/file.txt";
std::string testString1 = "User\\Documents";
//std::String testString1 = "C:\\Users\\User\\Documents";
return 0;
}

@ -1,22 +1,38 @@
#include <cstring> // 为了使用 memset
struct User {
char name[50];
int age;
};
int main() {
// 假设有一个使用者结构体需要重新分配内存
// User* users = (User*)malloc(5 * sizeof(User));
// 正确初始化指针
User* users = nullptr;
// 首次分配内存
users = new User[5];
// 在重新分配之前,确保已正确初始化或清空(如果需要的话)
// 注意:对于结构体中的对象成员,这可能并不必要,因为它们在新分配的内存中会被自动初始化为默认值
// memset(users, 0, 5 * sizeof(User)); // 如果确实需要清零整个结构体内容可以使用这个,但对于包含对象的数据类型不一定适用
// 在使用realloc()函数重新分配内存块之前,先清空使用者信息
// memset(users, 0, 5 * sizeof(User));
// 使用realloc()函数重新分配内存块 - C++ 中应使用 new 进行动态内存管理
// 用户 struct 是内置类型的组合,在 C++ 中通常不会用 realloc,而是使用 new 进行重新分配
// 如果坚持使用 realloc 的话,这段代码应该在 C 程序中,并且需要先检查 users 是否为 nullptr
// users = (User*)realloc(users, 10 * sizeof(User));
// 使用realloc()函数重新分配内存块
users = (User*)realloc(users, 10 * sizeof(User));//error
// C++ 中使用 new 进行重新分配
User* tempUsers = new User[10];
if (users != nullptr) {
std::memcpy(tempUsers, users, 5 * sizeof(User)); // 复制已有数据到新内存
delete[] users; // 释放旧内存
}
users = tempUsers;
// 继续使用重新分配后的内存块...
// 最后释放内存
// free(users);
delete[] users;
return 0;
}

@ -1,19 +1,53 @@
#include <iostream>
#include <mysql_driver.h> // MySQL Connector/C++库头文件
#include <mysql_connection.h>
// 假设你已经有了一个sanitizeString函数,用于清理SQL注入风险
std::string sanitizeString(const std::string& input) {
// 在这里实现SQL字符串清理逻辑
return cleanedInput;
}
int main() {
try {
sql::mysql::MySQL_Driver *driver;
sql::Connection *con;
// 初始化数据库连接
driver = sql::mysql::get_mysql_driver_instance();
con = driver->connect("tcp://127.0.0.1:3306", "username", "password");
con->setSchema("your_database");
std::string inputQuery = "";
// if(a(inputQuery)){
// }
std::cout << "请输入SQL查询语句: ";
std::getline(std::cin, inputQuery);
// 对输入的SQL语句进行验证和处理
std::string sqlQuery = sanitizeString(inputQuery); // error
std::string sqlQuery = sanitizeString(inputQuery);
// 执行SQL语句
stmt = con->createStatement();
res = stmt->executeQuery(sqlQuery);
// 创建并执行SQL语句
sql::Statement *stmt = con->createStatement();
sql::ResultSet *res = stmt->executeQuery(sqlQuery);
// 处理查询结果
while (res->next()) {
// 从结果集中获取数据并进行处理
std::string resultData = res->getString("column_name");
// 这里假设你知道第一列的名字,如果不是,请替换为实际列名
std::string resultData = res->getString("your_column_name");
std::cout << "查询结果: " << resultData << std::endl;
}
delete stmt;
delete res;
delete con;
}
catch (sql::SQLException &e) {
std::cerr << "# ERR: SQLException in " << __FILE__;
std::cerr << "(" << __FUNCTION__ << ") on line " << __LINE__ << std::endl;
std::cerr << "# ERR: " << e.what();
std::cerr << " (MySQL error code: " << e.getErrorCode();
std::cerr << ", SQLState: " << e.getSQLState() << " )" << std::endl;
}
return 0;
}

@ -1,26 +1,20 @@
#include <iostream>
#include <string>
using namespace std;
// 假设以下两个函数用于检查和验证路径
void checkPath(const std::string& path);
void verifyPath(const std::string& path);
//void func1(){
// string userPath;
// cout << "Enter a path: ";
// cin >> userPath; // 用户输入语句
//
// checkPath(userPath); // 合规,因为已经对userPath进行校验,方法名称包含check
// verifyPath(userPath); // 合规,因为已经对userPath进行校验,方法名称包含verify
// validPath(userPath); // 合规,因为已经对userPath进行校验,方法名称包含valid
//
//}
void main(){
int main(){
string userPath;
cout <<uPath;
cin >> userPath;// error
cout << "Enter a path: ";
cin >> userPath;
// 在获取用户输入之后立即对其进行验证
// checkPath(userPath);
// verifyPath(userPath);
// 违规,因为没有对userPath进行校验
return userPath;
// 示例程序通常在这里处理路径并结束
return 0;
}

Loading…
Cancel
Save