添加配置文件和用户删除功能的实现,包括配置读取、JWT 验证和用户存在性检查
This commit is contained in:
parent
a4aed41ff1
commit
58e93e295b
3
configs/config.json
Normal file
3
configs/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"server_port": 8081
|
||||
}
|
25
include/db/auth.h
Normal file
25
include/db/auth.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef DB_AUTH_H
|
||||
#define DB_AUTH_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
int open_user_db();
|
||||
|
||||
void close_user_db();
|
||||
|
||||
int check_user_exists(const char* user_id, int* result);
|
||||
|
||||
int set_user_password(const char* user_id, const char* password);
|
||||
|
||||
int login(const char* user_id, const char* password, int* result);
|
||||
|
||||
int delete_user(const char* user_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // DB_AUTH_H
|
@ -1,6 +1,12 @@
|
||||
#ifndef SERVER_AUTH_H
|
||||
#define SERVER_AUTH_H
|
||||
|
||||
// Your code here
|
||||
#include "server/types.h"
|
||||
|
||||
int login_handler(mg_connection* conn, void* cbdata);
|
||||
int register_handler(mg_connection* conn, void* cbdata);
|
||||
int delete_handler(mg_connection* conn, void* cbdata);
|
||||
|
||||
extern char* secret;
|
||||
|
||||
#endif // SERVER_AUTH_H
|
19
include/server/config.h
Normal file
19
include/server/config.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef SERVER_CONFIG_H
|
||||
#define SERVER_CONFIG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int server_port;
|
||||
char* secret;
|
||||
} config_t;
|
||||
|
||||
void config_ctor(config_t* this);
|
||||
void config_dtor(config_t* this);
|
||||
|
||||
bool config_read(config_t* this, const char* filename);
|
||||
|
||||
int print_config();
|
||||
|
||||
#endif // SERVER_CONFIG_H
|
@ -1,8 +0,0 @@
|
||||
#ifndef KQM_DEFS_H
|
||||
#define KQM_DEFS_H
|
||||
|
||||
#define CR "\r"
|
||||
#define LF "\n"
|
||||
#define CRLF "\r\n"
|
||||
|
||||
#endif // KQM_DEFS_H
|
@ -1,11 +1,10 @@
|
||||
#ifndef SERVER_TYPES_H
|
||||
#define SERVER_TYPES_H
|
||||
|
||||
#include <civetweb.h>
|
||||
|
||||
typedef struct mg_callbacks mg_callbacks;
|
||||
typedef struct mg_context mg_context;
|
||||
typedef struct mg_connection mg_connection;
|
||||
typedef struct mg_request_info mg_request_info;
|
||||
typedef struct mg_form_data_handler mg_form_data_handler;
|
||||
|
||||
#endif // SERVER_TYPES_H
|
||||
|
6
include/server/util.h
Normal file
6
include/server/util.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef SERVER_UTIL_H
|
||||
#define SERVER_UTIL_H
|
||||
|
||||
char* kqm_strndup(const char* str, size_t n);
|
||||
|
||||
#endif // SERVER_UTIL_H
|
@ -70,10 +70,10 @@ extern "C"
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto logout(const char* user_id) -> int
|
||||
auto delete_user(const char* user_id) -> int
|
||||
{
|
||||
leveldb::WriteOptions write_options;
|
||||
leveldb::Status status = user_db->Delete(write_options, user_id);
|
||||
auto write_options = leveldb::WriteOptions{};
|
||||
auto status = user_db->Delete(write_options, user_id);
|
||||
return status.ok();
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ extern "C"
|
||||
.set_issuer("KeqingMoe")
|
||||
.set_subject("user_id")
|
||||
.set_audience("web_client")
|
||||
.set_expires_at(std::chrono::system_clock::now() + 3s)
|
||||
.set_expires_at(std::chrono::system_clock::now() + 1h)
|
||||
.set_payload_claim("user_id", jwt::claim(std::string{user_id}))
|
||||
.sign(jwt::algorithm::hs256{secret});
|
||||
return strdup(token.c_str());
|
||||
|
5
src/server/auth/auth.c
Normal file
5
src/server/auth/auth.c
Normal file
@ -0,0 +1,5 @@
|
||||
#include <server/auth.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
char* secret = NULL;
|
134
src/server/auth/delete.c
Normal file
134
src/server/auth/delete.c
Normal file
@ -0,0 +1,134 @@
|
||||
#include "server/auth.h"
|
||||
#include "server/util.h"
|
||||
|
||||
#include "db/auth.h"
|
||||
|
||||
#include "jwt/jwt.h"
|
||||
|
||||
#include <civetweb.h>
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* token;
|
||||
} delete_form_t;
|
||||
|
||||
static void delete_form_dtor(delete_form_t* form)
|
||||
{
|
||||
if (form->token) free(form->token);
|
||||
}
|
||||
|
||||
static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data)
|
||||
{
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
static int field_get(const char* key, const char* value, size_t valuelen, void* user_data)
|
||||
{
|
||||
delete_form_t* form = (delete_form_t*)user_data;
|
||||
if (strcmp(key, "token") == 0) {
|
||||
form->token = kqm_strndup(value, valuelen);
|
||||
return MG_FORM_FIELD_HANDLE_ABORT;
|
||||
}
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
|
||||
int delete_handler(mg_connection* conn, void* cbdata)
|
||||
{
|
||||
const mg_request_info* post_body = mg_get_request_info(conn);
|
||||
|
||||
if (post_body == NULL) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n");
|
||||
mg_printf(conn, "{\"error\":\"null request\"}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(post_body->request_method, "POST")) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 405 Method Not Allowed\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n");
|
||||
mg_printf(conn, "{\"error\":\"must use post request\"}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
delete_form_t form = {NULL};
|
||||
|
||||
mg_form_data_handler delete_callback = {
|
||||
.field_found = field_found,
|
||||
.field_get = field_get,
|
||||
.field_store = NULL,
|
||||
.user_data = &form,
|
||||
};
|
||||
|
||||
mg_handle_form_request(conn, &delete_callback);
|
||||
|
||||
if (!form.token) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"need token\"}");
|
||||
delete_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = verify_token(form.token, secret);
|
||||
if (!result) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 401 Unauthorized\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"auth failed\"}");
|
||||
delete_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* user_id = get_payload(form.token);
|
||||
|
||||
int flag = check_user_exists(user_id, &result);
|
||||
if (!flag) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"failed to check user existence\"}");
|
||||
delete_form_dtor(&form);
|
||||
return 1;
|
||||
} else if (!result) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 404 Not Found\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"user does not exist\"}");
|
||||
delete_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
flag = delete_user(user_id);
|
||||
if (!flag) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"failed to delete account\"}");
|
||||
} else {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"delete account success\"}");
|
||||
}
|
||||
free(user_id);
|
||||
delete_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
145
src/server/auth/login.c
Normal file
145
src/server/auth/login.c
Normal file
@ -0,0 +1,145 @@
|
||||
#include "server/auth.h"
|
||||
#include "server/util.h"
|
||||
|
||||
#include "db/auth.h"
|
||||
|
||||
#include "jwt/jwt.h"
|
||||
|
||||
#include <civetweb.h>
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* user_id;
|
||||
char* password;
|
||||
} login_form_t;
|
||||
|
||||
static void login_form_dtor(login_form_t* form)
|
||||
{
|
||||
if (form->user_id) free(form->user_id);
|
||||
if (form->password) free(form->password);
|
||||
}
|
||||
|
||||
static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data)
|
||||
{
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
static int field_get(const char* key, const char* value, size_t valuelen, void* user_data)
|
||||
{
|
||||
login_form_t* form = (login_form_t*)user_data;
|
||||
if (strcmp(key, "user_id") == 0) {
|
||||
form->user_id = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "password") == 0) {
|
||||
form->password = kqm_strndup(value, valuelen);
|
||||
}
|
||||
if (form->user_id && form->password) {
|
||||
return MG_FORM_FIELD_HANDLE_ABORT;
|
||||
}
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
|
||||
int login_handler(mg_connection* conn, void* cbdata)
|
||||
{
|
||||
const mg_request_info* post_body = mg_get_request_info(conn);
|
||||
|
||||
if (post_body == NULL) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n");
|
||||
mg_printf(conn, "{\"error\":\"null request\"}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(post_body->request_method, "POST")) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 405 Method Not Allowed\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n");
|
||||
mg_printf(conn, "{\"error\":\"must use post request\"}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
login_form_t form = {NULL, NULL};
|
||||
|
||||
mg_form_data_handler login_callback = {
|
||||
.field_found = field_found,
|
||||
.field_get = field_get,
|
||||
.field_store = NULL,
|
||||
.user_data = &form,
|
||||
};
|
||||
|
||||
mg_handle_form_request(conn, &login_callback);
|
||||
|
||||
if (!form.user_id) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"need user_id\"}");
|
||||
login_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
if (!form.password) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"need password\"}");
|
||||
login_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result;
|
||||
int flag = check_user_exists(form.user_id, &result);
|
||||
if (!flag) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"failed to check user existence\"}");
|
||||
login_form_dtor(&form);
|
||||
return 1;
|
||||
} else if (!result) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 404 Not Found\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"user does not exist\"}");
|
||||
login_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
flag = login(form.user_id, form.password, &result);
|
||||
if (!flag) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"failed to login\"}");
|
||||
} else if (!result) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 401 Unauthorized\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"incorrect password or user id\"}");
|
||||
} else {
|
||||
char* token = create_token(form.user_id, secret);
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"token\":\"%s\"}",
|
||||
token);
|
||||
free(token);
|
||||
}
|
||||
login_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
134
src/server/auth/register.c
Normal file
134
src/server/auth/register.c
Normal file
@ -0,0 +1,134 @@
|
||||
#include "server/auth.h"
|
||||
#include "server/util.h"
|
||||
|
||||
#include "db/auth.h"
|
||||
|
||||
#include <civetweb.h>
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* user_id;
|
||||
char* password;
|
||||
} register_form_t;
|
||||
|
||||
static void register_form_dtor(register_form_t* form)
|
||||
{
|
||||
if (form->user_id) free(form->user_id);
|
||||
if (form->password) free(form->password);
|
||||
}
|
||||
|
||||
static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data)
|
||||
{
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
static int field_get(const char* key, const char* value, size_t valuelen, void* user_data)
|
||||
{
|
||||
register_form_t* form = (register_form_t*)user_data;
|
||||
if (strcmp(key, "user_id") == 0) {
|
||||
form->user_id = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "password") == 0) {
|
||||
form->password = kqm_strndup(value, valuelen);
|
||||
}
|
||||
if (form->user_id && form->password) {
|
||||
return MG_FORM_FIELD_HANDLE_ABORT;
|
||||
}
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
|
||||
int register_handler(mg_connection* conn, void* cbdata)
|
||||
{
|
||||
const mg_request_info* post_body = mg_get_request_info(conn);
|
||||
|
||||
if (post_body == NULL) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n");
|
||||
mg_printf(conn, "{\"error\":\"null request\"}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(post_body->request_method, "POST")) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 405 Method Not Allowed\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n");
|
||||
mg_printf(conn, "{\"error\":\"must use post request\"}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
register_form_t form = {NULL, NULL};
|
||||
|
||||
mg_form_data_handler register_callback = {
|
||||
.field_found = field_found,
|
||||
.field_get = field_get,
|
||||
.field_store = NULL,
|
||||
.user_data = &form,
|
||||
};
|
||||
|
||||
mg_handle_form_request(conn, ®ister_callback);
|
||||
|
||||
if (!form.user_id) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"need user_id\"}");
|
||||
register_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
if (!form.password) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"need password\"}");
|
||||
register_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result;
|
||||
int flag = check_user_exists(form.user_id, &result);
|
||||
if (!flag) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"failed to check user existence\"}");
|
||||
register_form_dtor(&form);
|
||||
return 1;
|
||||
} else if (result) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 409 Conflict\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"user already exists\"}");
|
||||
register_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
flag = set_user_password(form.user_id, form.password);
|
||||
if (!flag) {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"failed to register\"}");
|
||||
} else {
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"registration success\"}");
|
||||
}
|
||||
register_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
77
src/server/config.c
Normal file
77
src/server/config.c
Normal file
@ -0,0 +1,77 @@
|
||||
#include "server/config.h"
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
char* read_file(const char* filename)
|
||||
{
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (file == NULL) {
|
||||
printf("Error opening file\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long file_size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
char* buffer = (char*)malloc(file_size + 1);
|
||||
if (buffer == NULL) {
|
||||
printf("Memory allocation failed\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fread(buffer, 1, file_size, file);
|
||||
buffer[file_size] = '\0';
|
||||
|
||||
fclose(file);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void config_ctor(config_t* this)
|
||||
{
|
||||
this->server_port = 0;
|
||||
this->secret = NULL;
|
||||
}
|
||||
|
||||
void config_dtor(config_t* this)
|
||||
{
|
||||
if (this->secret) free(this->secret);
|
||||
}
|
||||
|
||||
bool config_read(config_t* this, const char* filename)
|
||||
{
|
||||
char* json_data = read_file(filename);
|
||||
if (json_data == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_Parse(json_data);
|
||||
if (root == NULL) {
|
||||
free(json_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON* server_port = cJSON_GetObjectItem(root, "server_port");
|
||||
if (cJSON_IsNumber(server_port)) {
|
||||
this->server_port = server_port->valueint;
|
||||
} else {
|
||||
this->server_port = 80;
|
||||
}
|
||||
|
||||
cJSON* secret = cJSON_GetObjectItem(root, "secret");
|
||||
if (cJSON_IsString(secret) && secret->valuestring != NULL) {
|
||||
this->secret = strdup(secret->valuestring);
|
||||
} else {
|
||||
this->secret = strdup("me@keqing.moe");
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
free(json_data);
|
||||
|
||||
return true;
|
||||
}
|
13
src/server/util.c
Normal file
13
src/server/util.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include "server/util.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
char* kqm_strndup(const char* str, size_t n)
|
||||
{
|
||||
char* s = (char*)malloc(n + 1);
|
||||
memcpy(s, str, n);
|
||||
s[n] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user