aboutsummaryrefslogtreecommitdiff
path: root/mastoapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'mastoapi.c')
-rw-r--r--mastoapi.c259
1 files changed, 192 insertions, 67 deletions
diff --git a/mastoapi.c b/mastoapi.c
index 990898b..09e18a1 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -293,47 +293,54 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
snac snac;
if (user_open(&snac, login)) {
- /* check the login + password */
- if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) {
- /* success! redirect to the desired uri */
- xs *code = random_str();
+ const char *addr = xs_or(xs_dict_get(req, "remote-addr"),
+ xs_dict_get(req, "x-forwarded-for"));
- xs_free(*body);
+ if (badlogin_check(login, addr)) {
+ /* check the login + password */
+ if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) {
+ /* success! redirect to the desired uri */
+ xs *code = random_str();
- if (strcmp(redir, "urn:ietf:wg:oauth:2.0:oob") == 0) {
- *body = xs_dup(code);
- }
- else {
- if (xs_str_in(redir, "?") != -1)
- *body = xs_fmt("%s&code=%s", redir, code);
- else
- *body = xs_fmt("%s?code=%s", redir, code);
+ xs_free(*body);
- status = HTTP_STATUS_SEE_OTHER;
- }
+ if (strcmp(redir, "urn:ietf:wg:oauth:2.0:oob") == 0) {
+ *body = xs_dup(code);
+ }
+ else {
+ if (xs_str_in(redir, "?") != -1)
+ *body = xs_fmt("%s&code=%s", redir, code);
+ else
+ *body = xs_fmt("%s?code=%s", redir, code);
- /* if there is a state, add it */
- if (!xs_is_null(state) && *state) {
- *body = xs_str_cat(*body, "&state=");
- *body = xs_str_cat(*body, state);
- }
+ status = HTTP_STATUS_SEE_OTHER;
+ }
- srv_log(xs_fmt("oauth x-snac-login: '%s' success, redirect to %s",
+ /* if there is a state, add it */
+ if (!xs_is_null(state) && *state) {
+ *body = xs_str_cat(*body, "&state=");
+ *body = xs_str_cat(*body, state);
+ }
+
+ srv_log(xs_fmt("oauth x-snac-login: '%s' success, redirect to %s",
login, *body));
- /* assign the login to the app */
- xs *app = app_get(cid);
+ /* assign the login to the app */
+ xs *app = app_get(cid);
- if (app != NULL) {
- app = xs_dict_set(app, "uid", login);
- app = xs_dict_set(app, "code", code);
- app_add(cid, app);
+ if (app != NULL) {
+ app = xs_dict_set(app, "uid", login);
+ app = xs_dict_set(app, "code", code);
+ app_add(cid, app);
+ }
+ else
+ srv_log(xs_fmt("oauth x-snac-login: error getting app %s", cid));
+ }
+ else {
+ srv_debug(1, xs_fmt("oauth x-snac-login: login '%s' incorrect", login));
+ badlogin_inc(login, addr);
}
- else
- srv_log(xs_fmt("oauth x-snac-login: error getting app %s", cid));
}
- else
- srv_debug(1, xs_fmt("oauth x-snac-login: login '%s' incorrect", login));
user_free(&snac);
}
@@ -474,29 +481,36 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
snac user;
if (user_open(&user, login)) {
- /* check the login + password */
- if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) {
- /* success! create a new token */
- xs *tokid = random_str();
+ const char *addr = xs_or(xs_dict_get(req, "remote-addr"),
+ xs_dict_get(req, "x-forwarded-for"));
- srv_debug(1, xs_fmt("x-snac-new-token: "
+ if (badlogin_check(login, addr)) {
+ /* check the login + password */
+ if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) {
+ /* success! create a new token */
+ xs *tokid = random_str();
+
+ srv_debug(1, xs_fmt("x-snac-new-token: "
"successful login for %s, new token %s", login, tokid));
- xs *token = xs_dict_new();
- token = xs_dict_append(token, "token", tokid);
- token = xs_dict_append(token, "client_id", "snac-client");
- token = xs_dict_append(token, "client_secret", "");
- token = xs_dict_append(token, "uid", login);
- token = xs_dict_append(token, "code", "");
+ xs *token = xs_dict_new();
+ token = xs_dict_append(token, "token", tokid);
+ token = xs_dict_append(token, "client_id", "snac-client");
+ token = xs_dict_append(token, "client_secret", "");
+ token = xs_dict_append(token, "uid", login);
+ token = xs_dict_append(token, "code", "");
- token_add(tokid, token);
+ token_add(tokid, token);
- *ctype = "text/plain";
- xs_free(*body);
- *body = xs_dup(tokid);
- }
+ *ctype = "text/plain";
+ xs_free(*body);
+ *body = xs_dup(tokid);
+ }
+ else
+ badlogin_inc(login, addr);
- user_free(&user);
+ user_free(&user);
+ }
}
}
}
@@ -898,7 +912,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
const char *o_href = xs_dict_get(v, "href");
const char *name = xs_dict_get(v, "name");
- if (xs_match(type, "image/*|video/*|Image|Video")) { /* */
+ if (xs_match(type, "image/*|video/*|audio/*|Image|Video")) { /* */
xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt));
xs *href = make_url(o_href, proxy, 1);
@@ -910,7 +924,8 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
d = xs_dict_append(d, "remote_url", href);
d = xs_dict_append(d, "description", name);
- d = xs_dict_append(d, "type", (*type == 'v' || *type == 'V') ? "video" : "image");
+ d = xs_dict_append(d, "type", (*type == 'v' || *type == 'V') ? "video" :
+ (*type == 'a' || *type == 'A') ? "audio" : "image");
matt = xs_list_append(matt, d);
}
@@ -990,7 +1005,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
const char *o_url = xs_dict_get(icon, "url");
if (!xs_is_null(o_url)) {
- xs *url = make_url(o_url, snac->actor, 1);
+ xs *url = make_url(o_url, snac ? snac->actor : NULL, 1);
xs *nm = xs_strip_chars_i(xs_dup(name), ":");
d1 = xs_dict_append(d1, "shortcode", nm);
@@ -1193,10 +1208,13 @@ int process_auth_token(snac *snac, const xs_dict *req)
return logged_in;
}
+
void credentials_get(char **body, char **ctype, int *status, snac snac)
{
xs *acct = xs_dict_new();
+ const xs_val *bot = xs_dict_get(snac.config, "bot");
+
acct = xs_dict_append(acct, "id", snac.md5);
acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid"));
acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid"));
@@ -1206,7 +1224,7 @@ void credentials_get(char **body, char **ctype, int *status, snac snac)
acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio"));
acct = xs_dict_append(acct, "url", snac.actor);
acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE));
- acct = xs_dict_append(acct, "bot", xs_dict_get(snac.config, "bot"));
+ acct = xs_dict_append(acct, "bot", xs_stock(xs_is_true(bot) ? XSTYPE_TRUE : XSTYPE_FALSE));
acct = xs_dict_append(acct, "emojis", xs_stock(XSTYPE_LIST));
xs *src = xs_json_loads("{\"privacy\":\"public\", \"language\":\"en\","
@@ -1220,7 +1238,7 @@ void credentials_get(char **body, char **ctype, int *status, snac snac)
src = xs_dict_set(src, "sensitive",
strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
- src = xs_dict_set(src, "bot", xs_dict_get(snac.config, "bot"));
+ src = xs_dict_set(src, "bot", xs_stock(xs_is_true(bot) ? XSTYPE_TRUE : XSTYPE_FALSE));
xs *avatar = NULL;
const char *av = xs_dict_get(snac.config, "avatar");
@@ -1319,7 +1337,7 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
const char *max_id = xs_dict_get(args, "max_id");
const char *since_id = xs_dict_get(args, "since_id");
- const char *min_id = xs_dict_get(args, "min_id");
+ const char *min_id = xs_dict_get(args, "min_id"); /* unsupported old-to-new navigation */
const char *limit_s = xs_dict_get(args, "limit");
int limit = 0;
int cnt = 0;
@@ -1330,7 +1348,7 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
if (limit == 0)
limit = 20;
- if (index_desc_first(f, md5, 0)) {
+ if (min_id == NULL && index_desc_first(f, md5, 0)) {
do {
xs *msg = NULL;
@@ -1348,13 +1366,6 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
break;
}
- /* only returns entries newer than min_id */
- /* what does really "Return results immediately newer than ID" mean? */
- if (min_id) {
- if (strcmp(md5, MID_TO_MD5(min_id)) == 0)
- break;
- }
-
/* get the entry */
if (user) {
if (!valid_status(timeline_get_by_md5(user, md5, &msg)))
@@ -1438,8 +1449,35 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
}
+xs_str *timeline_link_header(const char *endpoint, xs_list *timeline)
+/* returns a Link header with paging information */
+{
+ xs_str *s = NULL;
+
+ if (xs_list_len(timeline) == 0)
+ return NULL;
+
+ const xs_dict *first_st = xs_list_get(timeline, 0);
+ const xs_dict *last_st = xs_list_get(timeline, -1);
+ const char *first_id = xs_dict_get(first_st, "id");
+ const char *last_id = xs_dict_get(last_st, "id");
+ const char *host = xs_dict_get(srv_config, "host");
+ const char *protocol = xs_dict_get_def(srv_config, "protocol", "https");
+
+ s = xs_fmt(
+ "<%s:/" "/%s%s?max_id=%s>; rel=\"next\", "
+ "<%s:/" "/%s%s?since_id=%s>; rel=\"prev\"",
+ protocol, host, endpoint, last_id,
+ protocol, host, endpoint, first_id);
+
+ srv_debug(1, xs_fmt("timeline_link_header %s", s));
+
+ return s;
+}
+
+
int mastoapi_get_handler(const xs_dict *req, const char *q_path,
- char **body, int *b_size, char **ctype)
+ char **body, int *b_size, char **ctype, xs_str **link)
{
(void)b_size;
@@ -1699,6 +1737,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
xs *ifn = user_index_fn(&snac1, "private");
xs *out = mastoapi_timeline(&snac1, args, ifn);
+ *link = timeline_link_header("/api/v1/timelines/home", out);
+
*body = xs_json_dumps(out, 4);
*ctype = "application/json";
status = HTTP_STATUS_OK;
@@ -1763,8 +1803,14 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
xs *out = xs_list_new();
const xs_dict *v;
const xs_list *excl = xs_dict_get(args, "exclude_types[]");
+ const char *min_id = xs_dict_get(args, "min_id");
const char *max_id = xs_dict_get(args, "max_id");
+ if (dbglevel) {
+ xs *js = xs_json_dumps(args, 0);
+ srv_debug(1, xs_fmt("mastoapi_notifications args %s", js));
+ }
+
xs_list_foreach(l, v) {
xs *noti = notify_get(&snac1, v);
@@ -1795,6 +1841,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
continue;
}
+ if (min_id) {
+ if (strcmp(fid, min_id) <= 0) {
+ continue;
+ }
+ }
+
/* convert the type */
if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0)
type = "favourite";
@@ -1842,6 +1894,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
out = xs_list_append(out, mn);
}
+ srv_debug(1, xs_fmt("mastoapi_notifications count %d", xs_list_len(out)));
+
*body = xs_json_dumps(out, 4);
*ctype = "application/json";
status = HTTP_STATUS_OK;
@@ -2273,9 +2327,22 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
}
else
if (strcmp(cmd, "/v1/markers") == 0) { /** **/
- *body = xs_dup("{}");
- *ctype = "application/json";
- status = HTTP_STATUS_OK;
+ if (logged_in) {
+ const xs_list *timeline = xs_dict_get(args, "timeline[]");
+ xs_str *json = NULL;
+ if (!xs_is_null(timeline))
+ json = xs_json_dumps(markers_get(&snac1, timeline), 4);
+
+ if (!xs_is_null(json))
+ *body = json;
+ else
+ *body = xs_dup("{}");
+
+ *ctype = "application/json";
+ status = HTTP_STATUS_OK;
+ }
+ else
+ status = HTTP_STATUS_UNAUTHORIZED;
}
else
if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/
@@ -2310,6 +2377,37 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
if (xs_is_null(offset) || strcmp(offset, "0") == 0) {
/* reply something only for offset 0; otherwise,
apps like Tusky keep asking again and again */
+ if (xs_startswith(q, "https://")) {
+ xs *md5 = xs_md5_hex(q, strlen(q));
+
+ if (!timeline_here(&snac1, md5)) {
+ xs *object = NULL;
+ int status;
+
+ status = activitypub_request(&snac1, q, &object);
+ snac_debug(&snac1, 1, xs_fmt("Request searched URL %s %d", q, status));
+
+ if (valid_status(status)) {
+ /* got it; also request the actor */
+ const char *attr_to = get_atto(object);
+ xs *actor_obj = NULL;
+
+ if (!xs_is_null(attr_to)) {
+ status = actor_request(&snac1, attr_to, &actor_obj);
+
+ snac_debug(&snac1, 1, xs_fmt("Request author %s of %s %d", attr_to, q, status));
+
+ if (valid_status(status)) {
+ /* add the actor */
+ actor_add(attr_to, actor_obj);
+
+ /* add the post to the timeline */
+ timeline_add(&snac1, q, object);
+ }
+ }
+ }
+ }
+ }
if (!xs_is_null(q)) {
if (xs_is_null(type) || strcmp(type, "accounts") == 0) {
@@ -2945,6 +3043,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
}
}
+ else
if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/
if (logged_in) {
xs *l = xs_split(cmd, "/");
@@ -2972,9 +3071,35 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
}
}
}
+ }
+ else if (strcmp(cmd, "/v1/markers") == 0) { /** **/
+ xs_str *json = NULL;
+ if (logged_in) {
+ const xs_str *home_marker = xs_dict_get(args, "home[last_read_id]");
+ if (xs_is_null(home_marker)) {
+ const xs_dict *home = xs_dict_get(args, "home");
+ if (!xs_is_null(home))
+ home_marker = xs_dict_get(home, "last_read_id");
+ }
+
+ const xs_str *notify_marker = xs_dict_get(args, "notifications[last_read_id]");
+ if (xs_is_null(notify_marker)) {
+ const xs_dict *notify = xs_dict_get(args, "notifications");
+ if (!xs_is_null(notify))
+ notify_marker = xs_dict_get(notify, "last_read_id");
+ }
+ json = xs_json_dumps(markers_set(&snac, home_marker, notify_marker), 4);
+ }
+ if (!xs_is_null(json))
+ *body = json;
else
- status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
+ *body = xs_dup("{}");
+
+ *ctype = "application/json";
+ status = HTTP_STATUS_OK;
}
+ else
+ status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
/* user cleanup */
if (logged_in)