mirror of
https://github.com/lammertb/libhttp.git
synced 2025-07-29 21:01:13 +03:00
Added new URI to handler mapping for C code.
This commit is contained in:
@ -1,7 +1,12 @@
|
|||||||
Release Notes v1.4 (UNDER DEVELOPMENT)
|
Release Notes v1.4 (UNDER DEVELOPMENT)
|
||||||
===
|
===
|
||||||
### Objectives: *???*
|
### Objectives: *???*
|
||||||
|
The ma
|
||||||
|
|
||||||
|
- Added mg_set_request_handler() which provides a URI mapping for callbacks.
|
||||||
|
This is a new alternative to overriding callbacks.begin_request.
|
||||||
|
- Externalized mg_url_encode()
|
||||||
|
- Externalized mg_strncasecmp() for utiliy
|
||||||
- Added CivetServer::getParam methods
|
- Added CivetServer::getParam methods
|
||||||
- Added CivetServer::urlDecode methods
|
- Added CivetServer::urlDecode methods
|
||||||
- Added CivetServer::urlEncode methods
|
- Added CivetServer::urlEncode methods
|
||||||
|
@ -51,7 +51,7 @@ int main(int argc, char *argv[]) {
|
|||||||
server.addHandler(EXIT_URI, new ExitHandler());
|
server.addHandler(EXIT_URI, new ExitHandler());
|
||||||
|
|
||||||
printf("Browse files at http://localhost:%s/\n", PORT);
|
printf("Browse files at http://localhost:%s/\n", PORT);
|
||||||
printf("Run example at http://localhost:%s%s\n", PORT, EXIT_URI);
|
printf("Run example at http://localhost:%s%s\n", PORT, EXAMPLE_URI);
|
||||||
printf("Exit at http://localhost:%s%s\n", PORT, EXIT_URI);
|
printf("Exit at http://localhost:%s%s\n", PORT, EXIT_URI);
|
||||||
|
|
||||||
while (!exitNow) {
|
while (!exitNow) {
|
||||||
|
@ -81,7 +81,6 @@ public:
|
|||||||
*
|
*
|
||||||
* @param options - the web server options.
|
* @param options - the web server options.
|
||||||
* @param callbacks - optional web server callback methods.
|
* @param callbacks - optional web server callback methods.
|
||||||
* Note that this class overrides begin_request callback.
|
|
||||||
*/
|
*/
|
||||||
CivetServer(const char **options, const struct mg_callbacks *callbacks = 0);
|
CivetServer(const char **options, const struct mg_callbacks *callbacks = 0);
|
||||||
|
|
||||||
@ -110,8 +109,7 @@ public:
|
|||||||
* addHandler(const std::string &, CivetHandler *)
|
* addHandler(const std::string &, CivetHandler *)
|
||||||
*
|
*
|
||||||
* Adds a URI handler. If there is existing URI handler, it will
|
* Adds a URI handler. If there is existing URI handler, it will
|
||||||
* be replaced with this one. The handler is "owned" by this server
|
* be replaced with this one.
|
||||||
* and will be deallocated with it.
|
|
||||||
*
|
*
|
||||||
* URI's are ordered and partcial URI's are supported. For example,
|
* URI's are ordered and partcial URI's are supported. For example,
|
||||||
* consider two URIs in order: /a/b and /a; /a matches
|
* consider two URIs in order: /a/b and /a; /a matches
|
||||||
@ -127,31 +125,12 @@ public:
|
|||||||
/**
|
/**
|
||||||
* removeHandler(const std::string &)
|
* removeHandler(const std::string &)
|
||||||
*
|
*
|
||||||
* Removes a handler, deleting it if found.
|
* Removes a handler.
|
||||||
*
|
*
|
||||||
* @param - the exact URL used in addHandler().
|
* @param - the exact URL used in addHandler().
|
||||||
*/
|
*/
|
||||||
void removeHandler(const std::string &uri);
|
void removeHandler(const std::string &uri);
|
||||||
|
|
||||||
/**
|
|
||||||
* getHandler(const std::string &uri)
|
|
||||||
*
|
|
||||||
* @param uri - the URI
|
|
||||||
* @returns the handler that matches the requested URI or 0 if none were found.
|
|
||||||
*/
|
|
||||||
CivetHandler *getHandler(const std::string &uri) const {
|
|
||||||
return getHandler(uri.data(), (unsigned int)uri.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getHandler(const char *uri, unsigned urilen)
|
|
||||||
*
|
|
||||||
* @param uri - the URI
|
|
||||||
* @param urilen - the length of the URI
|
|
||||||
* @returns the handler that matches the requested URI or 0 if none were found.
|
|
||||||
*/
|
|
||||||
CivetHandler *getHandler(const char *uri, unsigned urilen) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getCookie(struct mg_connection *conn, const std::string &cookieName, std::string &cookieValue)
|
* getCookie(struct mg_connection *conn, const std::string &cookieName, std::string &cookieValue)
|
||||||
* @param conn - the connection information
|
* @param conn - the connection information
|
||||||
@ -293,30 +272,19 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
struct mg_context *context;
|
||||||
|
|
||||||
|
private:
|
||||||
/**
|
/**
|
||||||
* handleRequest(struct mg_connection *)
|
* requestHandler(struct mg_connection *, void *cbdata)
|
||||||
*
|
*
|
||||||
* Handles the incomming request.
|
* Handles the incomming request.
|
||||||
*
|
*
|
||||||
* @param conn - the connection information
|
* @param conn - the connection information
|
||||||
* @returns true if implemented, false otherwise
|
* @param cbdata - pointer to the CivetHandler instance.
|
||||||
|
* @returns 0 if implemented, false otherwise
|
||||||
*/
|
*/
|
||||||
virtual bool handleRequest(struct mg_connection *conn);
|
static int requestHandler(struct mg_connection *conn, void *cbdata);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the handler that matches the
|
|
||||||
* URI exactly.
|
|
||||||
*
|
|
||||||
* @param uri - the url to match
|
|
||||||
*/
|
|
||||||
int getIndex(const std::string &uri) const;
|
|
||||||
|
|
||||||
std::vector<std::string> uris;
|
|
||||||
std::vector<CivetHandler *> handlers;
|
|
||||||
struct mg_context *context;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int begin_request_callback(struct mg_connection *conn);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -168,6 +168,36 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks,
|
|||||||
// threads are stopped. Context pointer becomes invalid.
|
// threads are stopped. Context pointer becomes invalid.
|
||||||
void mg_stop(struct mg_context *);
|
void mg_stop(struct mg_context *);
|
||||||
|
|
||||||
|
// mg_request_handler
|
||||||
|
//
|
||||||
|
// Called when a new request comes in. This callback is URI based
|
||||||
|
// and configured with mg_sethandler().
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// conn: current connection information.
|
||||||
|
// cbdata: the callback data configured with mg_sethandler().
|
||||||
|
// Returns:
|
||||||
|
// 0: the handler could not handle the request, so fall through.
|
||||||
|
// 1: the handler processed the request.
|
||||||
|
typedef int (* mg_request_handler)(struct mg_connection *conn, void *cbdata);
|
||||||
|
|
||||||
|
// mg_set_request_handler
|
||||||
|
//
|
||||||
|
// Sets or removes a URI mapping for a request handler.
|
||||||
|
//
|
||||||
|
// URI's are ordered and partcial URI's are supported. For example,
|
||||||
|
// consider two URIs in order: /a/b and /a; /a matches
|
||||||
|
// /a, /a/b matches /a/b, /a/c matches /a. Reversing the order to
|
||||||
|
// /a and /a/b; /a matches /a/b, /a/b matches /a. /a/c matches /a.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// ctx: server context
|
||||||
|
// uri: the URI to configure
|
||||||
|
// handler: the callback handler to use when the URI is requested.
|
||||||
|
// If NULL, the URI will be removed.
|
||||||
|
// cbdata: the callback data to give to the handler when it s requested.
|
||||||
|
void mg_set_request_handler(struct mg_context *ctx, const char *uri, mg_request_handler handler, void *cbdata);
|
||||||
|
|
||||||
|
|
||||||
// Get the value of particular configuration parameter.
|
// Get the value of particular configuration parameter.
|
||||||
// The value returned is read-only. Civetweb does not allow changing
|
// The value returned is read-only. Civetweb does not allow changing
|
||||||
|
@ -37,109 +37,51 @@ bool CivetHandler::handleDelete(CivetServer *server, struct mg_connection *conn)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CivetServer::begin_request_callback(struct mg_connection *conn) {
|
int CivetServer::requestHandler(struct mg_connection *conn, void *cbdata) {
|
||||||
struct mg_request_info *request_info = mg_get_request_info(conn);
|
struct mg_request_info *request_info = mg_get_request_info(conn);
|
||||||
|
|
||||||
if (!request_info->user_data)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
CivetServer *me = (CivetServer*) (request_info->user_data);
|
CivetServer *me = (CivetServer*) (request_info->user_data);
|
||||||
|
CivetHandler *handler = (CivetHandler *)cbdata;
|
||||||
|
|
||||||
if (me->handleRequest(conn)) {
|
|
||||||
return 1; // Mark as processed
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CivetServer::handleRequest(struct mg_connection *conn) {
|
|
||||||
struct mg_request_info *request_info = mg_get_request_info(conn);
|
|
||||||
|
|
||||||
CivetHandler *handler = getHandler(request_info->uri);
|
|
||||||
if (handler) {
|
if (handler) {
|
||||||
if (strcmp(request_info->request_method, "GET") == 0) {
|
if (strcmp(request_info->request_method, "GET") == 0) {
|
||||||
return handler->handleGet(this, conn);
|
return handler->handleGet(me, conn) ? 1 : 0;
|
||||||
} else if (strcmp(request_info->request_method, "POST") == 0) {
|
} else if (strcmp(request_info->request_method, "POST") == 0) {
|
||||||
return handler->handlePost(this, conn);
|
return handler->handlePost(me, conn) ? 1 : 0;
|
||||||
} else if (strcmp(request_info->request_method, "PUT") == 0) {
|
} else if (strcmp(request_info->request_method, "PUT") == 0) {
|
||||||
return !handler->handlePost(this, conn);
|
return !handler->handlePut(me, conn) ? 1 : 0;
|
||||||
} else if (strcmp(request_info->request_method, "DELETE") == 0) {
|
} else if (strcmp(request_info->request_method, "DELETE") == 0) {
|
||||||
return !handler->handlePost(this, conn);
|
return !handler->handleDelete(me, conn) ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; // No handler found
|
return 0; // No handler found
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CivetServer::CivetServer(const char **options,
|
CivetServer::CivetServer(const char **options,
|
||||||
const struct mg_callbacks *_callbacks) :
|
const struct mg_callbacks *_callbacks) :
|
||||||
context(0) {
|
context(0) {
|
||||||
|
|
||||||
struct mg_callbacks callbacks;
|
|
||||||
|
|
||||||
if (_callbacks) {
|
if (_callbacks) {
|
||||||
memcpy(&callbacks, _callbacks, sizeof(callbacks));
|
context = mg_start(_callbacks, this, options);
|
||||||
} else {
|
} else {
|
||||||
|
struct mg_callbacks callbacks;
|
||||||
memset(&callbacks, 0, sizeof(callbacks));
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
|
context = mg_start(&callbacks, this, options);
|
||||||
}
|
}
|
||||||
callbacks.begin_request = &begin_request_callback;
|
|
||||||
|
|
||||||
context = mg_start(&callbacks, this, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CivetServer::~CivetServer() {
|
CivetServer::~CivetServer() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
CivetHandler *CivetServer::getHandler(const char *uri, unsigned urilen) const {
|
|
||||||
|
|
||||||
for (unsigned index = 0; index < uris.size(); index++) {
|
|
||||||
const std::string &handlerURI = uris[index];
|
|
||||||
|
|
||||||
// first try for an exact match
|
|
||||||
if (handlerURI == uri) {
|
|
||||||
return handlers[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
// next try for a partial match
|
|
||||||
// we will accept uri/something
|
|
||||||
if (handlerURI.length() < urilen
|
|
||||||
&& uri[handlerURI.length()] == '/'
|
|
||||||
&& handlerURI.compare(0, handlerURI.length(), uri, handlerURI.length()) == 0) {
|
|
||||||
|
|
||||||
return handlers[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // none found
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CivetServer::addHandler(const std::string &uri, CivetHandler *handler) {
|
void CivetServer::addHandler(const std::string &uri, CivetHandler *handler) {
|
||||||
int index = getIndex(uri);
|
mg_set_request_handler(context, uri.c_str(), requestHandler, handler);
|
||||||
if (index < 0) {
|
|
||||||
uris.push_back(uri);
|
|
||||||
handlers.push_back(handler);
|
|
||||||
} else if (handlers[index] != handler) {
|
|
||||||
delete handlers[index];
|
|
||||||
handlers[index] = handler;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CivetServer::removeHandler(const std::string &uri) {
|
void CivetServer::removeHandler(const std::string &uri) {
|
||||||
int index = getIndex(uri);
|
mg_set_request_handler(context, uri.c_str(), NULL, NULL);
|
||||||
if (index >= 0) {
|
|
||||||
uris.erase(uris.begin() + index, uris.begin() + index + 1);
|
|
||||||
handlers.erase(handlers.begin() + index, handlers.begin() + index + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int CivetServer::getIndex(const std::string &uri) const {
|
|
||||||
for (unsigned index = 0; index < uris.size(); index++) {
|
|
||||||
if (uris[index].compare(uri) == 0)
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CivetServer::close() {
|
void CivetServer::close() {
|
||||||
@ -147,12 +89,6 @@ void CivetServer::close() {
|
|||||||
mg_stop (context);
|
mg_stop (context);
|
||||||
context = 0;
|
context = 0;
|
||||||
}
|
}
|
||||||
for (int i = (int) handlers.size() - 1; i >= 0; i--) {
|
|
||||||
delete handlers[i];
|
|
||||||
}
|
|
||||||
handlers.clear();
|
|
||||||
uris.clear();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int CivetServer::getCookie(struct mg_connection *conn, const std::string &cookieName, std::string &cookieValue)
|
int CivetServer::getCookie(struct mg_connection *conn, const std::string &cookieName, std::string &cookieValue)
|
||||||
|
100
src/civetweb.c
100
src/civetweb.c
@ -494,6 +494,14 @@ static const char *config_options[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mg_request_handler_info {
|
||||||
|
char *uri;
|
||||||
|
size_t uri_len;
|
||||||
|
mg_request_handler handler;
|
||||||
|
void *cbdata;
|
||||||
|
struct mg_request_handler_info *next;
|
||||||
|
};
|
||||||
|
|
||||||
struct mg_context {
|
struct mg_context {
|
||||||
volatile int stop_flag; // Should we stop event loop
|
volatile int stop_flag; // Should we stop event loop
|
||||||
SSL_CTX *ssl_ctx; // SSL context
|
SSL_CTX *ssl_ctx; // SSL context
|
||||||
@ -513,6 +521,9 @@ struct mg_context {
|
|||||||
volatile int sq_tail; // Tail of the socket queue
|
volatile int sq_tail; // Tail of the socket queue
|
||||||
pthread_cond_t sq_full; // Signaled when socket is produced
|
pthread_cond_t sq_full; // Signaled when socket is produced
|
||||||
pthread_cond_t sq_empty; // Signaled when socket is consumed
|
pthread_cond_t sq_empty; // Signaled when socket is consumed
|
||||||
|
|
||||||
|
// linked list of uri handlers
|
||||||
|
struct mg_request_handler_info *request_handlers;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mg_connection {
|
struct mg_connection {
|
||||||
@ -4265,6 +4276,81 @@ static void redirect_to_https_port(struct mg_connection *conn, int ssl_index) {
|
|||||||
lsa.sin.sin_port), conn->request_info.uri);
|
lsa.sin.sin_port), conn->request_info.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void mg_set_request_handler(struct mg_context *ctx, const char *uri, mg_request_handler handler, void *cbdata) {
|
||||||
|
struct mg_request_handler_info *tmp_rh, *lastref = 0;
|
||||||
|
|
||||||
|
// first see it the uri exists
|
||||||
|
for (tmp_rh = ctx->request_handlers;
|
||||||
|
tmp_rh != NULL && strcmp(uri, tmp_rh->uri);
|
||||||
|
lastref = tmp_rh, tmp_rh = tmp_rh->next)
|
||||||
|
;
|
||||||
|
|
||||||
|
if (tmp_rh != NULL) {
|
||||||
|
// already there...
|
||||||
|
|
||||||
|
if (handler != NULL) {
|
||||||
|
// change this entry
|
||||||
|
tmp_rh->handler = handler;
|
||||||
|
tmp_rh->cbdata = cbdata;
|
||||||
|
} else {
|
||||||
|
// remove this entry
|
||||||
|
if (lastref != NULL)
|
||||||
|
lastref->next = tmp_rh->next;
|
||||||
|
else
|
||||||
|
ctx->request_handlers = tmp_rh->next;
|
||||||
|
free(tmp_rh->uri);
|
||||||
|
free(tmp_rh);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler == NULL) {
|
||||||
|
// no handler to set, this was a remove request
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp_rh = (struct mg_request_handler_info *)malloc(sizeof(struct mg_request_handler_info));
|
||||||
|
tmp_rh->uri = mg_strdup(uri);
|
||||||
|
tmp_rh->uri_len = strlen(uri);
|
||||||
|
tmp_rh->handler = handler;
|
||||||
|
tmp_rh->cbdata = cbdata;
|
||||||
|
tmp_rh->next = NULL;
|
||||||
|
|
||||||
|
if (lastref == NULL)
|
||||||
|
ctx->request_handlers = tmp_rh;
|
||||||
|
else
|
||||||
|
lastref->next = tmp_rh;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int use_request_handler(struct mg_connection *conn) {
|
||||||
|
struct mg_request_info *request_info = mg_get_request_info(conn);
|
||||||
|
const char *uri = request_info->uri;
|
||||||
|
size_t urilen = strlen(uri);
|
||||||
|
struct mg_request_handler_info *tmp_rh = conn->ctx->request_handlers;
|
||||||
|
|
||||||
|
for (; tmp_rh != NULL; tmp_rh = tmp_rh->next) {
|
||||||
|
|
||||||
|
// first try for an exact match
|
||||||
|
if (urilen == tmp_rh->uri_len && !strcmp(tmp_rh->uri,uri)) {
|
||||||
|
return tmp_rh->handler(conn, tmp_rh->cbdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// next try for a partial match
|
||||||
|
// we will accept uri/something
|
||||||
|
if (tmp_rh->uri_len < urilen
|
||||||
|
&& uri[tmp_rh->uri_len] == '/'
|
||||||
|
&& memcmp(tmp_rh->uri, uri, tmp_rh->uri_len) == 0) {
|
||||||
|
|
||||||
|
return tmp_rh->handler(conn, tmp_rh->cbdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // none found
|
||||||
|
}
|
||||||
|
|
||||||
// This is the heart of the Civetweb's logic.
|
// This is the heart of the Civetweb's logic.
|
||||||
// This function is called when the request is read, parsed and validated,
|
// This function is called when the request is read, parsed and validated,
|
||||||
// and Civetweb must decide what action to take: serve a file, or
|
// and Civetweb must decide what action to take: serve a file, or
|
||||||
@ -4297,6 +4383,9 @@ static void handle_request(struct mg_connection *conn) {
|
|||||||
} else if (conn->ctx->callbacks.begin_request != NULL &&
|
} else if (conn->ctx->callbacks.begin_request != NULL &&
|
||||||
conn->ctx->callbacks.begin_request(conn)) {
|
conn->ctx->callbacks.begin_request(conn)) {
|
||||||
// Do nothing, callback has served the request
|
// Do nothing, callback has served the request
|
||||||
|
} else if (conn->ctx->request_handlers != NULL &&
|
||||||
|
use_request_handler(conn)) {
|
||||||
|
// Do nothing, callback has served the request
|
||||||
#if defined(USE_WEBSOCKET)
|
#if defined(USE_WEBSOCKET)
|
||||||
} else if (is_websocket_request(conn)) {
|
} else if (is_websocket_request(conn)) {
|
||||||
handle_websocket_request(conn);
|
handle_websocket_request(conn);
|
||||||
@ -5182,6 +5271,8 @@ static void *master_thread(void *thread_func_param) {
|
|||||||
|
|
||||||
static void free_context(struct mg_context *ctx) {
|
static void free_context(struct mg_context *ctx) {
|
||||||
int i;
|
int i;
|
||||||
|
struct mg_request_handler_info *tmp_rh;
|
||||||
|
|
||||||
if (ctx == NULL)
|
if (ctx == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -5194,6 +5285,14 @@ static void free_context(struct mg_context *ctx) {
|
|||||||
free(ctx->config[i]);
|
free(ctx->config[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deallocate request handlers
|
||||||
|
while (ctx->request_handlers) {
|
||||||
|
tmp_rh = ctx->request_handlers;
|
||||||
|
ctx->request_handlers = tmp_rh->next;
|
||||||
|
free(tmp_rh->uri);
|
||||||
|
free(tmp_rh);
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef NO_SSL
|
#ifndef NO_SSL
|
||||||
// Deallocate SSL context
|
// Deallocate SSL context
|
||||||
if (ctx->ssl_ctx != NULL) {
|
if (ctx->ssl_ctx != NULL) {
|
||||||
@ -5244,6 +5343,7 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks,
|
|||||||
}
|
}
|
||||||
ctx->callbacks = *callbacks;
|
ctx->callbacks = *callbacks;
|
||||||
ctx->user_data = user_data;
|
ctx->user_data = user_data;
|
||||||
|
ctx->request_handlers = 0;
|
||||||
|
|
||||||
while (options && (name = *options++) != NULL) {
|
while (options && (name = *options++) != NULL) {
|
||||||
if ((i = get_option_index(name)) == -1) {
|
if ((i = get_option_index(name)) == -1) {
|
||||||
|
Reference in New Issue
Block a user