-/*
+/* oauth.c -- OAuth 2.0 implementation for XOAUTH2 in SMTP and POP3.
+ *
* This code is Copyright (c) 2014, by the authors of nmh. See the
* COPYRIGHT file in the root directory of the nmh distribution for
* complete copyright information.
#include <h/oauth.h>
#include <h/utils.h>
+#include "lock_file.h"
#define JSON_TYPE "application/json"
-/* We pretend access tokens expire 30 seconds earlier than they actually do to
+/* We pretend access tokens expire 60 seconds earlier than they actually do to
* allow for separate processes to use and refresh access tokens. The process
* that uses the access token (post) has an error if the token is expired; the
* process that refreshes the access token (send) must have already refreshed if
* the expiration is close.
*
- * 30s is arbitrary, and hopefully is enough to allow for clock skew.
+ * 60s is arbitrary, and hopefully is enough to allow for clock skew.
* Currently only Gmail supports XOAUTH2, and seems to always use a token
* life-time of 3600s, but that is not guaranteed. It is possible for Gmail to
* issue an access token with a life-time so short that even after send
* (not counting header and not null-terminated) */
#define RESPONSE_BODY_MAX 8192
-/* Maxium size for URLs and URI-encoded query strings, null-terminated.
+/* Maximum size for URLs and URI-encoded query strings, null-terminated.
*
* Actual maximum we need is based on the size of tokens (limited by
* RESPONSE_BODY_MAX), code user copies from a web page (arbitrarily large), and
*/
#define URL_MAX 8192
-struct service_info {
- /* Name of service, so we can search static SERVICES (below) and for
- * determining default credential file name. */
- char *name;
-
- /* Human-readable name of the service; in mh_oauth_ctx::svc this is not
- * another buffer to free, but a pointer to either static SERVICE data
- * (below) or to the name field. */
- char *display_name;
-
- /* [1] 2.2 Client Identifier, 2.3.1 Client Password */
- char *client_id;
- /* [1] 2.3.1 Client Password */
- char *client_secret;
- /* [1] 3.1 Authorization Endpoint */
- char *auth_endpoint;
- /* [1] 3.1.2 Redirection Endpoint */
- char *redirect_uri;
- /* [1] 3.2 Token Endpoint */
- char *token_endpoint;
- /* [1] 3.3 Access Token Scope */
- char *scope;
-};
-
-static const struct service_info SERVICES[] = {
- /* https://developers.google.com/accounts/docs/OAuth2InstalledApp */
- {
- /* name */ "gmail",
- /* display_name */ "Gmail",
-
- /* client_id */ "91584523849-8lv9kgp1rvp8ahta6fa4b125tn2polcg.apps.googleusercontent.com",
- /* client_secret */ "Ua8sX34xyv7hVrKM-U70dKI6",
-
- /* auth_endpoint */ "https://accounts.google.com/o/oauth2/auth",
- /* redirect_uri */ "urn:ietf:wg:oauth:2.0:oob",
- /* token_endpoint */ "https://accounts.google.com/o/oauth2/token",
- /* scope */ "https://mail.google.com/"
- }
-};
-
struct mh_oauth_cred {
mh_oauth_ctx *ctx;
};
struct mh_oauth_ctx {
- struct service_info svc;
+ struct mh_oauth_service_info svc;
CURL *curl;
FILE *log;
/* Whether the response was too big; if so, the rest of the output fields
* are undefined. */
- boolean too_big;
+ bool too_big;
/* HTTP response code */
long res_code;
char res_body[RESPONSE_BODY_MAX];
};
-static boolean get_json_strings(const char *, size_t, FILE *, ...);
-static boolean make_query_url(char *, size_t, CURL *, const char *, ...);
-static boolean post(struct curl_ctx *, const char *, const char *);
+static bool get_json_strings(const char *, size_t, FILE *, ...) ENDNULL;
+static bool make_query_url(char *, size_t, CURL *, const char *, ...) ENDNULL;
+static bool post(struct curl_ctx *, const char *, const char *);
-char *
-mh_oauth_do_xoauth(const char *user, const char *svc, FILE *log)
+int
+mh_oauth_do_xoauth(const char *user, const char *svc, unsigned char **oauth_res,
+ size_t *oauth_res_len, FILE *log)
{
mh_oauth_ctx *ctx;
mh_oauth_cred *cred;
char *fn;
int failed_to_lock = 0;
FILE *fp;
- size_t client_res_len;
char *client_res;
- char *client_res_b64;
- if (!mh_oauth_new (&ctx, svc)) adios(NULL, mh_oauth_get_err_string(ctx));
+ if (!mh_oauth_new (&ctx, svc))
+ adios(NULL, "%s", mh_oauth_get_err_string(ctx));
if (log != NULL) mh_oauth_log_to(stderr, ctx);
- fn = getcpy(mh_oauth_cred_fn(ctx));
+ fn = mh_oauth_cred_fn(svc);
fp = lkfopendata(fn, "r+", &failed_to_lock);
if (fp == NULL) {
if (errno == ENOENT) {
}
if ((cred = mh_oauth_cred_load(fp, ctx, user)) == NULL) {
- adios(NULL, mh_oauth_get_err_string(ctx));
+ adios(NULL, "%s", mh_oauth_get_err_string(ctx));
}
if (!mh_oauth_access_token_valid(time(NULL), cred)) {
if (mh_oauth_get_err_code(ctx) == MH_OAUTH_BAD_GRANT) {
adios(NULL, "credentials rejected -- run mhlogin -saslmech xoauth2 -authservice %s", svc);
}
- advise(NULL, "error refreshing OAuth2 token");
- adios(NULL, mh_oauth_get_err_string(ctx));
+ inform("error refreshing OAuth2 token");
+ adios(NULL, "%s", mh_oauth_get_err_string(ctx));
}
fseek(fp, 0, SEEK_SET);
if (!mh_oauth_cred_save(fp, cred, user)) {
- adios(NULL, mh_oauth_get_err_string(ctx));
+ adios(NULL, "%s", mh_oauth_get_err_string(ctx));
}
}
free(fn);
/* XXX writeBase64raw modifies the source buffer! make a copy */
- client_res = getcpy(mh_oauth_sasl_client_response(&client_res_len, user,
+ client_res = mh_xstrdup(mh_oauth_sasl_client_response(oauth_res_len, user,
cred));
mh_oauth_cred_free(cred);
mh_oauth_free(ctx);
- client_res_b64 = mh_xmalloc(((((client_res_len) + 2) / 3 ) * 4) + 1);
- if (writeBase64raw((unsigned char *)client_res, client_res_len,
- (unsigned char *)client_res_b64) != OK) {
- adios(NULL, "base64 encoding of XOAUTH2 client response failed");
- }
- free(client_res);
- return client_res_b64;
+ *oauth_res = (unsigned char *) client_res;
+
+ return OK;
}
-static boolean
+static bool
is_json(const char *content_type)
{
return content_type != NULL
- && strncasecmp(content_type, JSON_TYPE, sizeof JSON_TYPE - 1) == 0;
+ && strncasecmp(content_type, JSON_TYPE, LEN(JSON_TYPE)) == 0;
}
static void
if (curl_ctx->res_len > 0
&& is_json(curl_ctx->content_type)
&& get_json_strings(curl_ctx->res_body, curl_ctx->res_len, ctx->log,
- "error", &error, (void *)NULL)
+ "error", &error, NULL)
&& error != NULL) {
if (strcmp(error, "invalid_grant") == 0) {
code = MH_OAUTH_BAD_GRANT;
free(error);
}
-/* Copy service info so we don't have to free it only sometimes. */
-static void
-copy_svc(struct service_info *to, const struct service_info *from)
-{
- to->display_name = from->display_name;
-#define copy(_field_) to->_field_ = getcpy(from->_field_)
- copy(name);
- copy(scope);
- copy(client_id);
- copy(client_secret);
- copy(auth_endpoint);
- copy(token_endpoint);
- copy(redirect_uri);
-#undef copy
-}
-
-/* Return profile component node name for a service parameter. */
-static char *
-node_name_for_svc(const char *base_name, const char *svc)
-{
- char *result = mh_xmalloc(sizeof "oauth-" - 1
- + strlen(svc)
- + 1 /* '-' */
- + strlen(base_name)
- + 1 /* '\0' */);
- sprintf(result, "oauth-%s-%s", svc, base_name);
- /* TODO: s/_/-/g ? */
- return result;
-}
-
-/* Update one service_info field if overridden in profile. */
-static void
-update_svc_field(char **field, const char *base_name, const char *svc)
-{
- char *name = node_name_for_svc(base_name, svc);
- const char *value = context_find(name);
- if (value != NULL) {
- free(*field);
- *field = getcpy(value);
- }
- free(name);
-}
-
-/* Update all service_info fields that are overridden in profile. */
-static boolean
-update_svc(struct service_info *svc, const char *svc_name, mh_oauth_ctx *ctx)
-{
-#define update(name) \
- update_svc_field(&svc->name, #name, svc_name); \
- if (svc->name == NULL) { \
- set_err_details(ctx, MH_OAUTH_BAD_PROFILE, #name " is missing"); \
- return FALSE; \
- }
- update(scope);
- update(client_id);
- update(client_secret);
- update(auth_endpoint);
- update(token_endpoint);
- update(redirect_uri);
-#undef update
-
- if (svc->name == NULL) {
- svc->name = getcpy(svc_name);
- }
-
- if (svc->display_name == NULL) {
- svc->display_name = svc->name;
- }
-
- return TRUE;
-}
-
static char *
-make_user_agent()
+make_user_agent(void)
{
const char *curl = curl_version_info(CURLVERSION_NOW)->version;
- char *s = mh_xmalloc(strlen(user_agent)
- + 1
- + sizeof "libcurl"
- + 1
- + strlen(curl)
- + 1);
- sprintf(s, "%s libcurl/%s", user_agent, curl);
- return s;
+ return concat(user_agent, " libcurl/", curl, NULL);
}
-boolean
+bool
mh_oauth_new(mh_oauth_ctx **result, const char *svc_name)
{
- mh_oauth_ctx *ctx = *result = mh_xmalloc(sizeof *ctx);
- size_t i;
+ mh_oauth_ctx *ctx;
+ NEW(ctx);
+ *result = ctx;
ctx->curl = NULL;
ctx->log = NULL;
ctx->cred_fn = ctx->sasl_client_res = ctx->err_formatted = NULL;
- ctx->svc.name = ctx->svc.display_name = NULL;
- ctx->svc.scope = ctx->svc.client_id = NULL;
- ctx->svc.client_secret = ctx->svc.auth_endpoint = NULL;
- ctx->svc.token_endpoint = ctx->svc.redirect_uri = NULL;
-
- for (i = 0; i < sizeof SERVICES / sizeof SERVICES[0]; i++) {
- if (strcmp(SERVICES[i].name, svc_name) == 0) {
- copy_svc(&ctx->svc, &SERVICES[i]);
- break;
- }
- }
-
- if (!update_svc(&ctx->svc, svc_name, ctx)) {
- return FALSE;
+ if (!mh_oauth_get_service_info(svc_name, &ctx->svc, ctx->err_buf,
+ sizeof(ctx->err_buf))) {
+ set_err_details(ctx, MH_OAUTH_BAD_PROFILE, ctx->err_buf);
+ return false;
}
ctx->curl = curl_easy_init();
if (ctx->curl == NULL) {
set_err(ctx, MH_OAUTH_CURL_INIT);
- return FALSE;
+ return false;
}
curl_easy_setopt(ctx->curl, CURLOPT_ERRORBUFFER, ctx->err_buf);
if (curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT,
ctx->user_agent) != CURLE_OK) {
set_err_details(ctx, MH_OAUTH_CURL_INIT, ctx->err_buf);
- return FALSE;
+ return false;
}
- return TRUE;
+ return true;
}
void
const char *
mh_oauth_get_err_string(mh_oauth_ctx *ctx)
{
- char *result;
const char *base;
free(ctx->err_formatted);
base = "unknown error";
}
if (ctx->err_details == NULL) {
- return ctx->err_formatted = getcpy(base);
+ return ctx->err_formatted = mh_xstrdup(base);
}
- /* length of the two strings plus ": " and '\0' */
- result = mh_xmalloc(strlen(base) + strlen(ctx->err_details) + 3);
- sprintf(result, "%s: %s", base, ctx->err_details);
- return ctx->err_formatted = result;
+
+ ctx->err_formatted = concat(base, ": ", ctx->err_details, NULL);
+ return ctx->err_formatted;
}
const char *
"client_id", ctx->svc.client_id,
"redirect_uri", ctx->svc.redirect_uri,
"scope", ctx->svc.scope,
- (void *)NULL)) {
+ NULL)) {
set_err(ctx, MH_OAUTH_REQUEST_INIT);
return NULL;
}
return ctx->buf;
}
-static boolean
+static bool
cred_from_response(mh_oauth_cred *cred, const char *content_type,
const char *input, size_t input_len)
{
- boolean result = FALSE;
+ bool result = false;
char *access_token, *expires_in, *refresh_token;
const mh_oauth_ctx *ctx = cred->ctx;
if (!is_json(content_type)) {
- return FALSE;
+ return false;
}
access_token = expires_in = refresh_token = NULL;
"access_token", &access_token,
"expires_in", &expires_in,
"refresh_token", &refresh_token,
- (void *)NULL)) {
+ NULL)) {
goto out;
}
}
}
- result = TRUE;
+ result = true;
free(cred->access_token);
cred->access_token = access_token;
return result;
}
-static boolean
+static bool
do_access_request(mh_oauth_cred *cred, const char *req_body)
{
mh_oauth_ctx *ctx = cred->ctx;
} else {
set_err_details(ctx, MH_OAUTH_POST, ctx->err_buf);
}
- return FALSE;
+ return false;
}
if (curl_ctx.res_code != 200) {
set_err_http(ctx, &curl_ctx);
- return FALSE;
+ return false;
}
if (!cred_from_response(cred, curl_ctx.content_type, curl_ctx.res_body,
curl_ctx.res_len)) {
set_err(ctx, MH_OAUTH_RESPONSE_BAD);
- return FALSE;
+ return false;
}
- return TRUE;
+ return true;
}
mh_oauth_cred *
"redirect_uri", ctx->svc.redirect_uri,
"client_id", ctx->svc.client_id,
"client_secret", ctx->svc.client_secret,
- (void *)NULL)) {
+ NULL)) {
set_err(ctx, MH_OAUTH_REQUEST_INIT);
return NULL;
}
- result = mh_xmalloc(sizeof *result);
+ NEW(result);
result->ctx = ctx;
result->access_token = result->refresh_token = NULL;
return result;
}
-boolean
+bool
mh_oauth_refresh(mh_oauth_cred *cred)
{
- boolean result;
+ bool result;
mh_oauth_ctx *ctx = cred->ctx;
if (cred->refresh_token == NULL) {
set_err(ctx, MH_OAUTH_NO_REFRESH);
- return FALSE;
+ return false;
}
if (!make_query_url(ctx->buf, sizeof ctx->buf, ctx->curl, NULL,
"refresh_token", cred->refresh_token,
"client_id", ctx->svc.client_id,
"client_secret", ctx->svc.client_secret,
- (void *)NULL)) {
+ NULL)) {
set_err(ctx, MH_OAUTH_REQUEST_INIT);
- return FALSE;
+ return false;
}
result = do_access_request(cred, ctx->buf);
if (result && cred->access_token == NULL) {
set_err_details(ctx, MH_OAUTH_RESPONSE_BAD, "no access token");
- return FALSE;
+ return false;
}
return result;
}
-boolean
+bool
mh_oauth_access_token_valid(time_t t, const mh_oauth_cred *cred)
{
return cred->access_token != NULL && t + EXPIRY_FUDGE < cred->expires_at;
free(cred);
}
-const char *
-mh_oauth_cred_fn(mh_oauth_ctx *ctx)
-{
- char *result, *result_if_allocated;
- const char *svc = ctx->svc.name;
-
- char *component = node_name_for_svc("credential-file", svc);
- result = context_find(component);
- free(component);
-
- if (result == NULL) {
- result = mh_xmalloc(sizeof "oauth-" - 1
- + strlen(svc)
- + 1 /* '\0' */);
- sprintf(result, "oauth-%s", svc);
- result_if_allocated = result;
- } else {
- result_if_allocated = NULL;
- }
-
- if (result[0] != '/') {
- const char *tmp = m_maildir(result);
- free(result_if_allocated);
- result = getcpy(tmp);
- }
-
- free(ctx->cred_fn);
- return ctx->cred_fn = result;
-}
-
/* for loading multi-user cred files */
struct user_creds {
mh_oauth_cred *creds;
free(user_creds);
}
-static boolean
+static bool
load_creds(struct user_creds **result, FILE *fp, mh_oauth_ctx *ctx)
{
- boolean success = FALSE;
+ bool success = false;
char name[NAMESZ], value_buf[BUFSIZ];
int state;
- m_getfld_state_t getfld_ctx = 0;
+ m_getfld_state_t getfld_ctx;
- struct user_creds *user_creds = mh_xmalloc(sizeof *user_creds);
+ struct user_creds *user_creds;
+ NEW(user_creds);
user_creds->alloc = 4;
user_creds->len = 0;
user_creds->creds = mh_xmalloc(user_creds->alloc * sizeof *user_creds->creds);
+ getfld_ctx = m_getfld_state_init(fp);
for (;;) {
int size = sizeof value_buf;
- switch (state = m_getfld(&getfld_ctx, name, value_buf, &size, fp)) {
+ switch (state = m_getfld2(&getfld_ctx, name, value_buf, &size)) {
case FLD:
case FLDPLUS: {
char **save, *expire;
time_t *expires_at = NULL;
- if (strncmp(name, "access-", 7) == 0) {
+ if (has_prefix(name, "access-")) {
const char *user = name + 7;
mh_oauth_cred *creds = find_or_alloc_user_creds(user_creds,
user);
save = &creds->access_token;
- } else if (strncmp(name, "refresh-", 8) == 0) {
+ } else if (has_prefix(name, "refresh-")) {
const char *user = name + 8;
mh_oauth_cred *creds = find_or_alloc_user_creds(user_creds,
user);
save = &creds->refresh_token;
- } else if (strncmp(name, "expire-", 7) == 0) {
+ } else if (has_prefix(name, "expire-")) {
const char *user = name + 7;
mh_oauth_cred *creds = find_or_alloc_user_creds(user_creds,
user);
char *tmp = getcpy(value_buf);
while (state == FLDPLUS) {
size = sizeof value_buf;
- state = m_getfld(&getfld_ctx, name, value_buf, &size, fp);
+ state = m_getfld2(&getfld_ctx, name, value_buf, &size);
tmp = add(value_buf, tmp);
}
*save = trimcpy(tmp);
case BODY:
case FILEEOF:
- success = TRUE;
+ success = true;
break;
default:
- /* Not adding details for LENERR/FMTERR because m_getfld already
+ /* Not adding details for LENERR/FMTERR because m_getfld2 already
* wrote advise message to stderr. */
set_err(ctx, MH_OAUTH_CRED_FILE);
break;
return success;
}
-static boolean
+static bool
save_user(FILE *fp, const char *user, const char *access, const char *refresh,
long expires_at)
{
if (access != NULL) {
- if (fprintf(fp, "access-%s: %s\n", user, access) < 0) return FALSE;
+ if (fprintf(fp, "access-%s: %s\n", user, access) < 0) return false;
}
if (refresh != NULL) {
- if (fprintf(fp, "refresh-%s: %s\n", user, refresh) < 0) return FALSE;
+ if (fprintf(fp, "refresh-%s: %s\n", user, refresh) < 0) return false;
}
if (expires_at > 0) {
if (fprintf(fp, "expire-%s: %ld\n", user, (long)expires_at) < 0) {
- return FALSE;
+ return false;
}
}
- return TRUE;
+ return true;
}
-boolean
+bool
mh_oauth_cred_save(FILE *fp, mh_oauth_cred *cred, const char *user)
{
struct user_creds *user_creds;
/* Load existing creds if any. */
if (!load_creds(&user_creds, fp, cred->ctx)) {
- return FALSE;
+ return false;
}
if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) goto err;
free_user_creds(user_creds);
- return TRUE;
+ return true;
err:
free_user_creds(user_creds);
set_err(cred->ctx, MH_OAUTH_CRED_FILE);
- return FALSE;
+ return false;
}
mh_oauth_cred *
free(creds[i].user);
}
+ /* No longer need user_creds. result just uses its creds member. */
+ free(user_creds);
+
if (result == NULL) {
set_err_details(ctx, MH_OAUTH_CRED_USER_NOT_FOUND, user);
return NULL;
mh_oauth_sasl_client_response(size_t *res_len,
const char *user, const mh_oauth_cred *cred)
{
- size_t len = sizeof "user=" - 1
- + strlen(user)
- + sizeof "\1auth=Bearer " - 1
- + strlen(cred->access_token)
- + sizeof "\1\1" - 1;
- free(cred->ctx->sasl_client_res);
- cred->ctx->sasl_client_res = mh_xmalloc(len + 1);
- *res_len = len;
- sprintf(cred->ctx->sasl_client_res, "user=%s\1auth=Bearer %s\1\1",
- user, cred->access_token);
- return cred->ctx->sasl_client_res;
+ char **p;
+
+ p = &cred->ctx->sasl_client_res;
+ free(*p);
+ *p = concat("user=", user, "\1auth=Bearer ", cred->access_token, "\1\1", NULL);
+ *res_len = strlen(*p);
+ return *p;
}
/*******************************************************************************
/*
* Build null-terminated URL in the array pointed to by s. If the URL doesn't
- * fit within size (including the terminating null byte), return FALSE without *
+ * fit within size (including the terminating null byte), return false without *
* building the entire URL. Some of URL may already have been written into the
* result array in that case.
*/
-static boolean
+static bool
make_query_url(char *s, size_t size, CURL *curl, const char *base_url, ...)
{
- boolean result = FALSE;
+ bool result = false;
size_t len;
char *prefix;
va_list ap;
len = 0;
prefix = "";
} else {
- len = sprintf(s, "%s", base_url);
+ len = strlen(base_url);
+ if (len > size - 1) /* Less one for NUL. */
+ return false;
+ strcpy(s, base_url);
prefix = "?";
}
prefix = "&";
}
- result = TRUE;
+ result = true;
out:
va_end(ap);
}
static int
-debug_callback(const CURL *handle, curl_infotype type, const char *data,
+debug_callback(CURL *handle, curl_infotype type, char *data,
size_t size, void *userptr)
{
FILE *fp = userptr;
}
fwrite(data, 1, size, fp);
if (data[size - 1] != '\n') {
- fputs("\n", fp);
+ putc('\n', fp);
}
fflush(fp);
return 0;
size *= nmemb;
new_len = ctx->res_len + size;
if (new_len > sizeof ctx->res_body) {
- ctx->too_big = TRUE;
+ ctx->too_big = true;
return 0;
}
return size;
}
-static boolean
+static bool
post(struct curl_ctx *ctx, const char *url, const char *req_body)
{
CURL *curl = ctx->curl;
CURLcode status;
- ctx->too_big = FALSE;
+ ctx->too_big = false;
ctx->res_len = 0;
if (ctx->log != NULL) {
curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx->log);
}
- if ((status = curl_easy_setopt(curl, CURLOPT_URL, url)) != CURLE_OK) {
- return FALSE;
+ if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK) {
+ return false;
}
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req_body);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
+ if (has_prefix(url, "http://127.0.0.1:")) {
+ /* Hack: on Cygwin, curl doesn't fail to connect with ECONNREFUSED.
+ Instead, it waits to timeout. So set a really short timeout, but
+ just on localhost (for convenience of the user, and the test
+ suite). */
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2L);
+ }
+
status = curl_easy_perform(curl);
/* first check for error from callback */
if (ctx->too_big) {
- return FALSE;
+ return false;
}
/* now from curl */
if (status != CURLE_OK) {
- return FALSE;
+ return false;
}
- if ((status = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE,
- &ctx->res_code)) != CURLE_OK
- || (status = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE,
- &ctx->content_type)) != CURLE_OK) {
- return FALSE;
- }
-
- return TRUE;
+ return curl_easy_getinfo(curl,
+ CURLINFO_RESPONSE_CODE, &ctx->res_code) == CURLE_OK &&
+ curl_easy_getinfo(curl,
+ CURLINFO_CONTENT_TYPE, &ctx->content_type) == CURLE_OK;
}
/*******************************************************************************
*
* Even in that case, tokens has been allocated and must be freed.
*/
-static boolean
+static bool
parse_json(jsmntok_t **tokens, size_t *tokens_len,
const char *input, size_t input_len, FILE *log)
{
of the response body. */
*tokens = mh_xrealloc(*tokens, *tokens_len * sizeof **tokens);
}
- if (r <= 0) {
- return FALSE;
- }
- return TRUE;
+ return r > 0;
}
/*
int skip_tokens = 0;
/* whether the current token represents a field name */
/* The next token will be the value. */
- boolean is_key = TRUE;
+ bool is_key = true;
int i;
for (i = 1; i <= token_count; i++) {
if (skip_tokens > 0) {
skip_tokens--;
/* When we finish with the object or list, we'll have a key. */
- is_key = TRUE;
+ is_key = true;
continue;
}
if (is_key) {
- is_key = FALSE;
+ is_key = false;
continue;
}
key = input + tokens[i - 1].start;
(*val)[val_len] = '\0';
return;
}
- is_key = TRUE;
+ is_key = true;
}
}
*
* log may be used for debug-logging if not NULL.
*/
-static boolean
+static bool
get_json_strings(const char *input, size_t input_len, FILE *log, ...)
{
- boolean result = FALSE;
+ bool result = false;
jsmntok_t *tokens;
size_t tokens_len;
va_list ap;
goto out;
}
- result = TRUE;
+ result = true;
va_start(ap, log);
for (name = va_arg(ap, char *); name != NULL; name = va_arg(ap, char *)) {
free(tokens);
return result;
}
-
#endif