summaryrefslogtreecommitdiffstats
AgeCommit message (Expand)Author
2017-10-12Store all params of Content-Type into BTreeMap<String, String>Young Wu
2017-10-12Add Content-Disposition to mailYoung Wu
2017-10-06Update docsKartikaya Gupta
2017-07-18Add gitattributes for github linguistKartikaya Gupta
2017-01-26Minor updates to metadata in Cargo.tomlKartikaya Gupta
2016-12-26Fix documentation betterKartikaya Gupta
2016-12-26Version bumpKartikaya Gupta
2016-12-26Update documentationKartikaya Gupta
2016-12-26rustfmtKartikaya Gupta
2016-12-26add `get_body_raw`Wu Young
2016-12-23add name field to ParsedContentTypeWu Young
2016-12-04Version bump to 0.4.2Kartikaya Gupta
2016-12-04Update documentationKartikaya Gupta
2016-12-04Followup to 9f8013c2 (fix for issue #7) - deal with empty messagesKartikaya Gupta
2016-12-04Version bump to 0.4.1Kartikaya Gupta
2016-12-04Update documentationKartikaya Gupta
2016-12-04Fix for issue #7 - deal with lack of headersKartikaya Gupta
2016-12-04Less allocation by using case insensitive string comparisonJos van den Oever
2016-12-04Rename test and check for body as wellKartikaya Gupta
2016-12-03Fix panic when the mail body is missingJos van den Oever
2016-11-11Version bumpKartikaya Gupta
2016-11-11Update documentationKartikaya Gupta
2016-11-11Drop the redundant get_*_ci methods from MailHeaderMapBruce Guenter
2016-11-11Make MailHeaderMap perform case-insensitive searchesBruce Guenter
2016-10-02Version bumpKartikaya Gupta
2016-10-02Update docKartikaya Gupta
2016-10-02rustfmtKartikaya Gupta
2016-10-02Update main examples to include a dateparse usageKartikaya Gupta
2016-10-02Add dateparse documentationKartikaya Gupta
2016-10-02Add list of textual timezones from RFC 822Kartikaya Gupta
2016-10-02Expose the dateparse module from mailparseKartikaya Gupta
2016-10-02Fix dateparse to not overflow on larger timezonesKartikaya Gupta
2016-09-27Update dateparse to convert to epoch timestampsKartikaya Gupta
2016-09-25Add initial dateparse codeKartikaya Gupta
2016-09-02Disable travis email notificationsKartikaya Gupta
2016-09-02Add travis conf and crates.io badgeKartikaya Gupta
2016-06-22Reduce number of keywords to max of 5Kartikaya Gupta
2016-06-22Update documentationKartikaya Gupta
2016-06-22Add case-insensitive accessors for headersKartikaya Gupta
2016-06-22Handle case where an encoded-word has trailing whitespace inside the encodingKartikaya Gupta
2016-06-22Recognize encoded words in more placesKartikaya Gupta
2016-06-22Accept lowercase transfer-codings for the encoded-wordKartikaya Gupta
2016-06-22Update package documentation to mention MaildirKartikaya Gupta
2016-06-22Allow quoted-printable header words to be wrapped in quotesKartikaya Gupta
2016-06-18Fix .gitignoreKartikaya Gupta
2016-06-18Add generated documentationKartikaya Gupta
2016-06-18rustfmtKartikaya Gupta
2016-06-18Make formatting a little prettier using concatKartikaya Gupta
2016-06-18Add some crate metadataKartikaya Gupta
2016-06-18Add rustdoc to the source codeKartikaya Gupta
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
# External API v2 (Draft)

The **News app** offers a RESTful API which can be used to sync folders, feeds and items. The API also supports [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) which means that you can access the API from your browser using JavaScript.

In addition, an updater API is exposed which enables API users to run feed updates in parallel using a REST API or ownCloud console API.

## Conventions
This document uses the following conventions:

* Object aliases as comments
* Error objects are omitted

### Object Aliases As Comments

In order to only specify the JSON objects once, comments are used to alias them.

There are two types of aliases:
* Objects
* Object arrays

**Objects**:
```js
{
    "folder": { /* folder object */ },
}
```

means that the folder attributes will be listed inside the **folder** object

**Object arrays**:
```js
{
    "folders": [ /* array of folder objects */ ],
}
```

means that folder objects will be listed inside the **folders** array.

### Error Objects Are Omitted

This means that the error object will not be explicitely shown in the examples. All HTTP 400 response status codes contain an error object:

```json
{
    "error": {
        "code": 1,
        "message": "error message"
    }
}
```

## API Stability Contract

The API level will **change** if the following occurs:

* a required HTTP request header is added
* a required request parameter is added
* a field of a response object is removed
* a field of a response object is changed to a different datatype
* an HTTP response header is removed
* an HTTP response header is changed to a different datatype
* the meaning of an API call changes (e.g. /sync will not sync any more but show a sync timestamp)

The API level will **not change** if:

* a new HTTP response header is added
* an optional new HTTP request header is added
* a new response parameter is added (e.g. each item gets a new field "something": 1)
* The order of the JSON attributes is changed on any level (e.g. "id":3 is not the first field anymore, but the last)

You have to design your app with these things in mind!:

* **Don't depend on the order** of object attributes. In JSON it does not matter where the object attribute is since you access the value by name, not by index
* **Don't limit your app to the currently available attributes**. New ones might be added. If you don't handle them, ignore them
* **Use a library to compare versions**, ideally one that uses semantic versioning

## Authentication
Because REST is stateless you have to re-send user and password each time you access the API. Therefore running ownCloud **with SSL is highly recommended** otherwise **everyone in your network can log your credentials**.

The base URL for all calls is:

    https://yourowncloud.com/index.php/apps/news/api/v2

All defined routes in the Specification are appended to this url. To access the sync for instance you'd use the following url:

    https://yourowncloud.com/index.php/apps/news/api/v2/sync

Credentials are passed as an HTTP header using [HTTP basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side):

    Authorization: Basic $CREDENTIALS

where $CREDENTIALS is:

    base64(USER:PASSWORD)

This authentication/authorization method will be the recommended default until core provides an easy way to do OAuth

## Request Format
The required request headers are:
* **Accept**: application/json

Any request method except GET:
* **Content-Type**: application/json; charset=utf-8

Any route that allows caching:
* **If-None-Match**: an Etag, e.g. 6d82cbb050ddc7fa9cbb659014546e59. If no previous Etag is known, this header should be omitted

The request body is either passed in the URL in case of a **GET** request (e.g.: **?foo=bar&index=0**) or as JSON, e.g.:

```json
{
    "foo": "bar",
    "index": 0
}
```

## Response Format
The status codes are not always provided by the News app itself, but might also be returned because of ownCloud internal errors.

The following status codes can always be returned by ownCloud:
* **401**: The provided credentials to log into ownCloud are invalid.
* **403**: The user is not allowed to access the route. This can happen if for instance of only users in the admin group can access the route and the user is not in it.
* **404**: The route can not be found or the resource does not exist. Can also happen if for instance you are trying to delete a folder which does not exist.
* **5xx**: An internal server error occurred. This can happen if the server is in maintenance mode or because of other reasons.

The following status codes are returned by News:
* **200**: Everything went fine
* **304**: In case the resource was not modified, contains no response body. This means that you can ignore the request since everything is up to date.
* **400**: There was an app related error, check the **error** object if specified
* **409**: Conflict error which means that the resource exists already. Can be returned when updating (**PATCH**) or creating (**POST**) a resource, e.g. a folder

The response headers are:
* **Content-Type**: application/json; charset=utf-8
* **Etag**: A string containing a cache header of maximum length 64, e.g. 6d82cbb050ddc7fa9cbb659014546e59

The response body is a JSON structure that looks like this, which contains the actual data on the first level. The key is the resource in singular if it's a single resource or plural if its a collection. In case of HTTP 400, an error object is also present to help distinguishing between different error types:

```json
{
    "error": {
        "code": 1,
        "message": "error message"
    }
}
```

* **error**: Only present when an HTTP 400 is returned to help distinguishing between error causes
  * **code**: A unique error code
  * **message**: A translated error message. The user's configured locale is used.

In case of an **4xx** or **5xx** error the request was not successful and has to be retried. For instance marking items as read locally and syncing should send the same request again the next time the user syncs in case an error occured.

## Security Guidelines
Read the following notes carefully to prevent being subject to security exploits:
* You should always enforce SSL certificate verification and never offer a way to turn it off. Certificate verification is important to prevent MITM attacks which is especially important in the mobile world where users are almost always connected to untrusted networks. In case a user runs a self-signed certificate on his server ask him to either install his certificate on his device or direct him to one of the many ways to sign his certificate for free (most notably letsencrypt.com)
* All string fields in a JSON response **expect an item's body** are **not sanitized**. This means that if you do not escape it properly before rendering you will be vulnerable to [XSS](https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29) attacks
* Basic Auth headers can easily be decrypted by anyone since base64 is an encoding, not an encryption. Therefore only send them if you are accessing an HTTPS website or display an easy to understand warning if the user chooses HTTP
* When creating a feed you can choose to add basic auth authentication credentials. These must be stored in clear text so anyone with access to your database (however they might have achieved it, think of Sql injection) can read them and use them to access the website. You should warn the user about this.

## Syncing
All routes are given relative to the base API url, e.g.: **/sync** becomes  **https://yourowncloud.com/index.php/apps/news/api/v2/sync**

There are two usecases for syncing:
* **Initial sync**: the user does not have any data at all
* **Syncing local and remote changes**: the user has synced at least once and wants to submit and receive changes

### Initial Sync
The intial sync happens when a user adds an ownCloud account in your app. In that case you want to download all folders, feeds and unread/starred items. To do this, make the following request:

* **Method**: GET
* **Route**: /sync
* **Authentication**: [required](#authentication)
* **HTTP headers**:
  * **Accept: "application/json"**

This will return the following status codes:
* **200**: Success

and the following HTTP headers:
* **Content-Type**: application/json; charset=utf-8
* **Etag**: A string containing a cache header, maximum size 64 ASCII characters, e.g. 6d82cbb050ddc7fa9cbb659014546e59

and the following request body:
```js
{
    "folders": [ /* array of folder objects */ ],
    "feeds": [ /* array of feed objects */ ],
    "items": [ /* array of item objects */ ]
}
```

**Hint**: Each object is explained in more detail in a separate section:
* [Folders](#folders)
* [Feeds](#feeds)
* [Items](#items)


### Sync Local And Remote Changes
After the initial sync the app has all folders, feeds and items. Now you want to push changes and retrieve updates from the server. To do this, make the following request:

* **Method**: POST
* **Route**: /sync
* **Authentication**: [required](#authentication)
* **HTTP headers**:
  * **Content-Type: "application/json; charset=utf-8"**
  * **Accept: "application/json"**
  * **If-None-Match: "6d82cbb050ddc7fa9cbb659014546e59"** (Etag from the previous request to the /sync route)

with the following request body:

```js
{
    "items": [{
            // read and starred
            "id": 5,
            "isStarred": false,
            "isRead": true,
            "fingerprint": "08ffbcf94bd95a1faa6e9e799cc29054"
        }, {
            // only read
            "id": 6,
            "isRead": true,
            "fingerprint": "09ffbcf94bd95a1faa6e9e799cc29054"
        }, {
            // only starred
            "id": 7,
            "isStarred": false,
            "fingerprint": "18ffbcf94bd95a1faa6e9e799cc29054"
    }, /* etc */]
}
```

If no items have been read or starred, simply leave the **items** array empty, e.g.:

```js
{
    "items": []
}
```

The response matches the **GET** call, except there can be two different types of item objects:
* **[Full](#full)**: Contains all attributes
* **[Reduced](#reduced)**: Contains only **id**, **isRead** and **isStarred**

The deciding factor whether a full or reduced item object is being returned depends on the fingerprint in the request: If the fingerprint matches the record in the database a reduced item object is being returned, otherwise a full object is used. Both can occur in the same items array at the same time.

The idea behind this special handling is that if the fingerprint matches the record in the database, the actual item content did not change. Therefore it is enough to know the item status. This greatly reduces the amount sent over the Net which is especially important for mobile apps.

## Folders
Folders are represented using the following data structure:
```json
{
    "id": 3,
    "name": "funny stuff"
}
```

The attributes mean the following:
* **id**: 64bit Integer, id
* **name**: Abitrary long text, folder's name

### Deleting A Folder
To delete a folder, use the following request:
* **Method**: DELETE
* **Route**: /folders/{id}
* **Route Parameters**:
  * **{id}**: folder's id
* **Authentication**: [required](#authentication)

The following response is being returned:

Status codes:
* **200**: Folder was deleted successfully
* **404**: Folder does not exist

In case of an HTTP 200, the deleted folder is returned in full in the response, e.g.:

```js
{
    "folder": { /* folder object */ }
}
```
### Creating A Folder
To create a folder, use the following request:
* **Method**: POST
* **Route**: /folders
* **Authentication**: [required](#authentication)

with the following request body:
```json
{
    "name": "Folder name"
}
```

The following response is being returned:

Status codes:
* **200**: Folder was created successfully
* **400**: Folder creation error, check the error object:
  * **code**: 1: folder name is empty
* **409**: Folder with given name exists already

In case of an HTTP 200 or 409, the created or already existing folder is returned in full in the response, e.g.:

```js
{
    "folder": { /* folder object */ }
}
```

### Changing A Folder
The following attributes can be changed on the folder:
* **name**

To change any number of attributes on a folder, use the following request and provide as much attributes that can be changed as you want:
* **Method**: PATCH
* **Route**: /folders/{id}
* **Route Parameters**:
  * **{id}**: folder's id
* **Authentication**: [required](#authentication)

with the following request body:
```json
{
    "name": "New folder name"
}
```

* **name**: Abitrary long text, the folder's name

The following response is being returned:

Status codes:
* **200**: Folder was created successfully
* **400**: Folder creation error, check the error object:
  * **code**: 1: folder name is empty
* **409**: Folder with given name exists already
* Other ownCloud errors, see [Response Format](#response-format)

In case of an HTTP 200 or 409, the changed or already existing folder is returned in full in the response, e.g.:

```js
{
    "folder": { /* folder object */ }
}
```


## Feeds
Feeds are represented using the following data structure:

```json
{
    "id": 4,
    "name": "The Oatmeal - Comics, Quizzes, & Stories",
    "faviconLink": "http://theoatmeal.com/favicon.ico",
    "folderId": 3,
    "ordering": 0,
    "fullTextEnabled": false,
    "updateMode": 0,
    "i