diff options
author | Maximilian Krambach <[email protected]> | 2018-04-20 13:23:57 +0000 |
---|---|---|
committer | Maximilian Krambach <[email protected]> | 2018-04-20 13:23:57 +0000 |
commit | 94f21d9f6bc1cf94e068e26eae53e593189dcec6 (patch) | |
tree | b176a836c504cbd71a5ca17ca025b23285d1505a /src/gpgme-json.c | |
parent | js: Initial commit for JavaScript Native Messaging API (diff) | |
parent | core: Do not modify args for ignored failures (diff) | |
download | gpgme-94f21d9f6bc1cf94e068e26eae53e593189dcec6.tar.gz gpgme-94f21d9f6bc1cf94e068e26eae53e593189dcec6.zip |
Merge branch 'master' into javascript-binding
Diffstat (limited to 'src/gpgme-json.c')
-rw-r--r-- | src/gpgme-json.c | 568 |
1 files changed, 482 insertions, 86 deletions
diff --git a/src/gpgme-json.c b/src/gpgme-json.c index b54d9a8a..f1e9f256 100644 --- a/src/gpgme-json.c +++ b/src/gpgme-json.c @@ -20,8 +20,7 @@ /* This is tool implements the Native Messaging protocol of web * browsers and provides the server part of it. A Javascript based - * client can be found in lang/javascript. The used data format is - * similar to the API of openpgpjs. + * client can be found in lang/javascript. */ #include <config.h> @@ -33,6 +32,7 @@ #include <locale.h> #endif #include <stdint.h> +#include <sys/stat.h> #define GPGRT_ENABLE_ES_MACROS 1 #define GPGRT_ENABLE_LOG_MACROS 1 @@ -48,6 +48,16 @@ int main (void){fputs ("Build with Libgpg-error >= 1.28!\n", stderr);return 1;} /* We don't allow a request with more than 64 MiB. */ #define MAX_REQUEST_SIZE (64 * 1024 * 1024) +/* Minimal, default and maximum chunk size for returned data. The + * first chunk is returned directly. If the "more" flag is also + * returned, a "getmore" command needs to be used to get the next + * chunk. Right now this value covers just the value of the "data" + * element; so to cover for the other returned objects this values + * needs to be lower than the maximum allowed size of the browser. */ +#define MIN_REPLY_CHUNK_SIZE 512 +#define DEF_REPLY_CHUNK_SIZE (512 * 1024) +#define MAX_REPLY_CHUNK_SIZE (10 * 1024 * 1024) + static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN; static cjson_t error_object_v (cjson_t json, const char *message, @@ -63,6 +73,16 @@ static int opt_interactive; /* True is debug mode is active. */ static int opt_debug; +/* Pending data to be returned by a getmore command. */ +static struct +{ + char *buffer; /* Malloced data or NULL if not used. */ + size_t length; /* Length of that data. */ + size_t written; /* # of already written bytes from BUFFER. */ + const char *type;/* The "type" of the data. */ + int base64; /* The "base64" flag of the data. */ +} pending_data; + /* * Helper functions and macros @@ -94,6 +114,19 @@ static int opt_debug; #define spacep(p) (*(p) == ' ' || *(p) == '\t') +#ifndef HAVE_STPCPY +static GPGRT_INLINE char * +_my_stpcpy (char *a, const char *b) +{ + while (*b) + *a++ = *b++; + *a = 0; + return a; +} +#define stpcpy(a,b) _my_stpcpy ((a), (b)) +#endif /*!HAVE_STPCPY*/ + + static void xoutofcore (const char *type) @@ -146,11 +179,12 @@ xjson_AddBoolToObject (cjson_t object, const char *name, int abool) return ; } -/* This is similar to cJSON_AddStringToObject but takes a gpgme DATA - * object and adds it under NAME as a base 64 encoded string to - * OBJECT. Ownership of DATA is transferred to this function. */ +/* This is similar to cJSON_AddStringToObject but takes (DATA, + * DATALEN) and adds it under NAME as a base 64 encoded string to + * OBJECT. */ static gpg_error_t -add_base64_to_object (cjson_t object, const char *name, gpgme_data_t data) +add_base64_to_object (cjson_t object, const char *name, + const void *data, size_t datalen) { #if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */ return gpg_error (GPG_ERR_NOT_SUPPORTED); @@ -160,7 +194,6 @@ add_base64_to_object (cjson_t object, const char *name, gpgme_data_t data) gpgrt_b64state_t state = NULL; cjson_t j_str = NULL; void *buffer = NULL; - size_t buflen; fp = es_fopenmem (0, "rwb"); if (!fp) @@ -175,20 +208,9 @@ add_base64_to_object (cjson_t object, const char *name, gpgme_data_t data) goto leave; } - gpgme_data_write (data, "", 1); /* Make sure we have a string. */ - buffer = gpgme_data_release_and_get_mem (data, &buflen); - data = NULL; - if (!buffer) - { - err = gpg_error_from_syserror (); - goto leave; - } - - err = gpgrt_b64enc_write (state, buffer, buflen); + err = gpgrt_b64enc_write (state, data, datalen); if (err) goto leave; - xfree (buffer); - buffer = NULL; err = gpgrt_b64enc_finish (state); state = NULL; @@ -226,7 +248,6 @@ add_base64_to_object (cjson_t object, const char *name, gpgme_data_t data) cJSON_Delete (j_str); gpgrt_b64enc_finish (state); es_fclose (fp); - gpgme_data_release (data); return err; #endif } @@ -354,18 +375,42 @@ get_protocol (cjson_t json, gpgme_protocol_t *r_protocol) } -/* Extract the keys from the KEYS array in the JSON object. CTX is a - * GPGME context object. On success an array with the keys is stored - * at R_KEYS. In failure an error code is returned. */ +/* Get the chunksize from JSON and store it at R_CHUNKSIZE. */ static gpg_error_t -get_keys (gpgme_ctx_t ctx, cjson_t json, gpgme_key_t **r_keys) +get_chunksize (cjson_t json, size_t *r_chunksize) +{ + cjson_t j_item; + + *r_chunksize = DEF_REPLY_CHUNK_SIZE; + j_item = cJSON_GetObjectItem (json, "chunksize"); + if (!j_item) + ; + else if (!cjson_is_number (j_item)) + return gpg_error (GPG_ERR_INV_VALUE); + else if ((size_t)j_item->valueint < MIN_REPLY_CHUNK_SIZE) + *r_chunksize = MIN_REPLY_CHUNK_SIZE; + else if ((size_t)j_item->valueint > MAX_REPLY_CHUNK_SIZE) + *r_chunksize = MAX_REPLY_CHUNK_SIZE; + else + *r_chunksize = (size_t)j_item->valueint; + + return 0; +} + + +/* Extract the keys from the "keys" array in the JSON object. On + * success a string with the keys identifiers is stored at R_KEYS. + * The keys in that string are LF delimited. On failure an error code + * is returned. */ +static gpg_error_t +get_keys (cjson_t json, char **r_keystring) { - gpg_error_t err; cjson_t j_keys, j_item; int i, nkeys; - gpgme_key_t *keys; + char *p; + size_t length; - *r_keys = NULL; + *r_keystring = NULL; j_keys = cJSON_GetObjectItem (json, "keys"); if (!j_keys) @@ -373,8 +418,15 @@ get_keys (gpgme_ctx_t ctx, cjson_t json, gpgme_key_t **r_keys) if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys)) return gpg_error (GPG_ERR_INV_VALUE); + /* Fixme: We should better use a membuf like thing. */ + length = 1; /* For the EOS. */ if (cjson_is_string (j_keys)) - nkeys = 1; + { + nkeys = 1; + length += strlen (j_keys->valuestring); + if (strchr (j_keys->valuestring, '\n')) + return gpg_error (GPG_ERR_INV_USER_ID); + } else { nkeys = cJSON_GetArraySize (j_keys); @@ -385,43 +437,37 @@ get_keys (gpgme_ctx_t ctx, cjson_t json, gpgme_key_t **r_keys) j_item = cJSON_GetArrayItem (j_keys, i); if (!j_item || !cjson_is_string (j_item)) return gpg_error (GPG_ERR_INV_VALUE); + if (i) + length++; /* Space for delimiter. */ + length += strlen (j_item->valuestring); + if (strchr (j_item->valuestring, '\n')) + return gpg_error (GPG_ERR_INV_USER_ID); } } - /* Now allocate an array to store the gpgme key objects. */ - keys = xcalloc (nkeys + 1, sizeof *keys); + p = *r_keystring = xtrymalloc (length); + if (!p) + return gpg_error_from_syserror (); if (cjson_is_string (j_keys)) { - err = gpgme_get_key (ctx, j_keys->valuestring, &keys[0], 0); - if (err) - goto leave; + strcpy (p, j_keys->valuestring); } else { for (i=0; i < nkeys; i++) { j_item = cJSON_GetArrayItem (j_keys, i); - err = gpgme_get_key (ctx, j_item->valuestring, &keys[i], 0); - if (err) - goto leave; + if (i) + *p++ = '\n'; /* Add delimiter. */ + p = stpcpy (p, j_item->valuestring); } } - err = 0; - *r_keys = keys; - keys = NULL; - - leave: - if (keys) - { - for (i=0; keys[i]; i++) - gpgme_key_unref (keys[i]); - xfree (keys); - } - return err; + return 0; } + /* * GPGME support functions. @@ -546,10 +592,95 @@ data_from_base64_string (gpgme_data_t *r_data, cjson_t json) /* - * Implementaion of the commands. + * Implementation of the commands. */ +/* Create a "data" object and the "type", "base64" and "more" flags + * from DATA and append them to RESULT. Ownership if DATA is + * transferred to this function. TYPE must be a fixed string. + * CHUNKSIZE is the chunksize requested from the caller. If BASE64 is + * -1 the need for base64 encoding is determined by the content of + * DATA, all other values are take as rtue or false. Note that + * op_getmore has similar code but works on PENDING_DATA which is set + * here. */ +static gpg_error_t +make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize, + const char *type, int base64) +{ + gpg_error_t err; + char *buffer; + size_t buflen; + int c; + + if (!base64 || base64 == -1) /* Make sure that we really have a string. */ + gpgme_data_write (data, "", 1); + + buffer = gpgme_data_release_and_get_mem (data, &buflen); + data = NULL; + if (!buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (base64 == -1) + { + base64 = 0; + if (!buflen) + log_fatal ("Appended Nul byte got lost\n"); + if (memchr (buffer, 0, buflen-1)) + { + buflen--; /* Adjust for the extra nul byte. */ + base64 = 1; + } + /* Fixme: We might want to do more advanced heuristics than to + * only look for a Nul. */ + } + + /* Adjust the chunksize if we need to do base64 conversion. */ + if (base64) + chunksize = (chunksize / 4) * 3; + + xjson_AddStringToObject (result, "type", type); + xjson_AddBoolToObject (result, "base64", base64); + + if (buflen > chunksize) + { + xjson_AddBoolToObject (result, "more", 1); + + c = buffer[chunksize]; + buffer[chunksize] = 0; + if (base64) + err = add_base64_to_object (result, "data", buffer, chunksize); + else + err = cjson_AddStringToObject (result, "data", buffer); + buffer[chunksize] = c; + if (err) + goto leave; + + pending_data.buffer = buffer; + buffer = NULL; + pending_data.length = buflen; + pending_data.written = chunksize; + pending_data.type = type; + pending_data.base64 = base64; + } + else + { + if (base64) + err = add_base64_to_object (result, "data", buffer, buflen); + else + err = cjson_AddStringToObject (result, "data", buffer); + } + + leave: + gpgme_free (buffer); + return err; +} + + + static const char hlp_encrypt[] = "op: \"encrypt\"\n" "keys: Array of strings with the fingerprints or user-ids\n" @@ -559,14 +690,17 @@ static const char hlp_encrypt[] = "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "chunksize: Max number of bytes in the resulting \"data\".\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" + "mime: Indicate that data is a MIME object.\n" "armor: Request output in armored format.\n" "always-trust: Request --always-trust option.\n" "no-encrypt-to: Do not use a default recipient.\n" "no-compress: Do not compress the plaintext first.\n" "throw-keyids: Request the --throw-keyids option.\n" + "want-address: Require that the keys include a mail address.\n" "wrap: Assume the input is an OpenPGP message.\n" "\n" "Response on success:\n" @@ -574,27 +708,34 @@ static const char hlp_encrypt[] = "data: Unless armor mode is used a Base64 encoded binary\n" " ciphertext. In armor mode a string with an armored\n" " OpenPGP or a PEM message.\n" - "base64: Boolean indicating whether data is base64 encoded."; + "base64: Boolean indicating whether data is base64 encoded.\n" + "more: Optional boolean indicating that \"getmore\" is required."; static gpg_error_t op_encrypt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; + size_t chunksize; int opt_base64; - gpgme_key_t *keys = NULL; + int opt_mime; + char *keystring = NULL; cjson_t j_input; gpgme_data_t input = NULL; gpgme_data_t output = NULL; - int abool, i; + int abool; gpgme_encrypt_flags_t encrypt_flags = 0; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); + if ((err = get_chunksize (request, &chunksize))) + goto leave; if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) goto leave; + if ((err = get_boolean_flag (request, "mime", 0, &opt_mime))) + goto leave; if ((err = get_boolean_flag (request, "armor", 0, &abool))) goto leave; @@ -619,10 +760,14 @@ op_encrypt (cjson_t request, cjson_t result) goto leave; if (abool) encrypt_flags |= GPGME_ENCRYPT_WRAP; + if ((err = get_boolean_flag (request, "want-address", 0, &abool))) + goto leave; + if (abool) + encrypt_flags |= GPGME_ENCRYPT_WANT_ADDRESS; /* Get the keys. */ - err = get_keys (ctx, request, &keys); + err = get_keys (request, &keystring); if (err) { /* Provide a custom error response. */ @@ -663,6 +808,9 @@ op_encrypt (cjson_t request, cjson_t result) goto leave; } } + if (opt_mime) + gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME); + /* Create an output data object. */ err = gpgme_data_new (&output); @@ -674,7 +822,8 @@ op_encrypt (cjson_t request, cjson_t result) } /* Encrypt. */ - err = gpgme_op_encrypt (ctx, keys, encrypt_flags, input, output); + err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags, + input, output); /* encrypt_result = gpgme_op_encrypt_result (ctx); */ if (err) { @@ -684,48 +833,216 @@ op_encrypt (cjson_t request, cjson_t result) gpgme_data_release (input); input = NULL; - xjson_AddStringToObject (result, "type", "ciphertext"); - /* If armoring is used we do not need to base64 the output. */ - xjson_AddBoolToObject (result, "base64", !gpgme_get_armor (ctx)); - if (gpgme_get_armor (ctx)) - { - char *buffer; + /* We need to base64 if armoring has not been requested. */ + err = make_data_object (result, output, chunksize, + "ciphertext", !gpgme_get_armor (ctx)); + output = NULL; + + leave: + xfree (keystring); + release_context (ctx); + gpgme_data_release (input); + gpgme_data_release (output); + return err; +} + + + +static const char hlp_decrypt[] = + "op: \"decrypt\"\n" + "data: The encrypted data.\n" + "\n" + "Optional parameters:\n" + "protocol: Either \"openpgp\" (default) or \"cms\".\n" + "chunksize: Max number of bytes in the resulting \"data\".\n" + "\n" + "Optional boolean flags (default is false):\n" + "base64: Input data is base64 encoded.\n" + "\n" + "Response on success:\n" + "type: \"plaintext\"\n" + "data: The decrypted data. This may be base64 encoded.\n" + "base64: Boolean indicating whether data is base64 encoded.\n" + "mime: A Boolean indicating whether the data is a MIME object.\n" + "info: An optional object with extra information.\n" + "more: Optional boolean indicating that \"getmore\" is required."; +static gpg_error_t +op_decrypt (cjson_t request, cjson_t result) +{ + gpg_error_t err; + gpgme_ctx_t ctx = NULL; + gpgme_protocol_t protocol; + size_t chunksize; + int opt_base64; + cjson_t j_input; + gpgme_data_t input = NULL; + gpgme_data_t output = NULL; + gpgme_decrypt_result_t decrypt_result; + + if ((err = get_protocol (request, &protocol))) + goto leave; + ctx = get_context (protocol); + if ((err = get_chunksize (request, &chunksize))) + goto leave; + + if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) + goto leave; - /* Make sure that we really have a string. */ - gpgme_data_write (output, "", 1); - buffer = gpgme_data_release_and_get_mem (output, NULL); - if (!buffer) + /* Get the data. Note that INPUT is a shallow data object with the + * storage hold in REQUEST. */ + j_input = cJSON_GetObjectItem (request, "data"); + if (!j_input) + { + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + if (!cjson_is_string (j_input)) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + if (opt_base64) + { + err = data_from_base64_string (&input, j_input); + if (err) { - err = gpg_error_from_syserror (); + error_object (result, "Error decoding Base-64 encoded 'data': %s", + gpg_strerror (err)); goto leave; } - err = cjson_AddStringToObject (result, "data", buffer); - gpgme_free (buffer); - if (err) - goto leave; } else { - err = add_base64_to_object (result, "data", output); - output = NULL; + err = gpgme_data_new_from_mem (&input, j_input->valuestring, + strlen (j_input->valuestring), 0); if (err) - goto leave; + { + error_object (result, "Error getting 'data': %s", gpg_strerror (err)); + goto leave; + } } - leave: - if (keys) + /* Create an output data object. */ + err = gpgme_data_new (&output); + if (err) + { + error_object (result, "Error creating output data object: %s", + gpg_strerror (err)); + goto leave; + } + + /* Decrypt. */ + err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY, + input, output); + decrypt_result = gpgme_op_decrypt_result (ctx); + if (err) { - for (i=0; keys[i]; i++) - gpgme_key_unref (keys[i]); - xfree (keys); + error_object (result, "Decryption failed: %s", gpg_strerror (err)); + goto leave; } + gpgme_data_release (input); + input = NULL; + + if (decrypt_result->is_mime) + xjson_AddBoolToObject (result, "mime", 1); + + err = make_data_object (result, output, chunksize, "plaintext", -1); + output = NULL; + + leave: release_context (ctx); gpgme_data_release (input); + gpgme_data_release (output); return err; } + +static const char hlp_getmore[] = + "op: \"getmore\"\n" + "\n" + "Optional parameters:\n" + "chunksize: Max number of bytes in the \"data\" object.\n" + "\n" + "Response on success:\n" + "type: Type of the pending data\n" + "data: The next chunk of data\n" + "base64: Boolean indicating whether data is base64 encoded\n" + "more: Optional boolean requesting another \"getmore\"."; +static gpg_error_t +op_getmore (cjson_t request, cjson_t result) +{ + gpg_error_t err; + int c; + size_t n; + size_t chunksize; + + if ((err = get_chunksize (request, &chunksize))) + goto leave; + + /* Adjust the chunksize if we need to do base64 conversion. */ + if (pending_data.base64) + chunksize = (chunksize / 4) * 3; + + /* Do we have anything pending? */ + if (!pending_data.buffer) + { + err = gpg_error (GPG_ERR_NO_DATA); + error_object (result, "Operation not possible: %s", gpg_strerror (err)); + goto leave; + } + + xjson_AddStringToObject (result, "type", pending_data.type); + xjson_AddBoolToObject (result, "base64", pending_data.base64); + if (pending_data.written >= pending_data.length) + { + /* EOF reached. This should not happen but we return an empty + * string once in case of client errors. */ + gpgme_free (pending_data.buffer); + pending_data.buffer = NULL; + xjson_AddBoolToObject (result, "more", 0); + err = cjson_AddStringToObject (result, "data", ""); + } + else + { + n = pending_data.length - pending_data.written; + if (n > chunksize) + { + n = chunksize; + xjson_AddBoolToObject (result, "more", 1); + } + else + xjson_AddBoolToObject (result, "more", 0); + + c = pending_data.buffer[pending_data.written + n]; + pending_data.buffer[pending_data.written + n] = 0; + if (pending_data.base64) + err = add_base64_to_object (result, "data", + (pending_data.buffer + + pending_data.written), n); + else + err = cjson_AddStringToObject (result, "data", + (pending_data.buffer + + pending_data.written)); + pending_data.buffer[pending_data.written + n] = c; + if (!err) + { + pending_data.written += n; + if (pending_data.written >= pending_data.length) + { + gpgme_free (pending_data.buffer); + pending_data.buffer = NULL; + } + } + } + + leave: + return err; +} + + + static const char hlp_help[] = "The tool expects a JSON object with the request and responds with\n" "another JSON object. Even on error a JSON object is returned. The\n" @@ -735,6 +1052,7 @@ static const char hlp_help[] = "returned. To list all operations it is allowed to leave out \"op\" in\n" "help mode. Supported values for \"op\" are:\n\n" " encrypt Encrypt data.\n" + " getmore Retrieve remaining data.\n" " help Help overview."; static gpg_error_t op_help (cjson_t request, cjson_t result) @@ -757,6 +1075,10 @@ op_help (cjson_t request, cjson_t result) } + +/* + * Dispatcher + */ /* Process a request and return the response. The response is a newly * allocated string or NULL in case of an error. */ @@ -769,8 +1091,8 @@ process_request (const char *request) const char * const helpstr; } optbl[] = { { "encrypt", op_encrypt, hlp_encrypt }, - - + { "decrypt", op_decrypt, hlp_decrypt }, + { "getmore", op_getmore, hlp_getmore }, { "help", op_help, hlp_help }, { NULL } }; @@ -825,6 +1147,14 @@ process_request (const char *request) { gpg_error_t err; + /* If this is not the "getmore" command and we have any + * pending data release that data. */ + if (pending_data.buffer && optbl[idx].handler != op_getmore) + { + gpgme_free (pending_data.buffer); + pending_data.buffer = NULL; + } + err = optbl[idx].handler (json, response); if (err) { @@ -839,7 +1169,6 @@ process_request (const char *request) xjson_AddStringToObject (response, "op", op); } - } } else /* Operation not supported. */ @@ -866,6 +1195,48 @@ process_request (const char *request) * Driver code */ +static char * +get_file (const char *fname) +{ + gpg_error_t err; + estream_t fp; + struct stat st; + char *buf; + size_t buflen; + + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); + return NULL; + } + + if (fstat (es_fileno(fp), &st)) + { + err = gpg_error_from_syserror (); + log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); + es_fclose (fp); + return NULL; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (es_fread (buf, buflen, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + es_fclose (fp); + xfree (buf); + return NULL; + } + buf[buflen] = 0; + es_fclose (fp); + + return buf; +} + + /* Return a malloced line or NULL on EOF. Terminate on read * error. */ static char * @@ -929,14 +1300,39 @@ process_meta_commands (const char *request) request++; if (!strncmp (request, "help", 4) && (spacep (request+4) || !request[4])) - result = process_request ("{ \"op\": \"help\"," - " \"interactive_help\": " - "\"\\nMeta commands:\\n" - " ,help This help\\n" - " ,quit Terminate process\"" - "}"); + { + if (request[4]) + { + char *buf = xstrconcat ("{ \"help\":true, \"op\":\"", request+5, + "\" }", NULL); + result = process_request (buf); + xfree (buf); + } + else + result = process_request ("{ \"op\": \"help\"," + " \"interactive_help\": " + "\"\\nMeta commands:\\n" + " ,read FNAME Process data from FILE\\n" + " ,help CMD Print help for a command\\n" + " ,quit Terminate process\"" + "}"); + } else if (!strncmp (request, "quit", 4) && (spacep (request+4) || !request[4])) exit (0); + else if (!strncmp (request, "read", 4) && (spacep (request+4) || !request[4])) + { + if (!request[4]) + log_info ("usage: ,read FILENAME\n"); + else + { + char *buffer = get_file (request + 5); + if (buffer) + { + result = process_request (buffer); + xfree (buffer); + } + } + } else log_info ("invalid meta command\n"); |