mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-22 00:57:52 +00:00
24fdd1f225
Improve rejection of invalid chars
612 lines
16 KiB
C
612 lines
16 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
* JOSE is actually specified as part of JWS RFC7515. JWE references RFC7515
|
|
* to specify its JOSE JSON object. So it lives in ./lib/jose/jws/jose.c.
|
|
*/
|
|
|
|
#include "private-lib-core.h"
|
|
#include "jose/private-lib-jose.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
static const char * const jws_jose[] = {
|
|
"alg", /* REQUIRED */
|
|
"jku",
|
|
"jwk",
|
|
"kid",
|
|
"x5u",
|
|
"x5c",
|
|
"x5t",
|
|
"x5t#S256",
|
|
"typ",
|
|
"cty",
|
|
"crit",
|
|
|
|
/* valid for JWE only below here */
|
|
|
|
"recipients[].header",
|
|
"recipients[].header.alg",
|
|
"recipients[].header.kid",
|
|
"recipients[].encrypted_key",
|
|
|
|
"enc",
|
|
"zip", /* ("DEF" = deflate) */
|
|
|
|
"epk", /* valid for JWE ECDH only */
|
|
"apu", /* valid for JWE ECDH only */
|
|
"apv", /* valid for JWE ECDH only */
|
|
"iv", /* valid for JWE AES only */
|
|
"tag", /* valid for JWE AES only */
|
|
"p2s", /* valid for JWE PBES2 only */
|
|
"p2c" /* valid for JWE PBES2 only */
|
|
};
|
|
|
|
struct jose_cb_args {
|
|
struct lws_jose *jose;
|
|
|
|
struct lejp_ctx jwk_jctx; /* fake lejp context used to parse epk */
|
|
struct lws_jwk_parse_state jps; /* fake jwk parse state */
|
|
|
|
char *temp;
|
|
int *temp_len;
|
|
|
|
unsigned int is_jwe;
|
|
unsigned int recipients_array;
|
|
|
|
int recip;
|
|
};
|
|
|
|
/*
|
|
* JWE A.4.7 Complete JWE JSON Serialization example
|
|
*
|
|
* LEJPCB_CONSTRUCTED
|
|
* LEJPCB_START
|
|
* LEJPCB_OBJECT_START
|
|
*
|
|
* protected LEJPCB_PAIR_NAME
|
|
* protected LEJPCB_VAL_STR_START
|
|
* protected LEJPCB_VAL_STR_END
|
|
*
|
|
* unprotected LEJPCB_PAIR_NAME
|
|
* unprotected LEJPCB_OBJECT_START
|
|
* unprotected.jku LEJPCB_PAIR_NAME
|
|
* unprotected.jku LEJPCB_VAL_STR_START
|
|
* unprotected.jku LEJPCB_VAL_STR_END
|
|
* unprotected.jku LEJPCB_OBJECT_END
|
|
*
|
|
* recipients LEJPCB_PAIR_NAME
|
|
* recipients[] LEJPCB_ARRAY_START
|
|
*
|
|
* recipients[] LEJPCB_OBJECT_START
|
|
* recipients[].header LEJPCB_PAIR_NAME
|
|
* recipients[].header LEJPCB_OBJECT_START
|
|
* recipients[].header.alg LEJPCB_PAIR_NAME
|
|
* recipients[].header.alg LEJPCB_VAL_STR_START
|
|
* recipients[].header.alg LEJPCB_VAL_STR_END
|
|
* recipients[].header.kid LEJPCB_PAIR_NAME
|
|
* recipients[].header.kid LEJPCB_VAL_STR_START
|
|
* recipients[].header.kid LEJPCB_VAL_STR_END
|
|
* recipients[] LEJPCB_OBJECT_END
|
|
* recipients[].encrypted_key LEJPCB_PAIR_NAME
|
|
* recipients[].encrypted_key LEJPCB_VAL_STR_START
|
|
* recipients[].encrypted_key LEJPCB_VAL_STR_CHUNK
|
|
* recipients[].encrypted_key LEJPCB_VAL_STR_END
|
|
* recipients[] LEJPCB_OBJECT_END (ctx->sp = 1)
|
|
*
|
|
* recipients[] LEJPCB_OBJECT_START
|
|
* recipients[].header LEJPCB_PAIR_NAME
|
|
* recipients[].header LEJPCB_OBJECT_START
|
|
* recipients[].header.alg LEJPCB_PAIR_NAME
|
|
* recipients[].header.alg LEJPCB_VAL_STR_START
|
|
* recipients[].header.alg LEJPCB_VAL_STR_END
|
|
* recipients[].header.kid LEJPCB_PAIR_NAME
|
|
* recipients[].header.kid LEJPCB_VAL_STR_START
|
|
* recipients[].header.kid LEJPCB_VAL_STR_END
|
|
* recipients[] LEJPCB_OBJECT_END
|
|
* recipients[].encrypted_key LEJPCB_PAIR_NAME
|
|
* recipients[].encrypted_key LEJPCB_VAL_STR_START
|
|
* recipients[].encrypted_key LEJPCB_VAL_STR_END
|
|
* recipients[] LEJPCB_OBJECT_END (ctx->sp = 1)
|
|
*
|
|
* recipients[] LEJPCB_ARRAY_END
|
|
*
|
|
* iv LEJPCB_PAIR_NAME
|
|
* iv LEJPCB_VAL_STR_START
|
|
* iv LEJPCB_VAL_STR_END
|
|
* ciphertext LEJPCB_PAIR_NAME
|
|
* ciphertext LEJPCB_VAL_STR_START
|
|
* ciphertext LEJPCB_VAL_STR_END
|
|
* tag LEJPCB_PAIR_NAME
|
|
* tag LEJPCB_VAL_STR_START
|
|
* tag LEJPCB_VAL_STR_END
|
|
*
|
|
* tag LEJPCB_OBJECT_END
|
|
* tag LEJPCB_COMPLETE
|
|
* tag LEJPCB_DESTRUCTED
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* RFC7516 7.2.2
|
|
*
|
|
* Note that when using the flattened syntax, just as when using the
|
|
* general syntax, any unprotected Header Parameter values can reside in
|
|
* either the "unprotected" member or the "header" member, or in both.
|
|
*/
|
|
|
|
static signed char
|
|
lws_jws_jose_cb(struct lejp_ctx *ctx, char reason)
|
|
{
|
|
struct jose_cb_args *args = (struct jose_cb_args *)ctx->user;
|
|
int n; //, dest;
|
|
|
|
/*
|
|
* In JOSE JSON, the element "epk" contains a fully-formed JWK.
|
|
*
|
|
* For JOSE paths beginning "epk.", we pass them through to a JWK
|
|
* LEJP subcontext to parse using the JWK parser directly.
|
|
*/
|
|
|
|
if (args->is_jwe && !strncmp(ctx->path, "epk.", 4)) {
|
|
memcpy(args->jwk_jctx.path, ctx->path + 4,
|
|
sizeof(ctx->path) - 4);
|
|
memcpy(args->jwk_jctx.buf, ctx->buf, ctx->npos);
|
|
args->jwk_jctx.npos = ctx->npos;
|
|
|
|
if (!ctx->path_match)
|
|
args->jwk_jctx.path_match = 0;
|
|
lejp_check_path_match(&args->jwk_jctx);
|
|
|
|
if (args->jwk_jctx.path_match)
|
|
args->jwk_jctx.pst[args->jwk_jctx.pst_sp].
|
|
callback(&args->jwk_jctx, reason);
|
|
}
|
|
|
|
// lwsl_notice("%s: %s %d (%d)\n", __func__, ctx->path, reason, ctx->sp);
|
|
|
|
/* at the end of each recipients[] entry, bump recipients count */
|
|
|
|
if (args->is_jwe && reason == LEJPCB_OBJECT_END && ctx->sp == 1 &&
|
|
!strcmp(ctx->path, "recipients[]"))
|
|
args->jose->recipients++;
|
|
|
|
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
|
|
return 0;
|
|
|
|
//dest = ctx->path_match - 1;
|
|
|
|
switch (ctx->path_match - 1) {
|
|
|
|
/* strings */
|
|
|
|
case LJJHI_ALG: /* REQUIRED */
|
|
|
|
/*
|
|
* look up whether we support this alg and point the caller at
|
|
* its definition if so
|
|
*/
|
|
|
|
if (!args->is_jwe &&
|
|
lws_gencrypto_jws_alg_to_definition(ctx->buf,
|
|
&args->jose->alg)) {
|
|
lwsl_notice("%s: unknown alg '%s'\n", __func__,
|
|
ctx->buf);
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (args->is_jwe &&
|
|
lws_gencrypto_jwe_alg_to_definition(ctx->buf,
|
|
&args->jose->alg)) {
|
|
lwsl_notice("%s: unknown JWE alg '%s'\n", __func__,
|
|
ctx->buf);
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
case LJJHI_TYP: /* Optional: string: media type */
|
|
lws_strnncpy(args->jose->typ, ctx->buf, ctx->npos,
|
|
sizeof(args->jose->typ));
|
|
break;
|
|
|
|
case LJJHI_JKU: /* Optional: string */
|
|
case LJJHI_KID: /* Optional: string */
|
|
case LJJHI_X5U: /* Optional: string: url of public key cert / chain */
|
|
case LJJHI_CTY: /* Optional: string: content media type */
|
|
|
|
/* base64 */
|
|
|
|
case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */
|
|
|
|
/* base64-url */
|
|
|
|
case LJJHI_X5T: /* Optional: base64url: SHA-1 of actual cert */
|
|
case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of actual cert */
|
|
|
|
/* array of strings */
|
|
|
|
case LJJHI_CRIT: /* Optional for send, REQUIRED: array of strings:
|
|
* mustn't contain standardized strings or null set */
|
|
break;
|
|
|
|
/* jwk child */
|
|
|
|
case LJJHI_JWK: /* Optional: jwk JSON object: public key: */
|
|
|
|
/* past here, JWE only */
|
|
|
|
case LJJHI_RECIPS_HDR:
|
|
if (!args->is_jwe) {
|
|
lwsl_info("%s: recipients in jws\n", __func__);
|
|
return -1;
|
|
}
|
|
args->recipients_array = 1;
|
|
break;
|
|
|
|
case LJJHI_RECIPS_HDR_ALG:
|
|
case LJJHI_RECIPS_HDR_KID:
|
|
break;
|
|
|
|
case LJJHI_RECIPS_EKEY:
|
|
if (!args->is_jwe) {
|
|
lwsl_info("%s: recipients in jws\n", __func__);
|
|
return -1;
|
|
}
|
|
args->recipients_array = 1;
|
|
//dest = ;
|
|
goto append_string;
|
|
|
|
case LJJHI_ENC: /* JWE only: Mandatory: string */
|
|
if (!args->is_jwe) {
|
|
lwsl_info("%s: enc in jws\n", __func__);
|
|
return -1;
|
|
}
|
|
if (lws_gencrypto_jwe_enc_to_definition(ctx->buf,
|
|
&args->jose->enc_alg)) {
|
|
lwsl_notice("%s: unknown enc '%s'\n", __func__,
|
|
ctx->buf);
|
|
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case LJJHI_ZIP: /* JWE only: Optional: string ("DEF" = deflate) */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
goto append_string;
|
|
|
|
case LJJHI_EPK: /* Additional arg for JWE ECDH */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
/* Ephemeral key... this JSON subsection is actually a JWK */
|
|
break;
|
|
|
|
case LJJHI_APU: /* Additional arg for JWE ECDH */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
/* Agreement Party U */
|
|
goto append_string;
|
|
|
|
case LJJHI_APV: /* Additional arg for JWE ECDH */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
/* Agreement Party V */
|
|
goto append_string;
|
|
|
|
case LJJHI_IV: /* Additional arg for JWE AES */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
goto append_string;
|
|
|
|
case LJJHI_TAG: /* Additional arg for JWE AES */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
goto append_string;
|
|
|
|
case LJJHI_P2S: /* Additional arg for JWE PBES2 */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
goto append_string;
|
|
case LJJHI_P2C: /* Additional arg for JWE PBES2 */
|
|
if (!args->is_jwe)
|
|
return -1;
|
|
goto append_string;
|
|
|
|
/* ignore what we don't understand */
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
|
|
append_string:
|
|
|
|
if (*args->temp_len < ctx->npos) {
|
|
lwsl_err("%s: out of parsing space\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
if (!args->jose->e[ctx->path_match - 1].buf) {
|
|
args->jose->e[ctx->path_match - 1].buf = (uint8_t *)args->temp;
|
|
args->jose->e[ctx->path_match - 1].len = 0;
|
|
}
|
|
|
|
memcpy(args->temp, ctx->buf, ctx->npos);
|
|
args->temp += ctx->npos;
|
|
*args->temp_len -= ctx->npos;
|
|
args->jose->e[ctx->path_match - 1].len += ctx->npos;
|
|
|
|
if (reason == LEJPCB_VAL_STR_END &&
|
|
(int)args->jose->e[ctx->path_match - 1].len &&
|
|
!args->jose->edone[ctx->path_match - 1]) {
|
|
n = lws_b64_decode_string_len(
|
|
(const char *)args->jose->e[ctx->path_match - 1].buf,
|
|
(int)args->jose->e[ctx->path_match - 1].len,
|
|
(char *)args->jose->e[ctx->path_match - 1].buf,
|
|
(int)args->jose->e[ctx->path_match - 1].len + 1);
|
|
if (n < 0) {
|
|
lwsl_err("%s: b64 decode failed len %d\n", __func__,
|
|
(int)args->jose->e[ctx->path_match - 1].len);
|
|
|
|
return -1;
|
|
}
|
|
|
|
args->jose->edone[ctx->path_match - 1] = 1;
|
|
args->temp -= (int)args->jose->e[ctx->path_match - 1].len - n - 1;
|
|
*args->temp_len +=
|
|
(int)args->jose->e[ctx->path_match - 1].len - n - 1;
|
|
|
|
args->jose->e[ctx->path_match - 1].len = (uint32_t)n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_jose_init(struct lws_jose *jose)
|
|
{
|
|
memset(jose, 0, sizeof(*jose));
|
|
}
|
|
|
|
static void
|
|
lws_jose_recip_destroy(struct lws_jws_recpient *r)
|
|
{
|
|
lws_jwk_destroy(&r->jwk_ephemeral);
|
|
lws_jwk_destroy(&r->jwk);
|
|
}
|
|
|
|
void
|
|
lws_jose_destroy(struct lws_jose *jose)
|
|
{
|
|
int n;
|
|
|
|
for (n = 0; n < (int)LWS_ARRAY_SIZE(jose->recipient); n++)
|
|
lws_jose_recip_destroy(&jose->recipient[n]);
|
|
}
|
|
|
|
static int
|
|
lws_jose_parse(struct lws_jose *jose, const uint8_t *buf, int n,
|
|
char *temp, int *temp_len, int is_jwe)
|
|
{
|
|
struct lejp_ctx jctx;
|
|
struct jose_cb_args args;
|
|
int m;
|
|
|
|
if (is_jwe) {
|
|
/* prepare a context for JOSE epk ephemeral jwk parsing */
|
|
lws_jwk_init_jps(&args.jps,
|
|
&jose->recipient[jose->recipients].jwk_ephemeral,
|
|
NULL, NULL);
|
|
lejp_construct(&args.jwk_jctx, cb_jwk, &args.jps,
|
|
jwk_tok, LWS_ARRAY_SIZE(jwk_tok));
|
|
}
|
|
|
|
args.is_jwe = (unsigned int)is_jwe;
|
|
args.temp = temp;
|
|
args.temp_len = temp_len;
|
|
args.jose = jose;
|
|
args.recip = 0;
|
|
args.recipients_array = 0;
|
|
jose->recipients = 0;
|
|
|
|
lejp_construct(&jctx, lws_jws_jose_cb, &args, jws_jose,
|
|
LWS_ARRAY_SIZE(jws_jose));
|
|
|
|
m = lejp_parse(&jctx, (uint8_t *)buf, n);
|
|
lejp_destruct(&jctx);
|
|
if (m < 0) {
|
|
lwsl_notice("%s: parse returned %d\n", __func__, m);
|
|
return -1;
|
|
}
|
|
|
|
if (!args.recipients_array && jose->recipient[0].unprot[LJJHI_ALG].buf)
|
|
/* if no explicit recipients[], we got one */
|
|
jose->recipients++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_jws_parse_jose(struct lws_jose *jose,
|
|
const char *buf, int len, char *temp, int *temp_len)
|
|
{
|
|
return lws_jose_parse(jose, (const uint8_t *)buf, len,
|
|
temp, temp_len, 0);
|
|
}
|
|
|
|
int
|
|
lws_jwe_parse_jose(struct lws_jose *jose,
|
|
const char *buf, int len, char *temp, int *temp_len)
|
|
{
|
|
return lws_jose_parse(jose,
|
|
(const uint8_t *)buf, len, temp, temp_len, 1);
|
|
}
|
|
|
|
int
|
|
lws_jose_render(struct lws_jose *jose, struct lws_jwk *aux_jwk,
|
|
char *out, size_t out_len)
|
|
{
|
|
struct lws_jwk *jwk;
|
|
char *end = out + out_len - 1;
|
|
int n, m, f, sub = 0, vl;
|
|
|
|
/* JOSE requires an alg */
|
|
if (!jose->alg || !jose->alg->alg)
|
|
goto bail;
|
|
|
|
*out++ = '{';
|
|
|
|
for (n = 0; n < LWS_COUNT_JOSE_HDR_ELEMENTS; n++) {
|
|
switch (n) {
|
|
|
|
/* strings */
|
|
|
|
case LJJHI_ALG: /* REQUIRED */
|
|
case LJJHI_JKU: /* Optional: string */
|
|
case LJJHI_KID: /* Optional: string */
|
|
case LJJHI_TYP: /* Optional: string: media type */
|
|
case LJJHI_CTY: /* Optional: string: content media type */
|
|
case LJJHI_X5U: /* Optional: string: pubkey cert / chain URL */
|
|
case LJJHI_ENC: /* JWE only: Optional: string */
|
|
case LJJHI_ZIP: /* JWE only: Optional: string ("DEF"=deflate) */
|
|
if (jose->e[n].buf) {
|
|
out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
|
|
"%s\"%s\":\"%s\"", sub ? ",\n" : "",
|
|
jws_jose[n], jose->e[n].buf);
|
|
sub = 1;
|
|
}
|
|
break;
|
|
|
|
case LJJHI_X5T: /* Optional: base64url: SHA-1 of actual cert */
|
|
case LJJHI_X5T_S256: /* Optional: base64url: SHA-256 of cert */
|
|
case LJJHI_APU: /* Additional arg for JWE ECDH: b64url */
|
|
case LJJHI_APV: /* Additional arg for JWE ECDH: b64url */
|
|
case LJJHI_IV: /* Additional arg for JWE AES: b64url */
|
|
case LJJHI_TAG: /* Additional arg for JWE AES: b64url */
|
|
case LJJHI_P2S: /* Additional arg for JWE PBES2: b64url: salt */
|
|
if (jose->e[n].buf) {
|
|
out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
|
|
"%s\"%s\":\"", sub ? ",\n" : "",
|
|
jws_jose[n]);
|
|
sub = 1;
|
|
m = lws_b64_encode_string_url((const char *)
|
|
jose->e[n].buf, (int)jose->e[n].len,
|
|
out, lws_ptr_diff(end, out));
|
|
if (m < 0)
|
|
return -1;
|
|
out += m;
|
|
out += lws_snprintf(out, lws_ptr_diff_size_t(end, out), "\"");
|
|
}
|
|
break;
|
|
|
|
case LJJHI_P2C: /* Additional arg for JWE PBES2: int: count */
|
|
break; /* don't support atm */
|
|
|
|
case LJJHI_X5C: /* Optional: base64 (NOT -url): actual cert */
|
|
if (jose->e[n].buf) {
|
|
out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
|
|
"%s\"%s\":\"", sub ? ",\n" : "",
|
|
jws_jose[n]);
|
|
sub = 1;
|
|
m = lws_b64_encode_string((const char *)
|
|
jose->e[n].buf, (int)jose->e[n].len,
|
|
out, lws_ptr_diff(end, out));
|
|
if (m < 0)
|
|
return -1;
|
|
out += m;
|
|
out += lws_snprintf(out, lws_ptr_diff_size_t(end, out), "\"");
|
|
}
|
|
break;
|
|
|
|
case LJJHI_EPK: /* Additional arg for JWE ECDH: eph pubkey */
|
|
case LJJHI_JWK: /* Optional: jwk JSON object: public key: */
|
|
|
|
jwk = n == LJJHI_EPK ? &jose->recipient[0].jwk_ephemeral : aux_jwk;
|
|
if (!jwk || !jwk->kty)
|
|
break;
|
|
|
|
out += lws_snprintf(out, lws_ptr_diff_size_t(end, out), "%s\"%s\":",
|
|
sub ? ",\n" : "", jws_jose[n]);
|
|
sub = 1;
|
|
vl = lws_ptr_diff(end, out);
|
|
m = lws_jwk_export(jwk, 0, out, &vl);
|
|
if (m < 0) {
|
|
lwsl_notice("%s: failed to export key\n",
|
|
__func__);
|
|
|
|
return -1;
|
|
}
|
|
out += m;
|
|
break;
|
|
|
|
case LJJHI_CRIT:/* Optional for send, REQUIRED: array of strings:
|
|
* mustn't contain standardized strings or null set */
|
|
if (!jose->e[n].buf)
|
|
break;
|
|
|
|
out += lws_snprintf(out, lws_ptr_diff_size_t(end, out),
|
|
"%s\"%s\":[", sub ? ",\n" : "", jws_jose[n]);
|
|
sub = 1;
|
|
|
|
m = 0;
|
|
f = 1;
|
|
while ((unsigned int)m < jose->e[n].len && (end - out) > 1) {
|
|
if (jose->e[n].buf[m] == ' ') {
|
|
if (!f)
|
|
*out++ = '\"';
|
|
|
|
m++;
|
|
f = 1;
|
|
continue;
|
|
}
|
|
|
|
if (f) {
|
|
if (m)
|
|
*out++ = ',';
|
|
*out++ = '\"';
|
|
f = 0;
|
|
}
|
|
|
|
*out++ = (char)jose->e[n].buf[m];
|
|
m++;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
*out++ = '}';
|
|
|
|
if (out > end - 2)
|
|
return -1;
|
|
|
|
return lws_ptr_diff(out_len, (end - out)) - 1;
|
|
|
|
bail:
|
|
return -1;
|
|
}
|