diff --git a/cassandane/Cassandane/Cyrus/JMAPCore.pm b/cassandane/Cassandane/Cyrus/JMAPCore.pm index 176010d7cd..ea69efb7b2 100644 --- a/cassandane/Cassandane/Cyrus/JMAPCore.pm +++ b/cassandane/Cassandane/Cyrus/JMAPCore.pm @@ -66,6 +66,7 @@ sub new $config->set(caldav_realm => 'Cassandane', conversations => 'yes', httpmodules => 'carddav caldav jmap', + jmap_max_size_upload => '1k', httpallowcompress => 'no'); return $class->SUPER::new({ @@ -998,4 +999,54 @@ sub test_get_session_rename_race $self->assert_not_null($RawResponse->{headers}{'retry-after'}); } +sub test_blob_upload_too_large + :min_version_3_9 :needs_component_jmap :JMAPExtensions +{ + my $self = shift; + my $jmap = $self->{jmap}; + + xlog "Assert Problem Details report"; + my $httpReq = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => 'X' x 1025, + }; + my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane'), $httpReq); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($httpReq, $httpRes); + } + $self->assert_str_equals("413", $httpRes->{status}); + my $res = eval { decode_json($httpRes->{content}) }; + $self->assert_str_equals("413", $res->{status}); + $self->assert_str_equals("Content Too Large", $res->{title}); + $self->assert_str_equals("urn:ietf:params:jmap:error:limit", $res->{type}); + $self->assert_str_equals("maxSizeUpload", $res->{limit}); +} + +sub test_blob_upload_bad_url + :min_version_3_9 :needs_component_jmap :JMAPExtensions +{ + my $self = shift; + my $jmap = $self->{jmap}; + + xlog "Assert Problem Details report"; + my $httpReq = { + headers => { + 'Authorization' => $jmap->auth_header(), + }, + content => 'Hello World', + }; + my $httpRes = $jmap->ua->post($jmap->uploaduri('cassandane') . 'X', $httpReq); + if ($ENV{DEBUGJMAP}) { + warn "JMAP " . Dumper($httpReq, $httpRes); + } + $self->assert_str_equals("404", $httpRes->{status}); + my $res = eval { decode_json($httpRes->{content}) }; + $self->assert_str_equals("404", $res->{status}); + $self->assert_str_equals("Not Found", $res->{title}); + $self->assert_str_equals("about:blank", $res->{type}); + $self->assert_str_equals("unknown uploadUrl", $res->{detail}); +} + 1; diff --git a/imap/http_jmap.c b/imap/http_jmap.c index 09c18fc424..cbd257ac8b 100644 --- a/imap/http_jmap.c +++ b/imap/http_jmap.c @@ -1022,6 +1022,7 @@ static int jmap_upload(struct transaction_t *txn) const char **hdr; time_t now = time(NULL); struct appendstate as; + char *accountid = NULL; char *normalisedtype = NULL; int rawmessage = 0; struct conversations_state *cstate = NULL; @@ -1041,14 +1042,16 @@ static int jmap_upload(struct transaction_t *txn) if (datalen > (size_t) my_jmap_settings.limits[MAX_SIZE_UPLOAD]) { txn->error.desc = "JSON upload byte size exceeds maxSizeUpload"; - return HTTP_CONTENT_TOO_LARGE; + ret = HTTP_CONTENT_TOO_LARGE; + goto done; } /* Resource must be {accountId}/ with no trailing path */ - char *accountid = xstrdup(txn->req_tgt.resource); + accountid = xstrdup(txn->req_tgt.resource); char *slash = strchr(accountid, '/'); if (!slash || *(slash + 1) != '\0') { ret = HTTP_NOT_FOUND; + txn->error.desc = "unknown uploadUrl"; goto done; } *slash = '\0'; @@ -1058,6 +1061,7 @@ static int jmap_upload(struct transaction_t *txn) syslog(LOG_ERR, "jmap_upload: can't open conversations db for %s: %s", accountid, error_message(r)); ret = HTTP_SERVER_ERROR; + txn->error.desc = "can't open upload conversations db"; goto done; } @@ -1066,6 +1070,7 @@ static int jmap_upload(struct transaction_t *txn) syslog(LOG_ERR, "jmap_upload: can't open upload collection for %s: %s", accountid, error_message(r)); ret = HTTP_NOT_FOUND; + txn->error.desc = "can't open upload collection"; goto done; } @@ -1197,8 +1202,15 @@ static int jmap_upload(struct transaction_t *txn) append_abort(&as); syslog(LOG_ERR, "append_fromstage(%s) failed: %s", mailbox_name(mailbox), error_message(r)); - ret = HTTP_SERVER_ERROR; - txn->error.desc = "append_fromstage() failed"; + if (r == IMAP_QUOTA_EXCEEDED || r == IMAP_NO_OVERQUOTA) { + /* XXX Should never happen, but DTRT anyways */ + ret = HTTP_NO_STORAGE; + txn->error.desc = "Mailbox is over quota"; + } + else { + ret = HTTP_SERVER_ERROR; + txn->error.desc = "append_fromstage() failed"; + } goto done; } @@ -1224,6 +1236,8 @@ static int jmap_upload(struct transaction_t *txn) json_object_set_new(resp, "expires", json_string(datestr)); json_object_set_new(resp, "type", json_string(normalisedtype)); + ret = HTTP_CREATED; + done: free(normalisedtype); free(accountid); @@ -1250,11 +1264,29 @@ static int jmap_upload(struct transaction_t *txn) // checkpoint before replying sync_checkpoint(httpd_in); - /* Output the JSON object */ - if (resp) - ret = json_response(HTTP_CREATED, txn, resp); + if (!resp) { + /* Create Problem Details Object */ + const char *type, *limit; - return ret; + if (ret == HTTP_CONTENT_TOO_LARGE) { + type = "urn:ietf:params:jmap:error:limit"; + limit = "maxSizeUpload"; + } + else { + type = "about:blank"; + limit = NULL; + } + + resp = json_pack("{s:s s:s* s:s s:i s:s}", + "type", type, + "limit", limit, + "title", error_message(ret) + 4, + "status", atoi(error_message(ret)), + "detail", txn->error.desc); + } + + /* Output the JSON object */ + return json_response(ret, txn, resp); } /* Handle a GET on the session endpoint */